2 * the mvPlayList object code
3 * only included if playlist object found
5 * part of mwEmbed media projects see:
6 * http://www.mediawiki.org/wiki/Media_Projects_Overview
8 * @author: Michael Dale mdale@wikimedia.org
11 var mv_default_playlist_attributes
= {
12 // playlist attributes :
19 // playlist user controlled features
24 // enable sequencer? (only display top frame no navigation or accompanying text
27 // The call back rate for animations and internal timers in ms: 33 is about 30 frames a second:
28 var MV_ANIMATION_CB_RATE
= 33;
31 // 10 possible colors for clips: (can be in hexadecimal)
32 var mv_clip_colors
= new Array( 'aqua', 'blue', 'fuchsia', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red' );
34 // The base url for requesting stream metadata
35 if ( typeof wgServer
== 'undefined' ) {
36 var defaultMetaDataProvider
= 'http://metavid.org/overlay/archive_browser/export_cmml?stream_name=';
38 var defaultMetaDataProvider
= wgServer
+ wgScript
+ '?title=Special:MvExportStream&feed_format=roe&stream_name=';
41 * The playlist Object implements ~most~ of embedVideo but we don't inherit (other than to use the control builder)
42 * because pretty much every function has to be changed for the playlist context
44 var mvPlayList = function( element
) {
45 return this.init( element
);
47 // set up the mvPlaylist object
48 mvPlayList
.prototype = {
49 instanceOf
:'mvPlayList',
59 loading_external_data
:true, // if we are loading external data (set to loading by default)
60 //set initial state to "paused"
64 playlist_buffer_time
: 20, // how many seconds of future clips we should buffer
66 interface_url
:null, // the interface url
68 default_track
:null, // the default track to add clips to.
69 // the layout for the playlist object
72 clip_desc
:.63, // displays the clip description
73 clip_aspect
:1.33, // 4/3 video aspect ratio
74 seq
:.25, // display clip thumbnails
75 seq_thumb
:.25, // size for thumbnails (same as seq by default)
76 seq_nav
:0, // for a nav bar at the base (currently disabled)
77 // some pl_layout info:
81 // embed object type support system;
87 'volume_control':true,
90 'playlist_swap_loader':true // if the object supports playlist functions
92 init : function( element
) {
93 js_log( 'mvPlayList:init:' );
95 this.default_track
= null;
97 this.activeClipList
= new activeClipList();
98 // add default track & default track pointer:
99 this.tracks
[0] = new trackObj( { 'inx':0 } );
100 this.default_track
= this.tracks
[0];
102 // get all the attributes:
103 for ( var attr
in mv_default_playlist_attributes
) {
104 if ( element
.getAttribute( attr
) ) {
105 this[attr
] = element
.getAttribute( attr
);
106 // js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+'elm_val:' + element.getAttribute(attr) + "\n (set by elm)");
108 this[attr
] = mv_default_playlist_attributes
[attr
];
109 // js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+ 'elm_val:' + element.getAttribute(attr) + "\n (set by attr)");
112 // make sure height and width are int:
113 this.width
= parseInt( this.width
);
114 this.height
= parseInt( this.height
);
116 // if style is set override width and height
117 if ( element
.style
.width
)this.width
= parseInt( element
.style
.width
.replace( 'px', '' ) );
118 if ( element
.style
.height
)this.height
= parseInt( element
.style
.height
.replace( 'px', '' ) );
120 // if controls=false hide the title and the controls:
121 if ( this.controls
=== false ) {
122 this.pl_layout
.control_height
= 0;
123 this.pl_layout
.title_bar_height
= 0;
125 // setup the controlBuilder object:
126 this.ctrlBuilder
= new ctrlBuilder( this );
129 // the element has now been swapped into the dom:
130 on_dom_swap:function() {
131 js_log( 'pl: dom swap' );
132 // get and load the html:
135 // run inheritEmbedObj on every clip (we have changed the playback method)
136 inheritEmbedObj:function() {
137 $j
.each( this.tracks
, function( i
, track
) {
138 track
.inheritEmbedObj();
141 doOptionsHTML:function() {
142 // grab "options" use current clip:
143 this.cur_clip
.embed
.doOptionsHTML();
145 // pulls up the video editor inline
146 doEditor:function() {
147 // black out the page:
148 // $j('body').append('<div id="ui-widget-overlay"/> <div id="modalbox" class="ui-widget ui-widget-content ui-corner-all modal_editor">' );
149 $j( 'body' ).append( '<div class="ui-widget-overlay" style="width: 100%; height: 100%px; z-index: 10;"></div>' );
150 $j( 'body' ).append( '<div id="sequencer_target" style="z-index:11;position:fixed;top:10px;left:10px;right:10px;bottom:10px;" ' +
151 'class="ui-widget ui-widget-content ui-corner-all"></div>' );
153 // @@todo clone the playlist (for faster startup)
155 * var this_plObj_Clone = $j('#'+this.id).get(0).cloneNode(true);
156 * this_plObj_Clone.sequencer=true;
157 * this_plObj_Clone.id= 'seq_plobj';
162 $j( "#sequencer_target" ).sequencer( {
163 "mv_pl_src" : this.src
167 showPlayerselect:function() {
168 this.cur_clip
.embed
.showPlayerselect();
170 closeDisplayedHTML:function() {
171 this.cur_clip
.embed
.closeDisplayedHTML();
173 showDownload:function() {
174 this.cur_clip
.embed
.showDownload();
176 showShare:function() {
177 var embed_code
= '<script type="text/javascript" ' +
178 'src="' + mv_embed_path
+ 'mv_embed.js"></script> ' + "\n" +
179 '<playlist id="' + this.id
+ '" ';
181 embed_code
+= 'src="' + this.src
+ '" />';
183 embed_code
+= '>' + "\n";
184 embed_code
+= escape( this.data
);
185 embed_code
+= '<playlist/>';
187 this.cur_clip
.embed
.showShare( embed_code
);
189 timedTextSources:function() {
192 getPlaylist:function() {
193 js_log( "f:getPlaylist: " + this.srcType
);
194 // @@todo lazy load plLib
195 eval( 'var plObj = ' + this.srcType
+ 'Playlist;' );
196 // import methods from the plObj to this
197 for ( var method
in plObj
) {
198 // js parent preservation for local overwritten methods
199 if ( this[method
] )this['parent_' + method
] = this[method
];
200 this[method
] = plObj
[method
];
201 js_log( 'inherit:' + method
);
204 if ( typeof this.doParse
!= 'function' ) {
205 js_log( 'error: method doParse not found in plObj' + this.srcType
);
209 if ( typeof this.doParse
== 'function' ) {
210 if ( this.doParse() ) {
211 this.doWhenParseDone();
213 js_log( "error: failed to parse playlist" );
215 // error or parse needs to do ajax requests
219 doNativeWarningCheck:function() {
220 var clip
= this.default_track
.clips
[0];
222 return clip
.embed
.doNativeWarningCheck();
225 doWhenParseDone:function() {
226 js_log( 'f:doWhenParseDone' );
227 // do additional init for clips:
230 _this
.clip_ready_count
= 0;
231 for ( var i
in this.default_track
.clips
) {
232 var clip
= this.default_track
.clips
[i
];
233 if ( clip
.embed
.load_error
) {
234 var error
= clip
.embed
.load_error
;
235 // break on any clip we can't playback:
238 if ( clip
.embed
.ready_to_play
) {
239 _this
.clip_ready_count
++;
242 // js_log('clip sources count: '+ clip.embed.media_element.sources.length);
243 clip
.embed
.on_dom_swap();
244 if ( clip
.embed
.loading_external_data
== false &&
245 clip
.embed
.init_with_sources_loadedDone
== false ) {
246 clip
.embed
.init_with_sources_loaded();
250 // @@todo for some plugins we have to conform types of clips
251 // ie vlc can play flash _followed_by_ ogg _followed_by_ whatever
253 // native ff 3.1a2 can only play ogg
255 this.load_error
= error
;
256 this.is_ready
= false;
257 } else if ( _this
.clip_ready_count
== _this
.getClipCount() ) {
258 js_log( "done init all clips: " + _this
.clip_ready_count
+ ' = ' + _this
.getClipCount() );
259 this.doWhenClipLoadDone();
261 js_log( "only " + _this
.clip_ready_count
+ " clips done, scheduling callback:" );
262 if ( !mvJsLoader
.load_error
) // re-issue request if no load error:
263 setTimeout( function(){
264 _this
.doWhenParseDone();
269 doWhenClipLoadDone:function() {
270 js_log( 'mvPlaylist:doWhenClipLoadDone' );
271 this.ready_to_play
= true;
272 this.loading
= false;
275 getDuration:function( regen
) {
276 // js_log("GET PL DURRATION for : "+ this.tracks[this.default_track_id].clips.length + 'clips');
277 if ( !regen
&& this.pl_duration
)
278 return this.pl_duration
;
281 $j
.each( this.default_track
.clips
, function( i
, clip
) {
283 clip
.dur_offset
= durSum
;
284 // only calculate the solo Duration if a smil clip that could contain a transition:
285 if ( clip
.instanceOf
== 'mvSMILClip' ) {
286 // don't include transition time (for playlist_swap_loader compatible clips)
287 durSum
+= clip
.getSoloDuration();
289 durSum
+= clip
.getDuration();
292 js_log( "ERROR: clip " + clip
.id
+ " not ready" );
295 this.pl_duration
= durSum
;
296 // js_log("return dur: " + this.pl_duration);
297 return this.pl_duration
;
299 getTimeReq:function() {
300 // playlist does not really support time request atm ( in theory in the future we could embed playlists with temporal urls)
301 return '0:0:0/' + seconds2npt( this.getDuration() );
303 getDataSource:function() {
304 js_log( "f:getDataSource " + this.src
);
305 // determine the type / first is it m3u or xml?
306 var pl_parent
= this;
307 this.makeURLAbsolute();
308 if ( this.src
!= null ) {
309 do_request( this.src
, function( data
) {
310 pl_parent
.data
= data
;
311 pl_parent
.getSourceType();
318 getSourceType:function() {
319 js_log( 'data type of: ' + this.src
+ ' = ' + typeof ( this.data
) + "\n" + this.data
);
321 // if not external use different detection matrix
322 if ( this.loading_external_data
) {
323 if ( typeof this.data
== 'object' ) {
325 // object assume xml (either xspf or rss)
326 plElm
= this.data
.getElementsByTagName( 'playlist' )[0];
328 if ( plElm
.getAttribute( 'xmlns' ) == 'http://xspf.org/ns/0/' ) {
329 this.srcType
= 'xspf';
332 // check itunes style rss "items"
333 rssElm
= this.data
.getElementsByTagName( 'rss' )[0];
335 if ( rssElm
.getAttribute( 'xmlns:itunes' ) == 'http://www.itunes.com/dtds/podcast-1.0.dtd' ) {
336 this.srcType
= 'itunes';
339 // check for smil tag:
340 smilElm
= this.data
.getElementsByTagName( 'smil' )[0];
342 // don't check dtd yet.. (have not defined the smil subset)
343 this.srcType
= 'smil';
345 } else if ( typeof this.data
== 'string' ) {
347 // look at the first line:
348 var first_line
= this.data
.substring( 0, this.data
.indexOf( "\n" ) );
349 js_log( 'first line: ' + first_line
);
351 if ( first_line
.indexOf( '#EXTM3U' ) != -1 ) {
352 this.srcType
= 'm3u';
353 } else if ( first_line
.indexOf( '<smil' ) != -1 ) {
354 // @@todo parse string
355 this.srcType
= 'smil';
359 if ( this.srcType
) {
360 js_log( 'is of type:' + this.srcType
);
363 // unknown playlist type
364 js_log( 'unknown playlist type?' );
366 this.innerHTML
= 'error: unknown playlist type at url:<br> ' + this.src
;
368 this.innerHTML
= 'error: unset src or unknown inline playlist data<br>';
372 // simple function to make a path into an absolute url if its not already
373 makeURLAbsolute:function() {
375 if ( this.src
.indexOf( '://' ) == -1 ) {
376 var purl
= mw
.parseUri( document
.URL
);
377 if ( this.src
.charAt( 0 ) == '/' ) {
378 this.src
= purl
.protocol
+ '://' + purl
.host
+ this.src
;
380 this.src
= purl
.protocol
+ '://' + purl
.host
+ purl
.directory
+ this.src
;
385 // set up minimal media_element emulation:
388 supports_url_time_encoding
:true
391 // @@todo needs to update for multi-track clip counts
392 getClipCount:function() {
393 return this.default_track
.clips
.length
;
396 // takes in the playlist
397 // inherits all the properties
398 // swaps in the playlist object html/interface div
400 js_log( 'mvPlaylist:getHTML: loading:' + this.loading
);
401 if ( this.loading
) {
402 $j( '#' + this.id
).html( 'loading playlist...' );
403 if ( this.loading_external_data
) {
404 // load the data source chain of functions (to update the innerHTML)
405 this.getDataSource();
407 // detect datatype and parse directly:
408 this.getSourceType();
411 // check for empty playlist otherwise renderDisplay:
412 if ( this.default_track
.getClipCount() == 0 ) {
413 $j( this ).html( 'empty playlist' );
416 this.renderDisplay();
420 renderDisplay:function() {
421 js_log( 'mvPlaylist:renderDisplay:: track length: ' + this.default_track
.getClipCount() );
424 // append container and videoPlayer;
425 $j( this ).html( '<div id="dc_' + this.id
+ '" style="width:' + this.width
+ 'px;' +
426 'height:' + ( this.height
+ this.pl_layout
.title_bar_height
+
427 this.pl_layout
.control_height
) + 'px;position:relative;">' +
429 if ( this.controls
== true ) {
430 var cpos
= _this
.height
+ _this
.pl_layout
.title_bar_height
;
431 // give more space if not in sequence:
432 cpos
+= ( this.sequencer
) ? 2:5;
434 $j( '#dc_' + _this
.id
).append(
435 '<div style="font-size:13px;border:solid thin;width:' + this.width
+ 'px;" id="ptitle_' + this.id
+ '"></div>' +
436 '<div class="' + this.ctrlBuilder
.pClass
+ '" style="position:absolute;top:' + cpos
+ 'px">' +
437 '<div class="ui-widget-header ui-helper-clearfix control-bar" ' +
438 'style="width:' + _this
.width
+ 'px" >' +
439 _this
.getControlsHTML() +
444 // once the controls are in the DOM add hooks:
445 this.ctrlBuilder
.addControlHooks( );
447 // just append the video:
448 $j( '#dc_' + _this
.id
).append(
449 '<div class="' + this.ctrlBuilder
.pClass
+ '" style="position:absolute;top:' + ( _this
.height
+ _this
.pl_layout
.title_bar_height
+ 4 ) + 'px"></div>'
452 this.setupClipDisplay();
454 // update the title and status bar
455 this.updateBaseStatus();
456 this.doSmilActions();
458 setupClipDisplay:function() {
459 js_log( 'mvPlaylist:setupClipDisplay:: clip len:' + this.default_track
.clips
.length
);
461 $j
.each( this.default_track
.clips
, function( i
, clip
) {
462 var cout
= '<div class="clip_container cc_" id="clipDesc_' + clip
.id
+ '" ' +
463 'style="display:none;position:absolute;text-align: center;width:' + _this
.width
+ 'px;' +
464 'height:' + ( _this
.height
) + 'px;' +
465 'top:' + this.title_bar_height
+ 'px;left:0px;';
466 if ( _this
.controls
) {
467 cout
+= 'border:solid thin black;';
470 $j( '#dc_' + _this
.id
).append( cout
);
471 // update the embed html:
472 clip
.embed
.height
= _this
.height
;
473 clip
.embed
.width
= _this
.width
;
474 clip
.embed
.play_button
= false;
475 clip
.embed
.controls
= false;
477 clip
.embed
.getHTML();// get the thubnails for everything
479 $j( clip
.embed
).css( {
480 'position':"absolute",
484 if ( $j( '#clipDesc_' + clip
.id
).length
!= 0 ) {
485 js_log( "should set: #clipDesc_" + clip
.id
+ ' to: ' + $j( clip
.embed
).html() )
486 $j( '#clipDesc_' + clip
.id
).append( clip
.embed
);
488 js_log( 'cound not find: clipDesc_' + clip
.id
);
492 $j( '#clipDesc_' + this.cur_clip
.id
).css( { display
:'inline' } );
494 updateThumbPerc:function( perc
) {
495 // get float seconds:
496 var float_sec
= ( this.getDuration() * perc
);
497 this.updateThumbTime( float_sec
);
499 updateThumbTime:function( float_sec
) {
500 // update display & cur_clip:
502 var clip_float_sec
= 0;
503 // js_log('seeking clip: ');
504 for ( var i
in this.default_track
.clips
) {
505 var clip
= this.default_track
.clips
[i
];
506 if ( ( clip
.getDuration() + pl_sum_time
) >= float_sec
) {
507 if ( this.cur_clip
.id
!= clip
.id
) {
508 $j( '#clipDesc_' + this.cur_clip
.id
).hide();
509 this.cur_clip
= clip
;
510 $j( '#clipDesc_' + this.cur_clip
.id
).show();
514 pl_sum_time
+= clip
.getDuration();
517 // issue thumbnail update request: (if plugin supports it will render out frame
518 // if not then we do a call to the server to get a new jpeg thumbnail
519 this.cur_clip
.embed
.updateThumbTime( float_sec
- pl_sum_time
);
521 this.cur_clip
.embed
.currentTime
= ( float_sec
- pl_sum_time
) + this.cur_clip
.embed
.start_offset
;
522 this.cur_clip
.embed
.seek_time_sec
= ( float_sec
- pl_sum_time
);
524 // render effects ontop: (handled by doSmilActions)
525 this.doSmilActions();
527 updateBaseStatus:function() {
529 js_log( 'Playlist:updateBaseStatus' );
531 $j( '#ptitle_' + this.id
).html( '' +
532 '<b>' + this.title
+ '</b> ' +
533 this.getClipCount() + ' clips, <i>' +
534 seconds2npt( this.getDuration() ) + '</i>' );
536 // should probably be based on if we have a provider api url
537 if ( typeof wgEnableWriteAPI
!= 'undefined' && !this.sequencer
) {
538 $j( $j
.btnHtml( 'edit', 'editBtn_' + this.id
, 'pencil',
539 { 'style':'position:absolute;right:0;;font-size:x-small;height:10px;margin-bottom:0;padding-bottom:7px;padding-top:0;' } )
540 ).click( function() {
544 } ).appendTo( '#ptitle_' + this.id
);
545 $j( '.editBtn_' + this.id
).btnBind();
547 // render out the dividers on the timeline:
548 this.colorPlayHead();
550 this.setStatus( '0:0:00/' + seconds2npt( this.getDuration() ) );
552 /*setStatus override (could call the jquery directly) */
553 setStatus:function( value
) {
554 $j( '#' + this.id
+ ' .time-disp' ).text( value
);
556 setSliderValue:function( value
) {
557 // slider is on 1000 scale:
558 var val
= parseInt( value
* 1000 );
559 //js_log( 'update slider: #' + this.id + ' .play_head to ' + val );
560 $j( '#' + this.id
+ ' .play_head' ).slider( 'value', val
);
562 getPlayHeadPos: function( prec_done
) {
564 if ( $j( '#mv_seeker_' + this.id
).length
== 0 ) {
565 js_log( 'no playhead so we can\'t get playhead pos' );
568 var track_len
= $j( '#mv_seeker_' + this.id
).css( 'width' ).replace( /px/ , '' );
569 // assume the duration is static and present at .duration during playback
570 var clip_perc
= this.cur_clip
.embed
.duration
/ this.getDuration();
571 var perc_offset
= time_offset
= 0;
572 for ( var i
in this.default_track
.clips
) {
573 var clip
= this.default_track
.clips
[i
];
574 if ( this.cur_clip
.id
== clip
.id
)break;
575 perc_offset
+= ( clip
.embed
.duration
/ _this
.getDuration() );
576 time_offset
+= clip
.embed
.duration
;
578 // run any update time line hooks:
579 if ( this.update_tl_hook
) {
580 var cur_time_ms
= time_offset
+ Math
.round( this.cur_clip
.embed
.duration
* prec_done
);
581 if ( typeof update_tl_hook
== 'function' ) {
582 this.update_tl_hook( cur_time_ms
);
584 // string type passed use eval:
585 eval( this.update_tl_hook
+ '(' + cur_time_ms
+ ');' );
589 // handle offset hack @@todo fix so this is not needed:
590 if ( perc_offset
> .66 )
591 perc_offset
+= ( 8 / track_len
);
592 // js_log('perc:'+ perc_offset +' c:'+ clip_perc + '*' + prec_done + ' v:'+(clip_perc*prec_done));
593 return perc_offset
+ ( clip_perc
* prec_done
);
595 // attempts to load the embed object with the playlist
596 loadEmbedPlaylist: function() {
597 // js_log('load playlist');
599 /** mannages the loading of future clips
600 * called regurally while we are playing clips
602 * load works like so:
603 * if the current clip is full loaded
604 * load clips untill buffredEndTime < playlist_buffer_time load next
606 * this won't work so well with time range loading for smil (need to work on that)
608 loadFutureClips:function() {
609 /*if( this.cur_clip.embed.bufferedPercent == 1){
610 //set the buffer to the currentTime - duration
611 var curBuffredTime = this.cur_clip.getDuration() - this.cur_clip.embed.currentTime;
613 if(curBuffredTime < 0)
616 js_log( "curBuffredTime:: " + curBuffredTime );
617 if( curBuffredTime < this.playlist_buffer_time ){
618 js_log(" we only have " + curBuffredTime + ' buffed but we need: ' + this.playlist_buffer_time);
620 for(var inx = this.cur_clip.order + 1; inx < this.default_track.clips.length; inx++ ){
621 var cClip = this.default_track.getClip( inx );
623 //check if the clip is already loaded (add its duration)
624 if( cClip.embed.bufferedPercent == 1){
625 curBuffredTime += cClip.embed.getDuration();
627 //check if we still have to load a resource:
628 if( curBuffredTime < this.playlist_buffer_time ){
629 //issue the load request
630 if( cClip.embed.networkState==0 ){
633 break; //check back next time
639 // called to play the next clip if done call onClipDone
640 playNext: function() {
641 // advance the playhead to the next clip
642 var next_clip
= this.getNextClip();
645 js_log( 'play next with no next clip... must be done:' );
649 // @@todo where the plugin supports pre_loading future clips and manage that in javascript
651 this.cur_clip
.embed
.stop();
653 this.updateCurrentClip( next_clip
);
654 //if part of a transition should continue playing where it left off
655 this.cur_clip
.embed
.play();
657 onClipDone:function() {
658 js_log( "pl onClipDone" );
659 this.cur_clip
.embed
.stop();
661 updateCurrentClip : function( new_clip
, into_perc
) {
662 js_log( 'f:updateCurrentClip:' + new_clip
.id
);
664 // keep the active play clip in sync (stop the other clip)
665 if ( this.cur_clip
) {
666 // make sure we are not switching to the current
667 if ( this.cur_clip
.id
== new_clip
.id
) {
668 js_log( 'trying to updateCurrentClip to same clip' );
672 if ( !this.cur_clip
.embed
.isStoped() )
673 this.cur_clip
.embed
.stop();
674 this.activeClipList
.remove( this.cur_clip
)
676 //hide the current clip
677 $j( '#clipDesc_' + this.cur_clip
.id
).hide();
679 this.activeClipList
.add( new_clip
);
681 this.cur_clip
= new_clip
;
682 $j( '#clipDesc_' + this.cur_clip
.id
).show();
684 // Update the playhead:
686 // Check if we have into_perc
688 var clip_time
= this.cur_clip
.dur_offset
+ ( into_perc
* this.cur_clip
.getDuration() );
690 var clip_time
= this.cur_clip
.dur_offset
;
693 this.setSliderValue( clip_time
/ this.getDuration() );
696 playPrev: function() {
697 // advance the playhead to the previous clip
698 var prev_clip
= this.getPrevClip();
700 js_log( "tried to play PrevClip with no prev Clip.. setting prev_clip to start clip" );
701 prev_clip
= this.start_clip
;
703 // @@todo we could do something fancy like use playlist for sets of clips where supported.
704 // or in cases where the player nativly supports the playlist format we can just pass it in (ie m3u or xspf)
705 if ( this.cur_clip
.embed
.supports
['playlist_swap_loader'] ) {
706 // where the plugin supports pre_loading future clips and manage that in javascript
707 // pause current clip
708 this.cur_clip
.embed
.pause();
710 this.updateCurrentClip( prev_clip
);
711 this.cur_clip
.embed
.play();
713 js_log( 'do prev hard embed swap' );
714 this.switchPlayingClip( prev_clip
);
717 switchPlayingClip:function( new_clip
) {
718 // swap out the existing embed code for next clip embed code
719 $j( '#mv_ebct_' + this.id
).empty();
720 new_clip
.embed
.width
= this.width
;
721 new_clip
.embed
.height
= this.height
;
722 // js_log('set embed to: '+ new_clip.embed.getEmbedObj());
723 $j( '#mv_ebct_' + this.id
).html( new_clip
.embed
.getEmbedObj() );
724 this.cur_clip
= new_clip
;
726 this.cur_clip
.embed
.pe_postEmbedJS();
732 // hide the playlist play button:
733 $j( this.id
+ ' .play-btn-large' ).hide();
735 // un-pause if paused:
739 // update the control:
740 this.start_clip
= this.cur_clip
;
741 this.start_clip_src
= this.cur_clip
.src
;
743 if ( this.cur_clip
.embed
.supports
['playlist_swap_loader'] ) {
744 // set the cur_clip to active
745 this.activeClipList
.add( this.cur_clip
);
749 // * mv_playlist smil extension, manages transitions animations overlays etc.
750 // js_log('clip obj supports playlist swap_loader (ie playlist controlled playback)');
751 // @@todo pre-load each clip:
752 // play all active clips (playlist_swap_loader can have more than one clip active)
753 $j
.each( this.activeClipList
.getClipList(), function( inx
, clip
) {
756 } else if ( this.cur_clip
.embed
.supports
['playlist_driver'] ) {
757 // js_log('playlist_driver');
758 // embedObject is feed the playlist info directly and manages next/prev
759 this.cur_clip
.embed
.playMovieAt( this.cur_clip
.order
);
761 // not much playlist support just play the first clip:
762 // js_log('basic play');
764 this.cur_clip
.embed
.play();
766 // start up the playlist monitor
770 * the load function loads all the clips in order
773 // do nothing right now)
776 toggleMute:function() {
777 this.cur_clip
.embed
.toggleMute();
780 // js_log('f:pause: playlist');
782 this.pauseTime
= this.currentTime
;
784 // js_log('pause time: '+ this.pauseTime + ' call embed pause:');
786 // pause all the active clips:
787 $j
.each( this.activeClipList
.getClipList(), function( inx
, clip
) {
791 // @@todo mute across all child clips:
792 toggleMute:function() {
793 var this_id
= ( this.pc
!= null ) ? this.pc
.pp
.id
:this.id
;
796 $j( '#volume_control_' + this_id
+ ' span' ).removeClass( 'ui-icon-volume-off' ).addClass( 'ui-icon-volume-on' );
797 $j( '#volume_bar_' + this_id
).slider( 'value', 100 );
798 this.updateVolumen( 1 );
801 $j( '#volume_control_' + this_id
+ ' span' ).removeClass( 'ui-icon-volume-on' ).addClass( 'ui-icon-volume-off' );
802 $j( '#volume_bar_' + this_id
).slider( 'value', 0 );
803 this.updateVolumen( 0 );
805 js_log( 'f:toggleMute::' + this.muted
);
807 updateVolumen:function( perc
) {
808 js_log( 'update volume not supported with current playback type' );
810 fullscreen:function() {
811 this.cur_clip
.embed
.fullscreen();
813 // playlist stops playback for the current clip (and resets state for start clips)
816 /*js_log("pl stop:"+ this.start_clip.id + ' c:'+this.cur_clip.id);
818 if(this.start_clip.id!=this.cur_clip.id){
819 //restore clipDesc visibility & hide desc for start clip:
820 $j('#clipDesc_'+this.start_clip.id).html('');
821 this.start_clip.getDetail();
822 $j('#clipDesc_'+this.start_clip.id).css({display:'none'});
823 this.start_clip.setBaseEmbedDim(this.start_clip.embed);
824 //equivalent of base stop
825 $j('#'+this.start_clip.embed.id).html(this.start_clip.embed.getThumbnailHTML());
826 this.start_clip.embed.thumbnail_disp=true;
828 //empty the play-back container
829 $j('#mv_ebct_'+this.id).empty();*/
831 // stop all the clips: monitor:
832 window
.clearInterval( this.smil_monitorTimerId
);
833 /*for (var i=0;i<this.clips.length;i++){
834 var clip = this.clips[i];
837 $j('#clipDesc_'+clip.id).hide();
840 // stop, hide and remove all active clips:
841 $j
.each( this.activeClipList
.getClipList(), function( inx
, clip
) {
844 $j( '#clipDesc_' + clip
.id
).hide();
845 _this
.activeClipList
.remove( clip
);
848 // set the current clip to the first clip:
849 if ( this.start_clip
) {
850 this.cur_clip
= this.start_clip
;
851 // display the first clip thumb:
852 this.cur_clip
.embed
.stop();
854 $j( '#' + this.id
+ ' .clip_container' ).hide();
855 // show the first/current clip:
856 $j( '#clipDesc_' + this.cur_clip
.id
).show();
858 // reset the currentTime:
859 this.currentTime
= 0;
861 this.setSliderValue( 0 );
862 // FIXME still some issues with "stoping" and reseting the playlist
864 doSeek:function( v
) {
865 js_log( 'pl:doSeek:' + v
+ ' sts:' + this.seek_time_sec
);
868 var time
= v
* this.getDuration()
869 _this
.currentTime
= time
;
870 var relative_perc
= _this
.updateClipByTime();
872 // Update the clip relative seek_time_sec
873 _this
.cur_clip
.embed
.doSeek( relative_perc
);
878 setCurrentTime: function( time
, callback
) {
879 js_log( 'pl:setCurrentTime:' + time
);
881 _this
.currentTime
= time
;
883 var pl_perc
= time
/ this.getDuration();
884 var relative_perc
= _this
.updateClipByTime();
885 var clip_time
= relative_perc
* _this
.cur_clip
.embed
.getDuration();
886 _this
.cur_clip
.embed
.setCurrentTime( clip_time
, function() {
887 //update the smil actions now that the seek is done
888 _this
.doSmilActions();
897 * @returns the relative offsets of the current clip (given the playlist time)
899 updateClipByTime: function(){
902 //set the current percent done:
903 var pt
= this.currentTime
/ _this
.getDuration();
904 // jump to the clip in the current percent.
906 var next_perc_offset
= 0;
907 for ( var i
in _this
.default_track
.clips
) {
908 var clip
= _this
.default_track
.clips
[i
];
909 next_perc_offset
+= ( clip
.getDuration() / _this
.getDuration() ) ;
910 // js_log('on ' + clip.getDuration() +' next_perc_offset:'+ next_perc_offset);
911 if ( next_perc_offset
> pt
) {
912 // js_log('seek:'+ pt +' - '+perc_offset + ') / (' + next_perc_offset +' - '+ perc_offset);
913 var relative_perc
= ( pt
- perc_offset
) / ( next_perc_offset
- perc_offset
);
914 // update the current clip:
915 _this
.updateCurrentClip( clip
, relative_perc
);
916 return relative_perc
;
918 perc_offset
= next_perc_offset
;
922 // gets playlist controls large control height for sporting
923 // next prev button and more status display
924 getControlsHTML:function() {
925 // get controls from current clip (add some playlist specific controls:
926 return this.ctrlBuilder
.getControls( this );
928 // ads colors/dividers between tracks
929 colorPlayHead: function() {
932 if ( !_this
.mv_seeker_width
)
933 _this
.mv_seeker_width
= $j( '#' + _this
.id
+ ' .play_head' ).width();
935 if ( !_this
.track_len
)
936 _this
.track_len
= $j( '#' + _this
.id
+ ' .play_head' ).width();
939 var pl_duration
= _this
.getDuration();
944 // js_log("do play head total dur: "+pl_duration );
945 $j
.each( this.default_track
.clips
, function( i
, clip
) {
946 // (use getSoloDuration to not include transitions and such)
947 var perc
= ( clip
.getSoloDuration() / pl_duration
);
948 var pwidth
= Math
.round( perc
* _this
.track_len
);
949 // js_log('pstatus:c:'+ clip.getDuration() + ' of '+ pl_duration+' %:' + perc + ' width: '+ pwidth + ' of total: ' + _this.track_len);
950 // var pwidth = Math.round( perc * _this.track_len - (_this.mv_seeker_width*perc) );
952 // add the buffer child indicator:
953 var barHtml
= '<div id="cl_status_' + clip
.embed
.id
+ '" class="cl_status" style="' +
954 'left:' + cur_pixle
+ 'px;' +
955 'width:' + pwidth
+ 'px;';
956 // set left or right border based on track pos
957 barHtml
+= ( i
== _this
.default_track
.getClipCount() - 1 ) ?
958 'border-left:solid thin black;':
959 'border-right:solid thin black;';
960 barHtml
+= 'filter:alpha(opacity=40);' +
961 '-moz-opacity:.40;">';
963 barHtml
+= _this
.ctrlBuilder
.getMvBufferHtml();
967 // background:#DDD +clip.getColor();
969 $j( '#' + _this
.id
+ ' .play_head' ).append( barHtml
);
971 // js_log('offset:' + cur_pixle +' width:'+pwidth+' add clip'+ clip.id + ' is '+clip.embed.getDuration() +' = ' + perc +' of ' + _this.track_len);
975 // @@todo currently not really in use
976 setUpHover:function() {
977 js_log( 'Setup Hover' );
978 // set up hover for prev,next
980 var tw
= th
* this.pl_layout
.clip_aspect
;
982 $j( '#mv_prev_link_' + _this
.id
+ ',#mv_next_link_' + _this
.id
).hover( function() {
983 var clip
= ( this.id
== 'mv_prev_link_' + _this
.id
) ? _this
.getPrevClip() : _this
.getNextClip();
985 return js_log( 'missing clip for Hover' );
986 // get the position of #mv_perv|next_link:
987 var loc
= getAbsolutePos( this.id
);
988 // js_log('Hover: x:'+loc.x + ' y:' + loc.y + ' :'+clip.img);
989 $j( "body" ).append( '<div id="mv_Athub" style="position:absolute;' +
990 'top:' + loc
.y
+ 'px;left:' + loc
.x
+ 'px;width:' + tw
+ 'px;height:' + th
+ 'px;">' +
991 '<img style="border:solid 2px ' + clip
.getColor() + ';position:absolute;top:0px;left:0px;" width="' + tw
+ '" height="' + th
+ '" src="' + clip
.img
+ '"/>' +
994 $j( '#mv_Athub' ).remove();
997 // @@todo we need to move a lot of this track logic like "cur_clip" to the track Obj
998 // and have the playlist just drive the tracks.
999 getNextClip:function( track
) {
1001 track
= this.default_track
;
1002 var tc
= parseInt( this.cur_clip
.order
) + 1;
1004 if ( tc
> track
.getClipCount() - 1 )
1005 return false; // out of range
1007 return track
.getClip( tc
);
1009 getPrevClip:function( track
) {
1011 track
= this.default_track
;
1012 var tc
= parseInt( this.cur_clip
.order
) - 1;
1015 return track
.getClip( tc
);
1018 * generic add Clip to ~default~ track
1020 addCliptoTrack: function( clipObj
, pos
) {
1021 if ( typeof clipObj
['track_id'] == 'undefined' ) {
1022 var track
= this.default_track
;
1024 var track
= this.tracks
[ clipObj
.track_id
]
1026 js_log( 'add clip:' + clipObj
.id
+ ' to track: at:' + pos
);
1027 // set the first clip to current (maybe deprecated )
1028 if ( clipObj
.order
== 0 ) {
1029 if ( !this.cur_clip
)this.cur_clip
= clipObj
;
1031 track
.addClip( clipObj
, pos
);
1033 swapClipDesc: function( req_clipID
, callback
) {
1034 // hide all but the requested
1036 js_log( 'r:' + req_clipID
+ ' cur:' + _this
.id
);
1037 if ( req_clipID
== _this
.cur_clip
.id
) {
1038 js_log( 'no swap to same clip' );
1042 $j
.each( this.default_track
.clips
, function( i
, clip
) {
1043 if ( clip
.id
!= req_clipID
) {
1044 // fade out if display!=none already
1045 if ( $j( '#clipDesc_' + clip
.id
).css( 'display' ) != 'none' ) {
1046 $j( '#clipDesc_' + clip
.id
).fadeOut( "slow" );
1052 // fade in requested clip *and set req_clip to current
1053 $j( '#clipDesc_' + req_clipID
).fadeIn( "slow", function() {
1054 _this
.cur_clip
= req_clip
;
1060 // this is pretty outdated:
1061 getPLControls: function() {
1062 js_log( 'getPL cont' );
1063 return '<a id="mv_prev_link_' + this.id
+ '" title="Previus Clip" onclick="document.getElementById(\'' + this.id
+ '\').playPrev();return false;" href="#">' +
1064 getTransparentPng( { id
:'mv_prev_btn_' + this.id
, style
:'float:left', width
:'27', height
:'27', border
:"0",
1065 src
:mv_skin_img_path
+ 'vid_prev_sm.png' } ) +
1067 '<a id="mv_next_link_' + this.id
+ '" title="Next Clip" onclick="document.getElementById(\'' + this.id
+ '\').playNext();return false;" href="#">' +
1068 getTransparentPng( { id
:'mv_next_btn_' + this.id
, style
:'float:left', width
:'27', height
:'27', border
:"0",
1069 src
:mv_skin_img_path
+ 'vid_next_sm.png' } ) +
1072 run_transition: function( clip_inx
, trans_type
) {
1073 if ( typeof this.default_track
.clips
[ clip_inx
][ trans_type
] == 'undefined' )
1074 clearInterval( this.default_track
.clips
[ clip_inx
].timerId
);
1076 this.default_track
.clips
[ clip_inx
][ trans_type
].run_transition();
1078 playerPixelWidth : function()
1080 var player
= $j( '#dc_' + this.id
).get( 0 );
1081 if ( typeof player
!= 'undefined' && player
['offsetWidth'] )
1082 return player
.offsetWidth
;
1084 return parseInt( this.width
);
1086 playerPixelHeight : function()
1088 var player
= $j( '#dc_' + this.id
).get( 0 );
1089 if ( typeof player
!= 'undefined' && player
['offsetHeight'] )
1090 return player
.offsetHeight
;
1092 return parseInt( this.height
);
1098 * @videoTrack ... stores clips and layer info
1100 * @clip... each clip segment is a clip object.
1102 var mvClip = function( o
) {
1107 // set up the mvPlaylist object
1108 mvClip
.prototype = {
1110 pp
:null, // parent playlist
1111 order
:null, // the order/array key for the current clip
1121 init:function( o
) {
1122 // init object including pointer to parent
1123 for ( var i
in o
) {
1126 js_log( 'id is: ' + this.id
);
1128 // setup the embed object:
1129 setUpEmbedObj:function() {
1130 js_log( 'mvClip:setUpEmbedObj()' );
1133 // js_log('setup embed for clip '+ this.id + ':id is a function?');
1134 // set up the pl_mv_embed object:
1135 var init_pl_embed
= { id
:'e_' + this.id
,
1136 pc
:this, // parent clip
1140 this.setBaseEmbedDim( init_pl_embed
);
1143 // if in sequence mode hide controls / embed links
1144 // init_pl_embed.play_button=false;
1145 // init_pl_embed.controls=true;
1146 // if(this.pp.sequencer=='true'){
1147 init_pl_embed
.embed_link
= null;
1148 init_pl_embed
.linkback
= null;
1150 if( this.durationHint
)
1151 init_pl_embed
.durationHint
= this.durationHint
;
1153 if ( this.poster
)init_pl_embed
['thumbnail'] = this.poster
;
1155 if ( this.type
)init_pl_embed
['type'] = this.type
;
1157 this.embed
= new PlMvEmbed( init_pl_embed
);
1159 // js_log('media Duration:' + this.embed.getDuration() );
1160 // js_log('media element:'+ this.embed.media_element.length);
1161 // js_log('type of embed:' + typeof(this.embed) + ' seq:' + this.pp.sequencer+' pb:'+ this.embed.play_button);
1163 doAdjust:function( side
, delta
) {
1164 js_log( "f:doAdjust: " + side
+ ' , ' + delta
);
1166 if ( side
== 'start' ) {
1167 var start_offset
= parseInt( this.embed
.start_offset
) + parseInt( delta
* -1 );
1168 this.embed
.updateVideoTime( seconds2npt( start_offset
), seconds2npt ( this.embed
.start_offset
+ this.embed
.getDuration() ) );
1169 } else if ( side
== 'end' ) {
1170 var end_offset
= parseInt( this.embed
.start_offset
) + parseInt( this.embed
.getDuration() ) + parseInt( delta
);
1171 this.embed
.updateVideoTime( seconds2npt( this.embed
.start_offset
), seconds2npt( end_offset
) );
1173 // update everything:
1175 /*var base_src = this.src.substr(0,this.src.indexOf('?'));
1176 js_log("delta:"+ delta);
1178 //since we adjust start invert the delta:
1179 var start_offset =parseInt(this.embed.start_offset/1000)+parseInt(delta*-1);
1180 this.src = base_src +'?t='+ seconds2npt(start_offset) +'/'+ this.embed.end_ntp;
1181 }else if(side=='end'){
1182 //put back into seconds for adjustment:
1183 var end_offset = parseInt(this.embed.start_offset/1000) + parseInt(this.embed.duration/1000) + parseInt(delta);
1184 this.src = base_src +'?t='+ this.embed.start_ntp +'/'+ seconds2npt(end_offset);
1186 this.embed.updateVideoTime( this.src );
1188 this.duration = this.embed.getDuration();
1189 this.pp.pl_duration=null;
1190 //update playlist stuff:
1191 this.pp.updateTitle();*/
1194 getDuration:function() {
1195 if ( !this.embed
)this.setUpEmbedObj();
1196 return this.embed
.getDuration();
1198 setBaseEmbedDim:function( o
) {
1200 // o.height=Math.round(pl_layout.clip_desc*this.pp.height)-2;//give it some padding:
1201 // o.width=Math.round(o.height*pl_layout.clip_aspect)-2;
1202 o
.height
= this.pp
.height
;
1203 o
.width
= this.pp
.width
;
1205 // output the detail view:
1207 /*getDetail:function(){
1208 //js_log('get detail:' + this.pp.title);
1209 var th=Math.round( this.pl_layout.clip_desc * this.pp.height );
1210 var tw=Math.round( th * this.pl_layout.clip_aspect );
1212 var twDesc = (this.pp.width-tw)-2;
1214 if(this.title==null)
1215 this.title='clip ' + this.order + ' ' +this.pp.title;
1217 this.desc=this.pp.desc;
1218 //update the embed html:
1219 this.embed.getHTML();
1221 $j(this.embed).css({ 'position':"absolute",'top':"0px", 'left':"0px"});
1223 //js_log('append child to:#clipDesc_'+this.id);
1224 if($j('#clipDesc_'+this.id).get(0)){
1225 $j('#clipDesc_'+this.id).get(0).appendChild(this.embed);
1227 $j('#clipDesc_'+this.id).append(''+
1228 '<div id="pl_desc_txt_'+this.id+'" class="pl_desc" style="position:absolute;left:'+(tw+2)+'px;width:'+twDesc+'px;height:'+th+'px;overflow:auto;">'+
1229 '<b>'+this.title+'</b><br>'+
1230 this.desc + '<br>' +
1231 '<b>clip length:</b> '+ seconds2npt( this.embed.getDuration() );
1235 getTitle:function() {
1236 if ( typeof this.title
== 'string' )
1239 return 'untitled clip ' + this.order
;
1241 getClipImg:function( start_offset
, size
) {
1242 js_log( 'f:getClipImg ' + start_offset
+ ' s:' + size
);
1244 return mv_default_thumb_url
;
1246 if ( !size
&& !start_offset
) {
1249 // if a metavid image (has request parameters) use size and time args
1250 if ( this.img
.indexOf( '?' ) != -1 ) {
1251 js_log( 'get with offset: ' + start_offset
);
1252 var time
= seconds2npt( start_offset
+ ( this.embed
.start_offset
/ 1000 ) );
1253 js_log( "time is: " + time
);
1254 this.img
= this.img
.replace( /t\=[^&]*/gi, "t=" + time
);
1255 if ( this.img
.indexOf( '&size=' ) != -1 ) {
1256 this.img
= this.img
.replace( /size=[^&]*/gi, "size=" + size
);
1258 this.img
+= '&size=' + size
;
1265 getColor: function() {
1266 // js_log('get color:'+ num +' : '+ num.toString().substr(num.length-1, 1) + ' : '+colors[ num.toString().substr(num.length-1, 1)] );
1267 var num
= this.id
.substr( this.id
.length
- 1, 1 );
1268 if ( !isNaN( num
) ) {
1269 num
= num
.charCodeAt( 0 );
1271 if ( num
>= 10 )num
= num
% 10;
1272 return mv_clip_colors
[num
];
1275 /* mv_embed extensions for playlists */
1276 var PlMvEmbed = function( vid_init
) {
1277 // js_log('PlMvEmbed: '+ vid_init.id);
1278 // create the div container
1279 var ve
= document
.createElement( 'div' );
1280 // extend ve with all this
1281 this.init( vid_init
);
1282 for ( method
in this ) {
1283 if ( method
!= 'readyState' ) {
1284 ve
[method
] = this[method
];
1287 js_log( 've src len:' + ve
.media_element
.sources
.length
);
1290 // all the overwritten and new methods for playlist extension of baseEmbed
1291 PlMvEmbed
.prototype = {
1292 init:function( vid_init
) {
1293 // send embed_video a created video element:
1294 ve
= document
.createElement( 'div' );
1295 for ( var i
in vid_init
) {
1296 // set the parent clip pointer:
1298 this['pc'] = vid_init
['pc'];
1300 ve
.setAttribute( i
, vid_init
[i
] );
1303 var videoInterface
= new embedVideo( ve
);
1304 // inherit the videoInterface
1305 for ( method
in videoInterface
) {
1306 if ( method
!= 'style' ) {
1307 if ( this[ method
] ) {
1308 // parent embed method preservation:
1309 this['pe_' + method
] = videoInterface
[method
];
1311 this[method
] = videoInterface
[method
];
1314 // string -> boolean:
1315 if ( this[method
] == "false" )this[method
] = false;
1316 if ( this[method
] == "true" )this[method
] = true;
1319 onClipDone:function() {
1320 js_log( 'pl onClipDone (should go to next)' );
1321 // go to next in playlist:
1322 this.pc
.pp
.playNext();
1325 js_log( 'pl:do stop' );
1326 // set up convenience pointer to parent playlist
1327 var _this
= this.pc
.pp
;
1329 var th
= Math
.round( _this
.pl_layout
.clip_desc
* _this
.height
);
1330 var tw
= Math
.round( th
* _this
.pl_layout
.clip_aspect
);
1332 // run the parent stop:
1334 var pl_height
= ( _this
.sequencer
== 'true' ) ? _this
.height
+ 27:_this
.height
;
1339 // js_log('pl eb play');
1340 var _this
= this.pc
.pp
;
1341 // check if we are already playing
1342 if ( !this.thumbnail_disp
) {
1346 mv_lock_vid_updates
= true;
1349 // do post interface operations
1350 postEmbedJS:function() {
1351 // add playlist clips (if plugin supports it)
1352 if ( this.pc
.pp
.cur_clip
.embed
.playlistSupport() )
1353 this.pc
.pp
.loadEmbedPlaylist();
1354 // color playlist points (if play_head present)
1355 if ( this.pc
.pp
.disp_play_head
)
1356 this.pc
.pp
.colorPlayHead();
1357 // setup hover images (for playhead and next/prev buttons)
1358 this.pc
.pp
.setUpHover();
1359 // call the parent postEmbedJS
1360 this.pe_postEmbedJS();
1361 mv_lock_vid_updates
= false;
1363 getPlayButton:function() {
1364 return this.pe_getPlayButton( this.pc
.pp
.id
);
1366 setStatus:function( value
) {
1367 // status updates handled by playlist obj
1369 setSliderValue:function( value
) {
1370 //js_log( 'PlMvEmbed:setSliderValue:' + value );
1371 // setSlider value handled by playlist obj
1379 doParse:function() {
1380 // for each line not # add as clip
1383 // js_log('data:'+ this.data.toString());
1384 $j
.each( this.data
.split( "\n" ), function( i
, n
) {
1385 // js_log('on line '+i+' val:'+n+' len:'+n.length);
1386 if ( n
.charAt( 0 ) != '#' ) {
1387 if ( n
.length
> 3 ) {
1388 // @@todo make sure its a valid url
1389 // js_log('add url: '+i + ' '+ n);
1390 var cur_clip
= new mvClip( { type
:'srcClip', id
:'p_' + this_pl
.id
+ '_c_' + inx
, pp
:this_pl
, src
:n
, order
:inx
} );
1391 // setup the embed object
1392 cur_clip
.setUpEmbedObj();
1393 js_log( 'm3uPlaylist len:' + thisClip
.embed
.media_element
.sources
.length
);
1394 this_pl
.addCliptoTrack( cur_clip
);
1403 var itunesPlaylist
= {
1404 doParse:function() {
1405 var properties
= { title
:'title', linkback
:'link',
1406 author
:'itunes:author', desc
:'description',
1409 for ( i
in properties
) {
1410 tmpElm
= this.data
.getElementsByTagName( properties
[i
] )[0];
1412 this[i
] = tmpElm
.childNodes
[0].nodeValue
;
1413 // js_log('set '+i+' to '+this[i]);
1416 // image src is nested in itunes rss:
1417 tmpElm
= this.data
.getElementsByTagName( 'image' )[0];
1419 imgElm
= tmpElm
.getElementsByTagName( 'url' )[0];
1421 this.img
= imgElm
.childNodes
[0].nodeValue
;
1425 var clips
= this.data
.getElementsByTagName( "item" );
1426 properties
.src
= 'guid';
1427 for ( var i
= 0; i
< clips
.length
; i
++ ) {
1428 var cur_clip
= new mvClip( { type
:'srcClip', id
:'p_' + this.id
+ '_c_' + i
, pp
:this, order
:i
} );
1429 for ( var j
in properties
) {
1430 tmpElm
= clips
[i
].getElementsByTagName( properties
[j
] )[0];
1431 if ( tmpElm
!= null ) {
1432 cur_clip
[j
] = tmpElm
.childNodes
[0].nodeValue
;
1433 // js_log('set clip property: ' + j+' to '+cur_clip[j]);
1437 tmpElm
= clips
[i
].getElementsByTagName( 'image' )[0];
1439 imgElm
= tmpElm
.getElementsByTagName( 'url' )[0];
1441 cur_clip
.img
= imgElm
.childNodes
[0].nodeValue
;
1444 // set up the embed object now that all the values have been set
1445 cur_clip
.setUpEmbedObj();
1447 // add the current clip to the clip list
1448 this.addCliptoTrack( cur_clip
);
1456 * http://www.xspf.org/xspf-v1.html
1458 var xspfPlaylist
= {
1459 doParse:function() {
1460 // js_log('do xsfp parse: '+ this.data.innerHTML);
1461 var properties
= { title
:'title', linkback
:'info',
1462 author
:'creator', desc
:'annotation',
1463 poster
:'image', date
:'date' };
1465 // get the first instance of any of the meta tags (ok that may be the meta on the first clip)
1466 // js_log('do loop on properties:' + properties);
1467 for ( i
in properties
) {
1468 js_log( 'on property: ' + i
);
1469 tmpElm
= this.data
.getElementsByTagName( properties
[i
] )[0];
1471 if ( tmpElm
.childNodes
[0] ) {
1472 this[i
] = tmpElm
.childNodes
[0].nodeValue
;
1473 js_log( 'set pl property: ' + i
+ ' to ' + this[i
] );
1477 var clips
= this.data
.getElementsByTagName( "track" );
1478 js_log( 'found clips:' + clips
.length
);
1479 // add any clip specific properties
1480 properties
.src
= 'location';
1481 for ( var i
= 0; i
< clips
.length
; i
++ ) {
1482 var cur_clip
= new mvClip( { id
:'p_' + this.id
+ '_c_' + i
, pp
:this, order
:i
} );
1483 // js_log('cur clip:'+ cur_clip.id);
1484 for ( var j
in properties
) {
1485 tmpElm
= clips
[i
].getElementsByTagName( properties
[j
] )[0];
1486 if ( tmpElm
!= null ) {
1487 if ( tmpElm
.childNodes
.length
!= 0 ) {
1488 cur_clip
[j
] = tmpElm
.childNodes
[0].nodeValue
;
1489 js_log( 'set clip property: ' + j
+ ' to ' + cur_clip
[j
] );
1493 // add mvClip ref from info link:
1494 if ( cur_clip
.linkback
) {
1497 mvclippos
= cur_clip
.linkback
.indexOf( mvInx
);
1498 if ( mvclippos
!== false ) {
1499 cur_clip
.mvclip
= cur_clip
.linkback
.substr( mvclippos
+ mvInx
.length
);
1502 // set up the embed object now that all the values have been set
1503 cur_clip
.setUpEmbedObj();
1504 // add the current clip to the clip list
1505 this.addCliptoTrack( cur_clip
);
1507 // js_log('done with parse');
1511 /*****************************
1512 * SMIL CODE (could be put into another js file / lazy_loaded for improved basic playlist performance / modularity)
1513 *****************************/
1514 /*playlist driver extensions to the playlist object*/
1515 mvPlayList
.prototype.monitor = function() {
1516 // js_log('pl:monitor');
1517 // if paused stop updates
1518 if ( this.paused
) {
1519 // clearInterval( this.smil_monitorTimerId );
1522 // js_log("pl check: " + this.currentTime + ' > '+this.getDuration());
1523 // check if we should be done:
1524 if ( this.currentTime
> this.getDuration() )
1527 // update the playlist current time:
1528 // check for a trsnOut from the previus clip to subtract
1529 this.currentTime
= this.cur_clip
.dur_offset
+ this.cur_clip
.embed
.relativeCurrentTime();
1532 if ( !this.userSlide
) {
1533 this.setStatus( seconds2npt( this.currentTime
) + '/' + seconds2npt( this.getDuration() ) );
1534 this.setSliderValue( this.currentTime
/ this.getDuration() );
1536 // pre-load any future clips:
1537 this.loadFutureClips();
1540 // status updates are handled by children clips ... playlist mostly manages smil actions
1541 this.doSmilActions();
1543 if ( ! this.smil_monitorTimerId
) {
1544 if ( document
.getElementById( this.id
) ) {
1545 this.smil_monitorTimerId
= setInterval( '$j(\'#' + this.id
+ '\').get(0).monitor()', 250 );
1550 // handles the rendering of overlays load of future clips (if necessary)
1551 // @@todo could be lazy loaded if necessary
1552 mvPlayList
.prototype.doSmilActions = function( callback
) {
1554 // js_log('f:doSmilActions: ' + this.cur_clip.id + ' tid: ' + this.cur_clip.transOut );
1555 var offSetTime
= 0; // offset time should let us start a transition later on if we have to.
1556 var _clip
= this.cur_clip
; // setup a local pointer to cur_clip
1559 // do any smil time actions that may change the current clip
1560 if ( this.userSlide
) {
1561 // current clip set is updated mannually outside the scope of smil Actions
1563 // Assume playing and go to next:
1564 if ( _clip
.dur
<= _clip
.embed
.currentTime
1565 && _clip
.order
!= _clip
.pp
.getClipCount() - 1 ) {
1567 js_log( 'order:' + _clip
.order
+ ' != count:' + ( _clip
.pp
.getClipCount() - 1 ) +
1568 ' smil dur: ' + _clip
.dur
+ ' <= curTime: ' + _clip
.embed
.currentTime
+ ' go to next clip..' );
1570 _clip
.pp
.playNext();
1573 // @@todo could maybe generalize transIn with trasOut into one "flow" with a few scattered if statements
1574 // update/setup all transitions (will render current transition state)
1576 // process actions per transition types:
1577 _this
.procTranType( 'transIn', callback
);
1578 _this
.procTranType( 'transOut', callback
);
1583 * @param {string} tid the transition type [transIn|transOut]
1584 * @param {function} callback the callback function passed onto doUPdate
1586 mvPlayList
.prototype.procTranType = function( tid
, callback
){
1587 // Setup local clip pointer:
1588 var _clip
= this.cur_clip
;
1590 eval( 'var tObj = _clip.' + tid
);
1593 // js_log('f:doSmilActions: ' + _clip.id + ' tid:'+tObj.id + ' tclip_id:'+ tObj.pClip.id);
1594 // Check if we are in range:
1595 if ( tid
== 'transIn' )
1596 in_range
= ( _clip
.embed
.currentTime
<= tObj
.dur
) ? true : false;
1598 if ( tid
== 'transOut' )
1599 in_range
= ( _clip
.embed
.currentTime
>= ( _clip
.dur
- tObj
.dur
) ) ? true : false;
1602 if ( this.userSlide
|| this.paused
) {
1603 if ( tid
== 'transIn' ){
1604 mvTransLib
.doUpdate( tObj
,
1605 ( _clip
.embed
.currentTime
/ tObj
.dur
),
1608 if ( tid
== 'transOut' ){
1609 mvTransLib
.doUpdate( tObj
,
1610 ( ( _clip
.embed
.currentTime
- ( _clip
.dur
- tObj
.dur
) ) / tObj
.dur
),
1613 } else if ( tObj
.animation_state
== 0 ) {
1614 js_log( 'init/run_transition ' );
1615 tObj
.run_transition();
1618 // Close up transition if done & still onDispaly
1619 if ( tObj
.overlay_selector_id
) {
1620 js_log( 'close up transition :' + tObj
.overlay_selector_id
);
1621 mvTransLib
.doCloseTransition( tObj
);
1624 // Run the callback::
1630 * mvTransLib library of transitions
1631 * a single object called to initiate transition effects can easily be extended in separate js file
1632 * /mvTransLib is a all static object no instances of mvTransLib/
1633 * (that way a limited feature set "sequence" need not include a _lot_ of js unless necessary )
1635 * Smil Transition Effects see:
1636 * http://www.w3.org/TR/SMIL3/smil-transitions.html#TransitionEffects-TransitionAttribute
1640 * function doTransition lookups up the transition in the mvTransLib obj
1641 * and init the transition if its available
1642 * @param tObj transition attribute object
1643 * @param offSetTime default value 0 if we need to start rendering from a given time
1645 doInitTransition:function( tObj
) {
1646 js_log( 'mvTransLib:f:doInitTransition' );
1648 js_log( 'transition is missing type attribute' );
1652 if ( !tObj
.subtype
) {
1653 js_log( 'transition is missing subtype attribute' );
1657 if ( !this['type'][tObj
.type
] ) {
1658 js_log( 'mvTransLib does not support type: ' + tObj
.type
);
1662 if ( !this['type'][tObj
.type
][tObj
.subtype
] ) {
1663 js_log( 'mvTransLib does not support subType: ' + tObj
.subtype
);
1667 // setup overlay_selector_id
1668 if ( tObj
.subtype
== 'crossfade' ) {
1669 if ( tObj
.transAttrType
== 'transIn' )
1670 var other_pClip
= tObj
.pClip
.pp
.getPrevClip();
1671 if ( tObj
.transAttrType
== 'transOut' )
1672 var other_pClip
= tObj
.pClip
.pp
.getNextClip();
1674 if ( typeof( other_pClip
) == 'undefined' || other_pClip
=== false || other_pClip
.id
== tObj
.pClip
.pp
.cur_clip
.id
)
1675 js_log( 'Error: crossfade without target media asset' );
1676 // if not sliding start playback:
1677 if ( !tObj
.pClip
.pp
.userSlide
&& !tObj
.pClip
.pp
.paused
) {
1678 other_pClip
.embed
.play();
1680 //issue a load request:
1681 other_pClip
.embed
.load();
1683 // manualy ad the extra layer to the activeClipList
1684 tObj
.pClip
.pp
.activeClipList
.add( other_pClip
);
1685 tObj
.overlay_selector_id
= 'clipDesc_' + other_pClip
.id
;
1687 tObj
.overlay_selector_id
= this.getOverlaySelector( tObj
);
1690 // all good call function with tObj param
1691 js_log( 'should call: ' + tObj
.type
+ ' ' + tObj
.subtype
);
1692 this['type'][tObj
.type
][tObj
.subtype
].init( tObj
);
1694 doCloseTransition:function( tObj
) {
1695 if ( tObj
.subtype
== 'crossfade' ) {
1696 // close up crossfade
1697 js_log( "close up crossfade" );
1699 $j( '#' + tObj
.overlay_selector_id
).remove();
1702 tObj
.overlay_selector_id
= null;
1704 getOverlaySelector:function( tObj
) {
1705 var overlay_selector_id
= tObj
.transAttrType
+ tObj
.pClip
.id
;
1706 js_log( 'f:getOverlaySelector: ' + overlay_selector_id
+ ' append to: ' + '#videoPlayer_' + tObj
.pClip
.embed
.id
);
1707 // make sure overlay_selector_id not already here:
1708 if ( $j( '#' + overlay_selector_id
).length
== 0 ) {
1709 $j( '#videoPlayer_' + tObj
.pClip
.embed
.id
).prepend( '' +
1710 '<div id="' + overlay_selector_id
+ '" ' +
1711 'style="position:absolute;top:0px;left:0px;' +
1712 'height:' + parseInt( tObj
.pClip
.pp
.height
) + 'px;' +
1713 'width:' + parseInt( tObj
.pClip
.pp
.width
) + 'px;' +
1717 return overlay_selector_id
;
1719 doUpdate:function( tObj
, percent
, callback
) {
1720 // init the transition if necessary:
1721 if ( !tObj
.overlay_selector_id
)
1722 this.doInitTransition( tObj
);
1724 // @@todo we should ensure viability outside of doUpate loop
1725 if ( !$j( '#' + tObj
.overlay_selector_id
).is( ':visible' ) )
1726 $j( '#' + tObj
.overlay_selector_id
).show();
1729 /* js_log('doing update for: '+ tObj.pClip.id +
1730 ' type:' + tObj.transAttrType +
1731 ' t_type:'+ tObj.type +
1732 ' subypte:'+ tObj.subtype +
1733 ' percent:' + percent);*/
1735 this[ 'type' ][ tObj
.type
][ tObj
.subtype
].u( tObj
, percent
, callback
);
1737 getTransitionIcon:function( type
, subtype
) {
1738 return mv_embed_path
+ '/skins/common/transition_images/' + type
+ '_' + subtype
+ '.png';
1741 * mvTransLib: functional library mapping:
1747 'attr' : ['fadeColor'],
1748 'init' : function( tObj
) {
1749 // js_log('f:fadeFromColor: '+tObj.overlay_selector_id +' to color: '+ tObj.fadeColor);
1750 if ( !tObj
.fadeColor
)
1751 js_log( 'missing fadeColor' );
1752 if ( $j( '#' + tObj
.overlay_selector_id
).length
== 0 ) {
1753 js_log( "ERROR can't find: " + tObj
.overlay_selector_id
);
1755 // set the initial state
1756 $j( '#' + tObj
.overlay_selector_id
).css( {
1757 'background-color':tObj
.fadeColor
,
1761 'u' : function( tObj
, percent
) {
1762 // js_log(':fadeFromColor:update: '+ percent);
1763 // fade from color (invert the percent)
1764 var percent
= 1 - percent
;
1765 $j( '#' + tObj
.overlay_selector_id
).css( {
1773 "init" : function( tObj
) {
1774 js_log( 'f:crossfade: ' + tObj
.overlay_selector_id
);
1775 if ( $j( '#' + tObj
.overlay_selector_id
).length
== 0 )
1776 js_log( "ERROR overlay selector not found: " + tObj
.overlay_selector_id
);
1778 // set the initial state show the zero opacity animation
1779 $j( '#' + tObj
.overlay_selector_id
).css( { 'opacity':0 } ).show();
1781 'u':function( tObj
, percent
) {
1782 // Do the relative seek:
1783 $j( '#' + tObj
.overlay_selector_id
).css( {
1792 /* object to manage embedding html with smil timings
1793 * grabs settings from parent clip
1795 var transitionObj = function( element
) {
1796 this.init( element
);
1798 transitionObj
.prototype = {
1799 supported_attributes
: new Array(
1806 transAttrType
:null, // transIn or transOut
1807 overlay_selector_id
:null,
1810 animation_state
:0, // can be 0=unset, 1=running, 2=done
1811 interValCount
:0, // inter-intervalCount for animating between time updates
1812 dur
:2, // default duration of 2
1813 init:function( element
) {
1814 // load supported attributes:
1816 $j
.each( this.supported_attributes
, function( i
, attr
) {
1817 if ( element
.getAttribute( attr
) )
1818 _this
[attr
] = element
.getAttribute( attr
);
1820 // @@todo process duration (for now just strip s) per:
1821 // http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
1823 _this
.dur
= smilParseTime( _this
.dur
);
1826 * returns a visual representation of the transition
1828 getIconSrc:function( opt
) {
1829 // @@todo support some arguments
1830 return mvTransLib
.getTransitionIcon( this.type
, this.subtype
);
1832 getDuration:function() {
1835 // returns the values of supported_attributes:
1836 getAttributeObj:function() {
1838 for ( var i
in this.supported_attributes
) {
1839 var attr
= this.supported_attributes
[i
];
1841 elmObj
[ attr
] = this[ attr
];
1846 * the main animation loop called every MV_ANIMATION_CB_RATE or 34ms ~around 30frames per second~
1848 run_transition:function() {
1849 // js_log('f:run_transition:' + this.interValCount);
1851 // update the time from the video if native:
1852 if ( typeof this.pClip
.embed
.vid
!= 'undefined' ) {
1853 this.interValCount
= 0;
1854 this.pClip
.embed
.currentTime
= this.pClip
.embed
.vid
.currentTime
;
1858 // relay on currentTime update grabs (every 250ms or so) (ie for images)
1859 // if(this.prev_curtime!=this.pClip.embed.currentTime){
1860 // this.prev_curtime = this.pClip.embed.currentTime;
1861 // this.interValCount=0;
1865 // start_time =assigned by doSmilActions
1866 // base_cur_time = pClip.embed.currentTime;
1867 // dur = assigned by attribute
1868 if ( this.animation_state
== 0 ) {
1869 mvTransLib
.doInitTransition( this );
1870 this.animation_state
= 1;
1872 // set percentage include diffrence of currentTime to prev_curTime
1873 // ie updated in-between currentTime updates)
1875 if ( this.transAttrType
== 'transIn' )
1876 var percentage
= ( this.pClip
.embed
.currentTime
+
1877 ( ( this.interValCount
* MV_ANIMATION_CB_RATE
) / 1000 )
1880 if ( this.transAttrType
== 'transOut' )
1881 var percentage
= ( this.pClip
.embed
.currentTime
+
1882 ( ( this.interValCount
* MV_ANIMATION_CB_RATE
) / 1000 )
1883 - ( this.pClip
.dur
- this.dur
)
1886 /*js_log('percentage = ct:'+this.pClip.embed.currentTime + ' + ic:'+this.interValCount +' * cb:'+MV_ANIMATION_CB_RATE +
1887 ' / ' + this.dur + ' = ' + percentage );
1890 // js_log('cur percentage of transition: '+percentage);
1891 // update state based on current time + cur_time_offset (for now just use pClip.embed.currentTime)
1892 mvTransLib
.doUpdate( this, percentage
);
1894 if ( percentage
>= 1 ) {
1895 js_log( "transition done update with percentage " + percentage
);
1896 this.animation_state
= 2;
1897 clearInterval( this.timerId
);
1898 mvTransLib
.doCloseTransition( this )
1902 this.interValCount
++;
1903 // setInterval in we are still in running state and user is not using the playhead
1904 if ( this.animation_state
== 1 ) {
1905 if ( !this.timerId
) {
1906 this.timerId
= setInterval( 'document.getElementById(\'' + this.pClip
.pp
.id
+ '\').' +
1907 'run_transition(\'' + this.pClip
.pp
.cur_clip
.order
+ '\',' +
1908 '\'' + this.transAttrType
+ '\')',
1909 MV_ANIMATION_CB_RATE
);
1912 clearInterval( this.timerId
);
1917 var cObj
= new this.constructor();
1918 for ( var i
in this )
1924 // very limited smile feature set more details soon:
1925 // region="video_region" transIn="fromGreen" begin="2s"
1926 // http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#edef-ref
1927 var smilPlaylist
= {
1929 doParse:function() {
1931 js_log( 'f:doParse smilPlaylist' );
1932 // @@todo get/parse meta that we are interested in:
1933 var meta_tags
= this.data
.getElementsByTagName( 'meta' );
1942 $j
.each( meta_tags
, function( i
, meta_elm
) {
1943 // js_log( "on META tag: "+ $j(meta_elm).attr('name') );
1944 if ( $j( meta_elm
).attr( 'name' ) in metaNames
) {
1945 _this
[ $j( meta_elm
).attr( 'name' ) ] = $j( meta_elm
).attr( 'content' );
1947 // Special check for wikiDesc
1948 if ( $j( meta_elm
).attr( 'name' ) == 'wikiDesc' ) {
1949 if ( meta_elm
.firstChild
)
1950 _this
.wikiDesc
= meta_elm
.firstChild
.nodeValue
;
1953 // Add transition objects:
1954 var transition_tags
= this.data
.getElementsByTagName( 'transition' );
1955 $j
.each( transition_tags
, function( i
, trans_elm
) {
1956 if ( $j( trans_elm
).attr( "id" ) ) {
1957 _this
.transitions
[ $j( trans_elm
).attr( "id" )] = new transitionObj( trans_elm
);
1959 js_log( 'skipping transition: (missing id) ' + trans_elm
);
1962 js_log( 'loaded transitions:' + _this
.transitions
.length
);
1964 // Add seq (latter we will have support more than one seq tag) / more than one "track"
1965 var seq_tags
= this.data
.getElementsByTagName( 'seq' );
1966 $j
.each( seq_tags
, function( i
, seq_elm
) {
1968 // get all the clips for the given seq:
1969 $j
.each( seq_elm
.childNodes
, function( i
, mediaElement
) {
1970 // ~complex~ @@todo to handle a lot like "switch" "region" etc
1971 // js_log('process: ' + mediaElemnt.tagName);
1972 if ( typeof mediaElement
.tagName
!= 'undefined' ) {
1973 if ( _this
.tryAddMedia( mediaElement
, inx
) ) {
1979 js_log( "done proc seq tags" );
1982 tryAddMediaObj:function( mConfig
, order
, track_id
) {
1983 js_log( 'tryAddMediaObj::' );
1984 var mediaElement
= document
.createElement( 'div' );
1985 for ( var i
= 0; i
< mv_smil_ref_supported_attributes
.length
; i
++ ) {
1986 var attr
= mv_smil_ref_supported_attributes
[i
];
1987 if ( mConfig
[attr
] )
1988 $j( mediaElement
).attr( attr
, mConfig
[attr
] );
1990 this.tryAddMedia( mediaElement
, order
, track_id
);
1992 tryAddMedia:function( mediaElement
, order
, track_id
) {
1993 js_log( 'SMIL:tryAddMedia:' + mediaElement
);
1996 // Set up basic mvSMILClip send it the mediaElemnt & mvClip init:
1999 "id" : 'p_' + _this
.id
+ '_c_' + order
,
2000 "pp" : this, // set the parent playlist object pointer
2003 var clipObj
= new mvSMILClip( mediaElement
, cConfig
);
2005 // set optional params track
2006 if ( typeof track_id
!= 'undefined' )
2007 clipObj
["track_id"] = track_id
;
2012 clipObj
.setUpEmbedObj();
2013 // inhreit embedObject (only called on "new media"
2014 clipObj
.embed
.init_with_sources_loaded();
2015 // add clip to track:
2016 this.addCliptoTrack( clipObj
, order
);
2023 // http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#smilMediaNS-BasicMedia
2024 // and added resource description elements
2025 // @@ supporting the "ID" attribute turns out to be kind of tricky since we use it internally
2026 // (for now don't include)
2027 var mv_smil_ref_supported_attributes
= new Array(
2036 // some custom attributes:
2041 /* extension to mvClip to support smil properties */
2042 var mvSMILClip = function( sClipElm
, mvClipInit
) {
2043 return this.init( sClipElm
, mvClipInit
);
2045 // all the overwritten and new methods for SMIL extension of mv_embed
2046 mvSMILClip
.prototype = {
2047 instanceOf
:'mvSMILClip',
2048 params
: { }, // support param as child of ref clips per SMIL spec
2049 init:function( sClipElm
, mvClipInit
) {
2052 // make new mvCLip with ClipInit vals
2053 var myMvClip
= new mvClip( mvClipInit
);
2055 for ( var method
in myMvClip
) {
2056 if ( typeof this[method
] != 'undefined' ) {
2057 this['parent_' + method
] = myMvClip
[method
];
2059 this[method
] = myMvClip
[method
];
2063 // get supported media attr init non-set
2064 for ( var i
= 0; i
< mv_smil_ref_supported_attributes
.length
; i
++ ) {
2065 var attr
= mv_smil_ref_supported_attributes
[i
];
2066 if ( $j( sClipElm
).attr( attr
) ) {
2067 _this
[attr
] = $j( sClipElm
).attr( attr
);
2070 this['tagName'] = sClipElm
.tagName
;
2072 // Fix url paths (if needed)
2073 if( _this
['src'] && _this
.src
.indexOf('/') != 0 && _this
.src
.indexOf('://') === -1)
2074 _this
['src'] = mw
.absoluteUrl( _this
['src'], mvClipInit
.pp
.getSrc() );
2076 if ( sClipElm
.firstChild
) {
2077 this['wholeText'] = sClipElm
.firstChild
.nodeValue
;
2078 js_log( "SET wholeText for: " + this['tagName'] + ' ' + this['wholeText'] );
2081 // mv_embed specific property:
2082 if ( $j( sClipElm
).attr( 'poster' ) )
2083 this['img'] = $j( sClipElm
).attr( 'poster' );
2085 // lookup and assign copies of transitions
2086 // (since transition needs to hold some per-instance state info)
2087 if ( this.transIn
&& this.pp
.transitions
[ this.transIn
] ) {
2088 this.transIn
= this.pp
.transitions
[ this.transIn
]. clone ();
2089 this.transIn
.pClip
= _this
;
2090 this.transIn
.transAttrType
= 'transIn';
2093 if ( this.transOut
&& this.pp
.transitions
[ this.transOut
] ) {
2094 this.transOut
= this.pp
.transitions
[ this.transOut
]. clone ();
2095 this.transOut
.pClip
= _this
;
2096 this.transOut
.transAttrType
= 'transOut';
2098 // parse duration / begin times:
2100 this.dur
= smilParseTime( this.dur
);
2102 // parse the media duration hint ( the source media length)
2103 if ( this.durationHint
)
2104 this.durationHint
= smilParseTime( this.durationHint
);
2106 // conform type to vido/ogg:
2107 if ( this.type
== 'application/ogg' )
2108 this.type
= 'video/ogg'; // conform to 'video/ogg' type
2110 // if unset type and we have innerHTML assume text/html type
2111 if ( !this.type
&& this.wholeText
) {
2112 this.type
= 'text/html';
2114 // Also grab any child param elements if present:
2115 if ( sClipElm
.getElementsByTagName( 'param' )[0] ) {
2116 for ( var i
= 0; i
< sClipElm
.getElementsByTagName( 'param' ).length
; i
++ ) {
2117 this.params
[ sClipElm
.getElementsByTagName( 'param' )[i
].getAttribute( "name" ) ] =
2118 sClipElm
.getElementsByTagName( 'param' )[i
].firstChild
.nodeValue
;
2124 * Returns the values of supported_attributes:
2126 getAttributeObj:function() {
2128 for ( var i
= 0; i
< mv_smil_ref_supported_attributes
.length
; i
++ ) {
2129 var attr
= mv_smil_ref_supported_attributes
[i
];
2131 elmObj
[ attr
] = this[attr
];
2137 * @returns duration in int
2139 getDuration:function() {
2140 // check for smil dur:
2143 return this.embed
.getDuration();
2145 // gets the duration of the clip subracting transitions
2146 getSoloDuration:function() {
2147 var fulldur
= this.getDuration();
2148 // see if we need to subtract from time eating transitions (transOut)
2149 if ( this.transOut
)
2150 fulldur
-= this.transOut
.getDuration();
2152 // js_log("getSoloDuration:: td: " + this.getDuration() + ' sd:' + fulldur);
2155 // gets the duration of the original media asset (usefull for bounding setting of in-out-points)
2156 getSourceDuration:function() {
2157 if ( this.durationHint
)
2158 return this.durationHint
;
2159 // if we have no source duration just return the media dur:
2160 return this.getDuration();
2165 * @time_str input time string
2166 * returns time in seconds
2168 * @@todo process duration (for now just srip s) per:
2169 * http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
2170 * (probably have to use a Time object to fully support the smil spec
2172 function smilParseTime( time_str
) {
2173 time_str
= time_str
+ '';
2174 // first check for hh:mm:ss time:
2175 if ( time_str
.split( ':' ).length
== 3 ) {
2176 return npt2seconds( time_str
);
2178 // assume 34s secconds representation
2179 return parseInt( time_str
.replace( 's', '' ) );
2182 // stores a list pointers to active clips (maybe this should just be a property of clips (but results in lots of seeks)
2183 var activeClipList = function() {
2186 activeClipList
.prototype = {
2188 this.clipList
= new Array();
2190 add:function( clip
) {
2191 // make sure the clip is not already active:
2192 for ( var i
= 0; i
< this.clipList
.lenght
; i
++ ) {
2193 var active_clip
= this.clipList
[i
];
2194 if ( clip
.id
== active_clip
.id
) // clip already active:
2197 this.clipList
.push( clip
);
2200 remove:function( clip
) {
2201 for ( var i
= 0; i
< this.clipList
.length
; i
++ ) {
2202 var active_clip
= this.clipList
[i
];
2203 if ( clip
.id
== active_clip
.id
) {
2204 this.clipList
.splice( i
, 1 );
2210 getClipList:function() {
2211 return this.clipList
;
2214 var trackObj = function( iObj
) {
2215 return this.init( iObj
);
2217 var supported_track_attr
=
2218 trackObj
.prototype = {
2219 // should be something like "seq" per SMIL spec
2220 // http://www.w3.org/TR/SMIL3/smil-timing.html#edef-seq
2221 // but we don't really support anywhere near the full concept of seq containers yet either
2222 supported_attributes
: new Array(
2227 disp_mode
:'timeline_thumb',
2228 init : function( iObj
) {
2231 // make sure clips is new:
2232 this.clips
= new Array();
2235 $j
.each( this.supported_attributes
, function( i
, attr
) {
2237 _this
[attr
] = iObj
[attr
];
2240 // returns the values of supported_attributes:
2241 getAttributeObj:function() {
2243 for ( var i
in this.supported_attributes
) {
2244 var attr
= this.supported_attributes
[i
];
2246 elmObj
[ attr
] = this[attr
];
2250 addClip:function( clipObj
, pos
) {
2251 js_log( 'pl_Track: AddClip at:' + pos
+ ' clen: ' + this.clips
.length
);
2252 if ( typeof pos
== 'undefined' )
2253 pos
= this.clips
.length
;
2254 // get everything after pos
2255 this.clips
.splice( pos
, 0, clipObj
);
2256 // keep the clip order values accurate:
2257 this.reOrderClips();
2258 js_log( "did add now cLen: " + this.clips
.length
);
2260 getClip:function( inx
) {
2261 if ( !this.clips
[inx
] )
2263 return this.clips
[inx
];
2265 reOrderClips:function() {
2266 for ( var k
in this.clips
) {
2267 this.clips
[k
].order
= k
;
2270 getClipCount:function() {
2271 return this.clips
.length
;
2273 inheritEmbedObj: function() {
2274 $j
.each( this.clips
, function( i
, clip
) {
2275 clip
.embed
.inheritEmbedObj();
2280 /* utility functions
2281 * (could be combined with other stuff)
2283 function getAbsolutePos( objectId
) {
2284 // Get an object left position from the upper left viewport corner
2285 o
= document
.getElementById( objectId
);
2286 oLeft
= o
.offsetLeft
; // Get left position from the parent object
2287 while ( o
.offsetParent
!= null ) { // Parse the parent hierarchy up to the document element
2288 oParent
= o
.offsetParent
// Get parent object reference
2289 oLeft
+= oParent
.offsetLeft
// Add parent left position
2292 o
= document
.getElementById( objectId
);
2294 while ( o
.offsetParent
!= null ) { // Parse the parent hierarchy up to the document element
2295 oParent
= o
.offsetParent
// Get parent object reference
2296 oTop
+= oParent
.offsetTop
// Add parent top position
2299 return { x
:oLeft
, y
:oTop
};