* added k-attribution link in credits
[lhc/web/wiklou.git] / js2 / mwEmbed / libSequencer / mvPlayList.js
1 /*
2 * the mvPlayList object code
3 * only included if playlist object found
4 *
5 * part of mwEmbed media projects see:
6 * http://www.mediawiki.org/wiki/Media_Projects_Overview
7 *
8 * @author: Michael Dale mdale@wikimedia.org
9 * @license GPL2
10 */
11 var mv_default_playlist_attributes = {
12 // playlist attributes :
13 "id":null,
14 "title":null,
15 "width":400,
16 "height":300,
17 "desc":'',
18 "controls":true,
19 // playlist user controlled features
20 "linkback":null,
21 "src":null,
22 "embed_link":true,
23
24 // enable sequencer? (only display top frame no navigation or accompanying text
25 "sequencer":false
26 }
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;
29
30 // globals:
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' );
33
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=';
37 } else {
38 var defaultMetaDataProvider = wgServer + wgScript + '?title=Special:MvExportStream&feed_format=roe&stream_name=';
39 }
40 /*
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
43 */
44 var mvPlayList = function( element ) {
45 return this.init( element );
46 };
47 // set up the mvPlaylist object
48 mvPlayList.prototype = {
49 instanceOf:'mvPlayList',
50 pl_duration:null,
51 update_tl_hook:null,
52 clip_ready_count:0,
53 cur_clip:null,
54 start_clip:null,
55 start_clip_src:null,
56 disp_play_head:null,
57 userSlide:false,
58 loading:true,
59 loading_external_data:true, // if we are loading external data (set to loading by default)
60 //set initial state to "paused"
61 paused:true,
62
63 activeClipList:null,
64 playlist_buffer_time: 20, // how many seconds of future clips we should buffer
65
66 interface_url:null, // the interface url
67 tracks: { },
68 default_track:null, // the default track to add clips to.
69 // the layout for the playlist object
70 pl_layout : {
71 seq_title:.1,
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:
78 title_bar_height:17,
79 control_height:29
80 },
81 // embed object type support system;
82 supports: {
83 'play_head':true,
84 'pause':true,
85 'fullscreen':false,
86 'time_display':true,
87 'volume_control':true,
88
89 'overlays':true,
90 'playlist_swap_loader':true // if the object supports playlist functions
91 },
92 init : function( element ) {
93 js_log( 'mvPlayList:init:' );
94 this.tracks = { };
95 this.default_track = null;
96
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];
101
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)");
107 } else {
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)");
110 }
111 }
112 // make sure height and width are int:
113 this.width = parseInt( this.width );
114 this.height = parseInt( this.height );
115
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', '' ) );
119
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;
124 } else {
125 // setup the controlBuilder object:
126 this.ctrlBuilder = new ctrlBuilder( this );
127 }
128 },
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:
133 this.getHTML();
134 },
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();
139 } );
140 },
141 doOptionsHTML:function() {
142 // grab "options" use current clip:
143 this.cur_clip.embed.doOptionsHTML();
144 },
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>' );
152
153 // @@todo clone the playlist (for faster startup)
154 /*
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';
158 * debugger;
159 */
160
161 // load sequencer:
162 $j( "#sequencer_target" ).sequencer( {
163 "mv_pl_src" : this.src
164 } );
165
166 },
167 showPlayerselect:function() {
168 this.cur_clip.embed.showPlayerselect();
169 },
170 closeDisplayedHTML:function() {
171 this.cur_clip.embed.closeDisplayedHTML();
172 },
173 showDownload:function() {
174 this.cur_clip.embed.showDownload();
175 },
176 showShare:function() {
177 var embed_code = '&lt;script type=&quot;text/javascript&quot; ' +
178 'src=&quot;' + mv_embed_path + 'mv_embed.js&quot;&gt;&lt;/script&gt ' + "\n" +
179 '&lt;playlist id=&quot;' + this.id + '&quot; ';
180 if ( this.src ) {
181 embed_code += 'src=&quot;' + this.src + '&quot; /&gt;';
182 } else {
183 embed_code += '&gt;' + "\n";
184 embed_code += escape( this.data );
185 embed_code += '&lt;playlist/&gt;';
186 }
187 this.cur_clip.embed.showShare( embed_code );
188 },
189 timedTextSources:function() {
190 return false;
191 },
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 );
202 }
203
204 if ( typeof this.doParse != 'function' ) {
205 js_log( 'error: method doParse not found in plObj' + this.srcType );
206 return false;
207 }
208
209 if ( typeof this.doParse == 'function' ) {
210 if ( this.doParse() ) {
211 this.doWhenParseDone();
212 } else {
213 js_log( "error: failed to parse playlist" );
214 return false;
215 // error or parse needs to do ajax requests
216 }
217 }
218 },
219 doNativeWarningCheck:function() {
220 var clip = this.default_track.clips[0];
221 if ( clip ) {
222 return clip.embed.doNativeWarningCheck();
223 }
224 },
225 doWhenParseDone:function() {
226 js_log( 'f:doWhenParseDone' );
227 // do additional init for clips:
228 var _this = this;
229 var error = false;
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:
236 break;
237 }
238 if ( clip.embed.ready_to_play ) {
239 _this.clip_ready_count++;
240 continue;
241 }
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();
247 }
248 }
249
250 // @@todo for some plugins we have to conform types of clips
251 // ie vlc can play flash _followed_by_ ogg _followed_by_ whatever
252 // but
253 // native ff 3.1a2 can only play ogg
254 if ( error ) {
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();
260 } else {
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();
265 //debugger;
266 }, 100 );
267 }
268 },
269 doWhenClipLoadDone:function() {
270 js_log( 'mvPlaylist:doWhenClipLoadDone' );
271 this.ready_to_play = true;
272 this.loading = false;
273 this.getHTML();
274 },
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;
279
280 var durSum = 0;
281 $j.each( this.default_track.clips, function( i, clip ) {
282 if ( clip.embed ) {
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();
288 } else {
289 durSum += clip.getDuration();
290 }
291 } else {
292 js_log( "ERROR: clip " + clip.id + " not ready" );
293 }
294 } );
295 this.pl_duration = durSum;
296 // js_log("return dur: " + this.pl_duration);
297 return this.pl_duration;
298 },
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() );
302 },
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();
312 } );
313 }
314 },
315 getSrc: function(){
316 return this.src;
317 },
318 getSourceType:function() {
319 js_log( 'data type of: ' + this.src + ' = ' + typeof ( this.data ) + "\n" + this.data );
320 this.srcType = null;
321 // if not external use different detection matrix
322 if ( this.loading_external_data ) {
323 if ( typeof this.data == 'object' ) {
324 js_log( 'object' );
325 // object assume xml (either xspf or rss)
326 plElm = this.data.getElementsByTagName( 'playlist' )[0];
327 if ( plElm ) {
328 if ( plElm.getAttribute( 'xmlns' ) == 'http://xspf.org/ns/0/' ) {
329 this.srcType = 'xspf';
330 }
331 }
332 // check itunes style rss "items"
333 rssElm = this.data.getElementsByTagName( 'rss' )[0];
334 if ( rssElm ) {
335 if ( rssElm.getAttribute( 'xmlns:itunes' ) == 'http://www.itunes.com/dtds/podcast-1.0.dtd' ) {
336 this.srcType = 'itunes';
337 }
338 }
339 // check for smil tag:
340 smilElm = this.data.getElementsByTagName( 'smil' )[0];
341 if ( smilElm ) {
342 // don't check dtd yet.. (have not defined the smil subset)
343 this.srcType = 'smil';
344 }
345 } else if ( typeof this.data == 'string' ) {
346 js_log( '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 );
350 // string
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';
356 }
357 }
358 }
359 if ( this.srcType ) {
360 js_log( 'is of type:' + this.srcType );
361 this.getPlaylist();
362 } else {
363 // unknown playlist type
364 js_log( 'unknown playlist type?' );
365 if ( this.src ) {
366 this.innerHTML = 'error: unknown playlist type at url:<br> ' + this.src;
367 } else {
368 this.innerHTML = 'error: unset src or unknown inline playlist data<br>';
369 }
370 }
371 },
372 // simple function to make a path into an absolute url if its not already
373 makeURLAbsolute:function() {
374 if ( this.src ) {
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;
379 } else {
380 this.src = purl.protocol + '://' + purl.host + purl.directory + this.src;
381 }
382 }
383 }
384 },
385 // set up minimal media_element emulation:
386 media_element: {
387 selected_source: {
388 supports_url_time_encoding:true
389 }
390 },
391 // @@todo needs to update for multi-track clip counts
392 getClipCount:function() {
393 return this.default_track.clips.length;
394 },
395 // },
396 // takes in the playlist
397 // inherits all the properties
398 // swaps in the playlist object html/interface div
399 getHTML:function() {
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();
406 } else {
407 // detect datatype and parse directly:
408 this.getSourceType();
409 }
410 } else {
411 // check for empty playlist otherwise renderDisplay:
412 if ( this.default_track.getClipCount() == 0 ) {
413 $j( this ).html( 'empty playlist' );
414 return ;
415 } else {
416 this.renderDisplay();
417 }
418 }
419 },
420 renderDisplay:function() {
421 js_log( 'mvPlaylist:renderDisplay:: track length: ' + this.default_track.getClipCount() );
422 var _this = this;
423
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;">' +
428 '</div>' );
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;
433 // append title:
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() +
440 '</div>' +
441 '</div>'
442 );
443
444 // once the controls are in the DOM add hooks:
445 this.ctrlBuilder.addControlHooks( );
446 } else {
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>'
450 );
451 }
452 this.setupClipDisplay();
453
454 // update the title and status bar
455 this.updateBaseStatus();
456 this.doSmilActions();
457 },
458 setupClipDisplay:function() {
459 js_log( 'mvPlaylist:setupClipDisplay:: clip len:' + this.default_track.clips.length );
460 var _this = this;
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;';
468 }
469 cout += '"></div>';
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;
476
477 clip.embed.getHTML();// get the thubnails for everything
478
479 $j( clip.embed ).css( {
480 'position':"absolute",
481 'top':"0px",
482 'left':"0px"
483 } );
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 );
487 } else {
488 js_log( 'cound not find: clipDesc_' + clip.id );
489 }
490 } );
491 if ( this.cur_clip )
492 $j( '#clipDesc_' + this.cur_clip.id ).css( { display:'inline' } );
493 },
494 updateThumbPerc:function( perc ) {
495 // get float seconds:
496 var float_sec = ( this.getDuration() * perc );
497 this.updateThumbTime( float_sec );
498 },
499 updateThumbTime:function( float_sec ) {
500 // update display & cur_clip:
501 var pl_sum_time = 0;
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();
511 }
512 break;
513 }
514 pl_sum_time += clip.getDuration();
515 }
516
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 );
520
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 );
523
524 // render effects ontop: (handled by doSmilActions)
525 this.doSmilActions();
526 },
527 updateBaseStatus:function() {
528 var _this = this;
529 js_log( 'Playlist:updateBaseStatus' );
530
531 $j( '#ptitle_' + this.id ).html( '' +
532 '<b>' + this.title + '</b> ' +
533 this.getClipCount() + ' clips, <i>' +
534 seconds2npt( this.getDuration() ) + '</i>' );
535
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() {
541 _this.stop();
542 _this.doEditor();
543 return false;
544 } ).appendTo( '#ptitle_' + this.id );
545 $j( '.editBtn_' + this.id ).btnBind();
546 }
547 // render out the dividers on the timeline:
548 this.colorPlayHead();
549 // update status:
550 this.setStatus( '0:0:00/' + seconds2npt( this.getDuration() ) );
551 },
552 /*setStatus override (could call the jquery directly) */
553 setStatus:function( value ) {
554 $j( '#' + this.id + ' .time-disp' ).text( value );
555 },
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 );
561 },
562 getPlayHeadPos: function( prec_done ) {
563 var _this = this;
564 if ( $j( '#mv_seeker_' + this.id ).length == 0 ) {
565 js_log( 'no playhead so we can\'t get playhead pos' );
566 return 0;
567 }
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;
577 }
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 );
583 } else {
584 // string type passed use eval:
585 eval( this.update_tl_hook + '(' + cur_time_ms + ');' );
586 }
587 }
588
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 );
594 },
595 // attempts to load the embed object with the playlist
596 loadEmbedPlaylist: function() {
597 // js_log('load playlist');
598 },
599 /** mannages the loading of future clips
600 * called regurally while we are playing clips
601 *
602 * load works like so:
603 * if the current clip is full loaded
604 * load clips untill buffredEndTime < playlist_buffer_time load next
605 *
606 * this won't work so well with time range loading for smil (need to work on that)
607 */
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;
612
613 if(curBuffredTime < 0)
614 curBuffredTime = 0;
615
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);
619
620 for(var inx = this.cur_clip.order + 1; inx < this.default_track.clips.length; inx++ ){
621 var cClip = this.default_track.getClip( inx );
622
623 //check if the clip is already loaded (add its duration)
624 if( cClip.embed.bufferedPercent == 1){
625 curBuffredTime += cClip.embed.getDuration();
626 }
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 ){
631 cClip.embed.load();
632 }
633 break; //check back next time
634 }
635 }
636 }
637 }*/
638 },
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();
643
644 if ( !next_clip ) {
645 js_log( 'play next with no next clip... must be done:' );
646 this.onClipDone();
647 return ;
648 }
649 // @@todo where the plugin supports pre_loading future clips and manage that in javascript
650 // stop current clip
651 this.cur_clip.embed.stop();
652
653 this.updateCurrentClip( next_clip );
654 //if part of a transition should continue playing where it left off
655 this.cur_clip.embed.play();
656 },
657 onClipDone:function() {
658 js_log( "pl onClipDone" );
659 this.cur_clip.embed.stop();
660 },
661 updateCurrentClip : function( new_clip , into_perc) {
662 js_log( 'f:updateCurrentClip:' + new_clip.id );
663
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' );
669 return false;
670 }
671
672 if ( !this.cur_clip.embed.isStoped() )
673 this.cur_clip.embed.stop();
674 this.activeClipList.remove( this.cur_clip )
675
676 //hide the current clip
677 $j( '#clipDesc_' + this.cur_clip.id ).hide();
678 }
679 this.activeClipList.add( new_clip );
680 // do swap:
681 this.cur_clip = new_clip;
682 $j( '#clipDesc_' + this.cur_clip.id ).show();
683
684 // Update the playhead:
685 if( this.controls ){
686 // Check if we have into_perc
687 if( into_perc ){
688 var clip_time = this.cur_clip.dur_offset + ( into_perc * this.cur_clip.getDuration() );
689 }else{
690 var clip_time = this.cur_clip.dur_offset;
691 }
692
693 this.setSliderValue( clip_time / this.getDuration() );
694 }
695 },
696 playPrev: function() {
697 // advance the playhead to the previous clip
698 var prev_clip = this.getPrevClip();
699 if ( !prev_clip ) {
700 js_log( "tried to play PrevClip with no prev Clip.. setting prev_clip to start clip" );
701 prev_clip = this.start_clip;
702 }
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();
709 // do swap:
710 this.updateCurrentClip( prev_clip );
711 this.cur_clip.embed.play();
712 } else {
713 js_log( 'do prev hard embed swap' );
714 this.switchPlayingClip( prev_clip );
715 }
716 },
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;
725 // run js code:
726 this.cur_clip.embed.pe_postEmbedJS();
727 },
728 // playlist play
729 play: function() {
730 var _this = this;
731 js_log( 'pl play' );
732 // hide the playlist play button:
733 $j( this.id + ' .play-btn-large' ).hide();
734
735 // un-pause if paused:
736 if ( this.paused )
737 this.paused = false;
738
739 // update the control:
740 this.start_clip = this.cur_clip;
741 this.start_clip_src = this.cur_clip.src;
742
743 if ( this.cur_clip.embed.supports['playlist_swap_loader'] ) {
744 // set the cur_clip to active
745 this.activeClipList.add( this.cur_clip );
746
747 // navtive support:
748 // * pre-loads clips
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 ) {
754 clip.embed.play();
755 } );
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 );
760 } else {
761 // not much playlist support just play the first clip:
762 // js_log('basic play');
763 // play cur_clip
764 this.cur_clip.embed.play();
765 }
766 // start up the playlist monitor
767 this.monitor();
768 },
769 /*
770 * the load function loads all the clips in order
771 */
772 load:function() {
773 // do nothing right now)
774 alert('load pl');
775 },
776 toggleMute:function() {
777 this.cur_clip.embed.toggleMute();
778 },
779 pause:function() {
780 // js_log('f:pause: playlist');
781 var ct = new Date();
782 this.pauseTime = this.currentTime;
783 this.paused = true;
784 // js_log('pause time: '+ this.pauseTime + ' call embed pause:');
785
786 // pause all the active clips:
787 $j.each( this.activeClipList.getClipList(), function( inx, clip ) {
788 clip.embed.pause();
789 } );
790 },
791 // @@todo mute across all child clips:
792 toggleMute:function() {
793 var this_id = ( this.pc != null ) ? this.pc.pp.id:this.id;
794 if ( this.muted ) {
795 this.muted = false;
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 );
799 } else {
800 this.muted = true;
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 );
804 }
805 js_log( 'f:toggleMute::' + this.muted );
806 },
807 updateVolumen:function( perc ) {
808 js_log( 'update volume not supported with current playback type' );
809 },
810 fullscreen:function() {
811 this.cur_clip.embed.fullscreen();
812 },
813 // playlist stops playback for the current clip (and resets state for start clips)
814 stop:function() {
815 var _this = this;
816 /*js_log("pl stop:"+ this.start_clip.id + ' c:'+this.cur_clip.id);
817 //if start clip
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;
827 }
828 //empty the play-back container
829 $j('#mv_ebct_'+this.id).empty();*/
830
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];
835 if(clip){
836 clip.embed.stop();
837 $j('#clipDesc_'+clip.id).hide();
838 }
839 }*/
840 // stop, hide and remove all active clips:
841 $j.each( this.activeClipList.getClipList(), function( inx, clip ) {
842 if ( clip ) {
843 clip.embed.stop();
844 $j( '#clipDesc_' + clip.id ).hide();
845 _this.activeClipList.remove( clip );
846 }
847 } );
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();
853 // hide other clips:
854 $j( '#' + this.id + ' .clip_container' ).hide();
855 // show the first/current clip:
856 $j( '#clipDesc_' + this.cur_clip.id ).show();
857 }
858 // reset the currentTime:
859 this.currentTime = 0;
860 // rest the sldier
861 this.setSliderValue( 0 );
862 // FIXME still some issues with "stoping" and reseting the playlist
863 },
864 doSeek:function( v ) {
865 js_log( 'pl:doSeek:' + v + ' sts:' + this.seek_time_sec );
866 var _this = this;
867
868 var time = v * this.getDuration()
869 _this.currentTime = time;
870 var relative_perc = _this.updateClipByTime();
871
872 // Update the clip relative seek_time_sec
873 _this.cur_clip.embed.doSeek( relative_perc );
874 _this.monitor();
875
876 return '';
877 },
878 setCurrentTime: function( time, callback ) {
879 js_log( 'pl:setCurrentTime:' + time );
880 var _this = this;
881 _this.currentTime = time;
882
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();
889 //say we are "ready"
890 if ( callback )
891 callback();
892 } );
893 },
894 /*
895 * updateClipByTime::
896 *
897 * @returns the relative offsets of the current clip (given the playlist time)
898 */
899 updateClipByTime: function(){
900 var _this = this;
901 var prevClip = null;
902 //set the current percent done:
903 var pt = this.currentTime / _this.getDuration();
904 // jump to the clip in the current percent.
905 var perc_offset = 0;
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;
917 }
918 perc_offset = next_perc_offset;
919 }
920 return 0;
921 },
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 );
927 },
928 // ads colors/dividers between tracks
929 colorPlayHead: function() {
930 var _this = this;
931
932 if ( !_this.mv_seeker_width )
933 _this.mv_seeker_width = $j( '#' + _this.id + ' .play_head' ).width();
934
935 if ( !_this.track_len )
936 _this.track_len = $j( '#' + _this.id + ' .play_head' ).width();
937
938 // total duration:
939 var pl_duration = _this.getDuration();
940
941 var cur_pixle = 0;
942 // set up _this
943
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) );
951
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;">';
962
963 barHtml += _this.ctrlBuilder.getMvBufferHtml();
964
965 barHtml += '</div>';
966
967 // background:#DDD +clip.getColor();
968
969 $j( '#' + _this.id + ' .play_head' ).append( barHtml );
970
971 // js_log('offset:' + cur_pixle +' width:'+pwidth+' add clip'+ clip.id + ' is '+clip.embed.getDuration() +' = ' + perc +' of ' + _this.track_len);
972 cur_pixle += pwidth;
973 } );
974 },
975 // @@todo currently not really in use
976 setUpHover:function() {
977 js_log( 'Setup Hover' );
978 // set up hover for prev,next
979 var th = 50;
980 var tw = th * this.pl_layout.clip_aspect;
981 var _this = this;
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();
984 if ( !clip )
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 + '"/>' +
992 '</div>' );
993 }, function() {
994 $j( '#mv_Athub' ).remove();
995 } );
996 },
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 ) {
1000 if ( !track )
1001 track = this.default_track;
1002 var tc = parseInt( this.cur_clip.order ) + 1;
1003 var cat = track;
1004 if ( tc > track.getClipCount() - 1 )
1005 return false; // out of range
1006
1007 return track.getClip( tc );
1008 },
1009 getPrevClip:function( track ) {
1010 if ( !track )
1011 track = this.default_track;
1012 var tc = parseInt( this.cur_clip.order ) - 1;
1013 if ( tc < 0 )
1014 return false;
1015 return track.getClip( tc );
1016 },
1017 /*
1018 * generic add Clip to ~default~ track
1019 */
1020 addCliptoTrack: function( clipObj, pos ) {
1021 if ( typeof clipObj['track_id'] == 'undefined' ) {
1022 var track = this.default_track;
1023 } else {
1024 var track = this.tracks[ clipObj.track_id ]
1025 }
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;
1030 }
1031 track.addClip( clipObj, pos );
1032 },
1033 swapClipDesc: function( req_clipID, callback ) {
1034 // hide all but the requested
1035 var _this = this;
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' );
1039 } else {
1040 // fade out clips
1041 req_clip = null;
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" );
1047 }
1048 } else {
1049 req_clip = clip;
1050 }
1051 } );
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;
1055 if ( callback )
1056 callback();
1057 } );
1058 }
1059 },
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' } ) +
1066 '</a>' +
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' } ) +
1070 '</a>';
1071 },
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 );
1075 else
1076 this.default_track.clips[ clip_inx ][ trans_type ].run_transition();
1077 },
1078 playerPixelWidth : function()
1079 {
1080 var player = $j( '#dc_' + this.id ).get( 0 );
1081 if ( typeof player != 'undefined' && player['offsetWidth'] )
1082 return player.offsetWidth;
1083 else
1084 return parseInt( this.width );
1085 },
1086 playerPixelHeight : function()
1087 {
1088 var player = $j( '#dc_' + this.id ).get( 0 );
1089 if ( typeof player != 'undefined' && player['offsetHeight'] )
1090 return player.offsetHeight;
1091 else
1092 return parseInt( this.height );
1093 }
1094 }
1095
1096 /* Object Stubs:
1097 *
1098 * @videoTrack ... stores clips and layer info
1099 *
1100 * @clip... each clip segment is a clip object.
1101 * */
1102 var mvClip = function( o ) {
1103 if ( o )
1104 this.init( o );
1105 return this;
1106 };
1107 // set up the mvPlaylist object
1108 mvClip.prototype = {
1109 id:null, // clip id
1110 pp:null, // parent playlist
1111 order:null, // the order/array key for the current clip
1112 src:null,
1113 info:null,
1114 title:null,
1115 mvclip:null,
1116 type:null,
1117 img:null,
1118 duration:null,
1119 loading:false,
1120 isAnimating:false,
1121 init:function( o ) {
1122 // init object including pointer to parent
1123 for ( var i in o ) {
1124 this[i] = o[i];
1125 };
1126 js_log( 'id is: ' + this.id );
1127 },
1128 // setup the embed object:
1129 setUpEmbedObj:function() {
1130 js_log( 'mvClip:setUpEmbedObj()' );
1131
1132 this.embed = null;
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
1137 src:this.src
1138 };
1139
1140 this.setBaseEmbedDim( init_pl_embed );
1141
1142
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;
1149
1150 if( this.durationHint )
1151 init_pl_embed.durationHint = this.durationHint;
1152
1153 if ( this.poster )init_pl_embed['thumbnail'] = this.poster;
1154
1155 if ( this.type )init_pl_embed['type'] = this.type;
1156
1157 this.embed = new PlMvEmbed( init_pl_embed );
1158
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);
1162 },
1163 doAdjust:function( side, delta ) {
1164 js_log( "f:doAdjust: " + side + ' , ' + delta );
1165 if ( this.embed ) {
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 ) );
1172 }
1173 // update everything:
1174 this.pp.refresh();
1175 /*var base_src = this.src.substr(0,this.src.indexOf('?'));
1176 js_log("delta:"+ delta);
1177 if(side=='start'){
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);
1185 }
1186 this.embed.updateVideoTime( this.src );
1187 //update values
1188 this.duration = this.embed.getDuration();
1189 this.pp.pl_duration=null;
1190 //update playlist stuff:
1191 this.pp.updateTitle();*/
1192 }
1193 },
1194 getDuration:function() {
1195 if ( !this.embed )this.setUpEmbedObj();
1196 return this.embed.getDuration();
1197 },
1198 setBaseEmbedDim:function( o ) {
1199 if ( !o )o = this;
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;
1204 },
1205 // output the detail view:
1206 // @@todo
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 );
1211
1212 var twDesc = (this.pp.width-tw)-2;
1213
1214 if(this.title==null)
1215 this.title='clip ' + this.order + ' ' +this.pp.title;
1216 if(this.desc==null)
1217 this.desc=this.pp.desc;
1218 //update the embed html:
1219 this.embed.getHTML();
1220
1221 $j(this.embed).css({ 'position':"absolute",'top':"0px", 'left':"0px"});
1222
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);
1226
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() );
1232 '</div>');
1233 }
1234 },*/
1235 getTitle:function() {
1236 if ( typeof this.title == 'string' )
1237 return this.title
1238
1239 return 'untitled clip ' + this.order;
1240 },
1241 getClipImg:function( start_offset, size ) {
1242 js_log( 'f:getClipImg ' + start_offset + ' s:' + size );
1243 if ( !this.img ) {
1244 return mv_default_thumb_url;
1245 } else {
1246 if ( !size && !start_offset ) {
1247 return this.img;
1248 } else {
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 );
1257 } else {
1258 this.img += '&size=' + size;
1259 }
1260 }
1261 return this.img;
1262 }
1263 }
1264 },
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 );
1270 }
1271 if ( num >= 10 )num = num % 10;
1272 return mv_clip_colors[num];
1273 }
1274 }
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];
1285 }
1286 }
1287 js_log( 've src len:' + ve.media_element.sources.length );
1288 return ve;
1289 }
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:
1297 if ( i == 'pc' ) {
1298 this['pc'] = vid_init['pc'];
1299 } else {
1300 ve.setAttribute( i, vid_init[i] );
1301 }
1302 }
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];
1310 } else {
1311 this[method] = videoInterface[method];
1312 }
1313 }
1314 // string -> boolean:
1315 if ( this[method] == "false" )this[method] = false;
1316 if ( this[method] == "true" )this[method] = true;
1317 }
1318 },
1319 onClipDone:function() {
1320 js_log( 'pl onClipDone (should go to next)' );
1321 // go to next in playlist:
1322 this.pc.pp.playNext();
1323 },
1324 stop:function() {
1325 js_log( 'pl:do stop' );
1326 // set up convenience pointer to parent playlist
1327 var _this = this.pc.pp;
1328
1329 var th = Math.round( _this.pl_layout.clip_desc * _this.height );
1330 var tw = Math.round( th * _this.pl_layout.clip_aspect );
1331
1332 // run the parent stop:
1333 this.pe_stop();
1334 var pl_height = ( _this.sequencer == 'true' ) ? _this.height + 27:_this.height;
1335
1336 this.getHTML();
1337 },
1338 play:function() {
1339 // js_log('pl eb play');
1340 var _this = this.pc.pp;
1341 // check if we are already playing
1342 if ( !this.thumbnail_disp ) {
1343 this.pe_play();
1344 return '';
1345 }
1346 mv_lock_vid_updates = true;
1347 this.pe_play();
1348 },
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;
1362 },
1363 getPlayButton:function() {
1364 return this.pe_getPlayButton( this.pc.pp.id );
1365 },
1366 setStatus:function( value ) {
1367 // status updates handled by playlist obj
1368 },
1369 setSliderValue:function( value ) {
1370 //js_log( 'PlMvEmbed:setSliderValue:' + value );
1371 // setSlider value handled by playlist obj
1372 }
1373 }
1374
1375 /*
1376 * m3u parse
1377 */
1378 var m3uPlaylist = {
1379 doParse:function() {
1380 // for each line not # add as clip
1381 var inx = 0;
1382 var this_pl = this;
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 );
1395 inx++;
1396 }
1397 }
1398 } );
1399 return true;
1400 }
1401 }
1402
1403 var itunesPlaylist = {
1404 doParse:function() {
1405 var properties = { title:'title', linkback:'link',
1406 author:'itunes:author', desc:'description',
1407 date:'pubDate' };
1408 var tmpElm = null;
1409 for ( i in properties ) {
1410 tmpElm = this.data.getElementsByTagName( properties[i] )[0];
1411 if ( tmpElm ) {
1412 this[i] = tmpElm.childNodes[0].nodeValue;
1413 // js_log('set '+i+' to '+this[i]);
1414 }
1415 }
1416 // image src is nested in itunes rss:
1417 tmpElm = this.data.getElementsByTagName( 'image' )[0];
1418 if ( tmpElm ) {
1419 imgElm = tmpElm.getElementsByTagName( 'url' )[0];
1420 if ( imgElm ) {
1421 this.img = imgElm.childNodes[0].nodeValue;
1422 }
1423 }
1424 // get the clips:
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]);
1434 }
1435 }
1436 // image is nested
1437 tmpElm = clips[i].getElementsByTagName( 'image' )[0];
1438 if ( tmpElm ) {
1439 imgElm = tmpElm.getElementsByTagName( 'url' )[0];
1440 if ( imgElm ) {
1441 cur_clip.img = imgElm.childNodes[0].nodeValue;
1442 }
1443 }
1444 // set up the embed object now that all the values have been set
1445 cur_clip.setUpEmbedObj();
1446
1447 // add the current clip to the clip list
1448 this.addCliptoTrack( cur_clip );
1449 }
1450 return true;
1451 }
1452 }
1453
1454 /*
1455 * parse xsfp:
1456 * http://www.xspf.org/xspf-v1.html
1457 */
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' };
1464 var tmpElm = null;
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];
1470 if ( tmpElm ) {
1471 if ( tmpElm.childNodes[0] ) {
1472 this[i] = tmpElm.childNodes[0].nodeValue;
1473 js_log( 'set pl property: ' + i + ' to ' + this[i] );
1474 }
1475 }
1476 }
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] );
1490 }
1491 }
1492 }
1493 // add mvClip ref from info link:
1494 if ( cur_clip.linkback ) {
1495 // if mv linkback
1496 mvInx = 'Stream:';
1497 mvclippos = cur_clip.linkback.indexOf( mvInx );
1498 if ( mvclippos !== false ) {
1499 cur_clip.mvclip = cur_clip.linkback.substr( mvclippos + mvInx.length );
1500 }
1501 }
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 );
1506 }
1507 // js_log('done with parse');
1508 return true;
1509 }
1510 }
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 );
1520 return ;
1521 }
1522 // js_log("pl check: " + this.currentTime + ' > '+this.getDuration());
1523 // check if we should be done:
1524 if ( this.currentTime > this.getDuration() )
1525 this.stop();
1526
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();
1530
1531 // update slider:
1532 if ( !this.userSlide ) {
1533 this.setStatus( seconds2npt( this.currentTime ) + '/' + seconds2npt( this.getDuration() ) );
1534 this.setSliderValue( this.currentTime / this.getDuration() );
1535 }
1536 // pre-load any future clips:
1537 this.loadFutureClips();
1538
1539
1540 // status updates are handled by children clips ... playlist mostly manages smil actions
1541 this.doSmilActions();
1542
1543 if ( ! this.smil_monitorTimerId ) {
1544 if ( document.getElementById( this.id ) ) {
1545 this.smil_monitorTimerId = setInterval( '$j(\'#' + this.id + '\').get(0).monitor()', 250 );
1546 }
1547 }
1548 }
1549
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 ) {
1553 var _this = this;
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
1557
1558
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
1562 } else {
1563 // Assume playing and go to next:
1564 if ( _clip.dur <= _clip.embed.currentTime
1565 && _clip.order != _clip.pp.getClipCount() - 1 ) {
1566 // force next clip
1567 js_log( 'order:' + _clip.order + ' != count:' + ( _clip.pp.getClipCount() - 1 ) +
1568 ' smil dur: ' + _clip.dur + ' <= curTime: ' + _clip.embed.currentTime + ' go to next clip..' );
1569 // do a _play next:
1570 _clip.pp.playNext();
1571 }
1572 }
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)
1575
1576 // process actions per transition types:
1577 _this.procTranType( 'transIn', callback);
1578 _this.procTranType( 'transOut', callback);
1579 }
1580
1581 /*
1582 * procTranType
1583 * @param {string} tid the transition type [transIn|transOut]
1584 * @param {function} callback the callback function passed onto doUPdate
1585 */
1586 mvPlayList.prototype.procTranType = function( tid, callback){
1587 // Setup local clip pointer:
1588 var _clip = this.cur_clip;
1589
1590 eval( 'var tObj = _clip.' + tid );
1591 if ( !tObj )
1592 return;
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;
1597
1598 if ( tid == 'transOut' )
1599 in_range = ( _clip.embed.currentTime >= ( _clip.dur - tObj.dur ) ) ? true : false;
1600
1601 if ( in_range ) {
1602 if ( this.userSlide || this.paused ) {
1603 if ( tid == 'transIn' ){
1604 mvTransLib.doUpdate( tObj,
1605 ( _clip.embed.currentTime / tObj.dur ),
1606 callback );
1607 }
1608 if ( tid == 'transOut' ){
1609 mvTransLib.doUpdate( tObj,
1610 ( ( _clip.embed.currentTime - ( _clip.dur - tObj.dur ) ) / tObj.dur ),
1611 callback );
1612 }
1613 } else if ( tObj.animation_state == 0 ) {
1614 js_log( 'init/run_transition ' );
1615 tObj.run_transition();
1616 }
1617 } else {
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 );
1622 }
1623 }
1624 // Run the callback::
1625 if( callback )
1626 callback();
1627 }
1628
1629 /*
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 )
1634 *
1635 * Smil Transition Effects see:
1636 * http://www.w3.org/TR/SMIL3/smil-transitions.html#TransitionEffects-TransitionAttribute
1637 */
1638 var mvTransLib = {
1639 /*
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
1644 */
1645 doInitTransition:function( tObj ) {
1646 js_log( 'mvTransLib:f:doInitTransition' );
1647 if ( !tObj.type ) {
1648 js_log( 'transition is missing type attribute' );
1649 return false;
1650 }
1651
1652 if ( !tObj.subtype ) {
1653 js_log( 'transition is missing subtype attribute' );
1654 return false;
1655 }
1656
1657 if ( !this['type'][tObj.type] ) {
1658 js_log( 'mvTransLib does not support type: ' + tObj.type );
1659 return false;
1660 }
1661
1662 if ( !this['type'][tObj.type][tObj.subtype] ) {
1663 js_log( 'mvTransLib does not support subType: ' + tObj.subtype );
1664 return false;
1665 }
1666
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();
1673
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();
1679 }else{
1680 //issue a load request:
1681 other_pClip.embed.load();
1682 }
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;
1686 } else {
1687 tObj.overlay_selector_id = this.getOverlaySelector( tObj );
1688 }
1689
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 );
1693 },
1694 doCloseTransition:function( tObj ) {
1695 if ( tObj.subtype == 'crossfade' ) {
1696 // close up crossfade
1697 js_log( "close up crossfade" );
1698 } else {
1699 $j( '#' + tObj.overlay_selector_id ).remove();
1700 }
1701 // null selector:
1702 tObj.overlay_selector_id = null;
1703 },
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;' +
1714 'z-index:2">' +
1715 '</div>' );
1716 }
1717 return overlay_selector_id;
1718 },
1719 doUpdate:function( tObj, percent, callback ) {
1720 // init the transition if necessary:
1721 if ( !tObj.overlay_selector_id )
1722 this.doInitTransition( tObj );
1723
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();
1727
1728 // do update:
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);*/
1734
1735 this[ 'type' ][ tObj.type ][ tObj.subtype ].u( tObj, percent, callback);
1736 },
1737 getTransitionIcon:function( type, subtype ) {
1738 return mv_embed_path + '/skins/common/transition_images/' + type + '_' + subtype + '.png';
1739 },
1740 /*
1741 * mvTransLib: functional library mapping:
1742 */
1743 type: {
1744 // Types:
1745 fade: {
1746 fadeFromColor: {
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 );
1754 }
1755 // set the initial state
1756 $j( '#' + tObj.overlay_selector_id ).css( {
1757 'background-color':tObj.fadeColor,
1758 'opacity':"1"
1759 } );
1760 },
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( {
1766 "opacity" : percent
1767 } );
1768 }
1769 },
1770 // corssFade
1771 crossfade: {
1772 "attr" : [],
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 );
1777
1778 // set the initial state show the zero opacity animation
1779 $j( '#' + tObj.overlay_selector_id ).css( { 'opacity':0 } ).show();
1780 },
1781 'u':function( tObj, percent ) {
1782 // Do the relative seek:
1783 $j( '#' + tObj.overlay_selector_id ).css( {
1784 "opacity" : percent
1785 } );
1786 }
1787 }
1788 }
1789 }
1790 }
1791
1792 /* object to manage embedding html with smil timings
1793 * grabs settings from parent clip
1794 */
1795 var transitionObj = function( element ) {
1796 this.init( element );
1797 };
1798 transitionObj.prototype = {
1799 supported_attributes : new Array(
1800 'id',
1801 'type',
1802 'subtype',
1803 'fadeColor',
1804 'dur'
1805 ),
1806 transAttrType:null, // transIn or transOut
1807 overlay_selector_id:null,
1808 pClip:null,
1809 timerId: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:
1815 var _this = this;
1816 $j.each( this.supported_attributes, function( i, attr ) {
1817 if ( element.getAttribute( attr ) )
1818 _this[attr] = element.getAttribute( attr );
1819 } );
1820 // @@todo process duration (for now just strip s) per:
1821 // http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
1822 if ( _this.dur )
1823 _this.dur = smilParseTime( _this.dur );
1824 },
1825 /*
1826 * returns a visual representation of the transition
1827 */
1828 getIconSrc:function( opt ) {
1829 // @@todo support some arguments
1830 return mvTransLib.getTransitionIcon( this.type, this.subtype );
1831 },
1832 getDuration:function() {
1833 return this.dur;
1834 },
1835 // returns the values of supported_attributes:
1836 getAttributeObj:function() {
1837 var elmObj = { };
1838 for ( var i in this.supported_attributes ) {
1839 var attr = this.supported_attributes[i];
1840 if ( this[ attr ] )
1841 elmObj[ attr ] = this[ attr ];
1842 }
1843 return elmObj;
1844 },
1845 /*
1846 * the main animation loop called every MV_ANIMATION_CB_RATE or 34ms ~around 30frames per second~
1847 */
1848 run_transition:function() {
1849 // js_log('f:run_transition:' + this.interValCount);
1850
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;
1855 }
1856
1857 // }else{
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;
1862 // }
1863 // }
1864
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;
1871 }
1872 // set percentage include diffrence of currentTime to prev_curTime
1873 // ie updated in-between currentTime updates)
1874
1875 if ( this.transAttrType == 'transIn' )
1876 var percentage = ( this.pClip.embed.currentTime +
1877 ( ( this.interValCount * MV_ANIMATION_CB_RATE ) / 1000 )
1878 ) / this.dur ;
1879
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 )
1884 ) / this.dur ;
1885
1886 /*js_log('percentage = ct:'+this.pClip.embed.currentTime + ' + ic:'+this.interValCount +' * cb:'+MV_ANIMATION_CB_RATE +
1887 ' / ' + this.dur + ' = ' + percentage );
1888 */
1889
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 );
1893
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 )
1899 return true;
1900 }
1901
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 );
1910 }
1911 } else {
1912 clearInterval( this.timerId );
1913 }
1914 return true;
1915 },
1916 clone :function() {
1917 var cObj = new this.constructor();
1918 for ( var i in this )
1919 cObj[i] = this[i];
1920 return cObj;
1921 }
1922 }
1923
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 = {
1928 transitions: { },
1929 doParse:function() {
1930 var _this = this;
1931 js_log( 'f:doParse smilPlaylist' );
1932 // @@todo get/parse meta that we are interested in:
1933 var meta_tags = this.data.getElementsByTagName( 'meta' );
1934 var metaNames = {
1935 'title':'',
1936 'interface_url':"",
1937 'linkback':"",
1938 'mTitle':"",
1939 'mTalk':"",
1940 'mTouchedTime':""
1941 };
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' );
1946 }
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;
1951 }
1952 } );
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 );
1958 } else {
1959 js_log( 'skipping transition: (missing id) ' + trans_elm );
1960 }
1961 } );
1962 js_log( 'loaded transitions:' + _this.transitions.length );
1963
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 ) {
1967 var inx = 0;
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 ) ) {
1974 inx++;
1975 }
1976 }
1977 } );
1978 } );
1979 js_log( "done proc seq tags" );
1980 return true;
1981 },
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] );
1989 }
1990 this.tryAddMedia( mediaElement, order, track_id );
1991 },
1992 tryAddMedia:function( mediaElement, order, track_id ) {
1993 js_log( 'SMIL:tryAddMedia:' + mediaElement );
1994
1995 var _this = this;
1996 // Set up basic mvSMILClip send it the mediaElemnt & mvClip init:
1997 var clipObj = { };
1998 var cConfig = {
1999 "id" : 'p_' + _this.id + '_c_' + order,
2000 "pp" : this, // set the parent playlist object pointer
2001 "order" : order
2002 };
2003 var clipObj = new mvSMILClip( mediaElement, cConfig );
2004
2005 // set optional params track
2006 if ( typeof track_id != 'undefined' )
2007 clipObj["track_id"] = track_id;
2008
2009
2010 if ( clipObj ) {
2011 // set up embed:
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 );
2017
2018 return true;
2019 }
2020 return false;
2021 }
2022 }
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(
2028 'src',
2029 'type',
2030 'region',
2031 'transIn',
2032 'transOut',
2033 'fill',
2034 'dur',
2035 'title',
2036 // some custom attributes:
2037 'uri',
2038 'durationHint',
2039 'poster'
2040 );
2041 /* extension to mvClip to support smil properties */
2042 var mvSMILClip = function( sClipElm, mvClipInit ) {
2043 return this.init( sClipElm, mvClipInit );
2044 }
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 ) {
2050 _this = this;
2051 this.params = { };
2052 // make new mvCLip with ClipInit vals
2053 var myMvClip = new mvClip( mvClipInit );
2054 // inherit mvClip
2055 for ( var method in myMvClip ) {
2056 if ( typeof this[method] != 'undefined' ) {
2057 this['parent_' + method] = myMvClip[method];
2058 } else {
2059 this[method] = myMvClip[method];
2060 }
2061 }
2062
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 );
2068 }
2069 }
2070 this['tagName'] = sClipElm.tagName;
2071
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() );
2075
2076 if ( sClipElm.firstChild ) {
2077 this['wholeText'] = sClipElm.firstChild.nodeValue;
2078 js_log( "SET wholeText for: " + this['tagName'] + ' ' + this['wholeText'] );
2079 }
2080 // debugger;
2081 // mv_embed specific property:
2082 if ( $j( sClipElm ).attr( 'poster' ) )
2083 this['img'] = $j( sClipElm ).attr( 'poster' );
2084
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';
2091 }
2092
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';
2097 }
2098 // parse duration / begin times:
2099 if ( this.dur )
2100 this.dur = smilParseTime( this.dur );
2101
2102 // parse the media duration hint ( the source media length)
2103 if ( this.durationHint )
2104 this.durationHint = smilParseTime( this.durationHint );
2105
2106 // conform type to vido/ogg:
2107 if ( this.type == 'application/ogg' )
2108 this.type = 'video/ogg'; // conform to 'video/ogg' type
2109
2110 // if unset type and we have innerHTML assume text/html type
2111 if ( !this.type && this.wholeText ) {
2112 this.type = 'text/html';
2113 }
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;
2119 }
2120 }
2121 return this;
2122 },
2123 /**
2124 * Returns the values of supported_attributes:
2125 */
2126 getAttributeObj:function() {
2127 var elmObj = { };
2128 for ( var i = 0; i < mv_smil_ref_supported_attributes.length; i++ ) {
2129 var attr = mv_smil_ref_supported_attributes[i];
2130 if ( this[attr] )
2131 elmObj[ attr ] = this[attr];
2132 }
2133 return elmObj;
2134 },
2135 /*
2136 * getDuration
2137 * @returns duration in int
2138 */
2139 getDuration:function() {
2140 // check for smil dur:
2141 if ( this.dur )
2142 return this.dur;
2143 return this.embed.getDuration();
2144 },
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();
2151
2152 // js_log("getSoloDuration:: td: " + this.getDuration() + ' sd:' + fulldur);
2153 return fulldur;
2154 },
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();
2161 }
2162 }
2163 /*
2164 * takes an input
2165 * @time_str input time string
2166 * returns time in seconds
2167 *
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
2171 */
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 );
2177 } else {
2178 // assume 34s secconds representation
2179 return parseInt( time_str.replace( 's', '' ) );
2180 }
2181 }
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() {
2184 return this.init();
2185 }
2186 activeClipList.prototype = {
2187 init:function() {
2188 this.clipList = new Array();
2189 },
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:
2195 return false;
2196 }
2197 this.clipList.push( clip );
2198 return true;
2199 },
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 );
2205 return true;
2206 }
2207 }
2208 return false;
2209 },
2210 getClipList:function() {
2211 return this.clipList;
2212 }
2213 }
2214 var trackObj = function( iObj ) {
2215 return this.init( iObj );
2216 }
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(
2223 'title',
2224 'desc',
2225 'inx'
2226 ),
2227 disp_mode:'timeline_thumb',
2228 init : function( iObj ) {
2229 if ( !iObj )
2230 iObj = { };
2231 // make sure clips is new:
2232 this.clips = new Array();
2233
2234 var _this = this;
2235 $j.each( this.supported_attributes, function( i, attr ) {
2236 if ( iObj[attr] )
2237 _this[attr] = iObj[attr];
2238 } );
2239 },
2240 // returns the values of supported_attributes:
2241 getAttributeObj:function() {
2242 var elmObj = { };
2243 for ( var i in this.supported_attributes ) {
2244 var attr = this.supported_attributes[i];
2245 if ( this[attr] )
2246 elmObj[ attr ] = this[attr];
2247 }
2248 return elmObj;
2249 },
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 );
2259 },
2260 getClip:function( inx ) {
2261 if ( !this.clips[inx] )
2262 return false;
2263 return this.clips[inx];
2264 },
2265 reOrderClips:function() {
2266 for ( var k in this.clips ) {
2267 this.clips[k].order = k;
2268 }
2269 },
2270 getClipCount:function() {
2271 return this.clips.length;
2272 },
2273 inheritEmbedObj: function() {
2274 $j.each( this.clips, function( i, clip ) {
2275 clip.embed.inheritEmbedObj();
2276 } );
2277 }
2278 };
2279
2280 /* utility functions
2281 * (could be combined with other stuff)
2282 */
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
2290 o = oParent
2291 }
2292 o = document.getElementById( objectId );
2293 oTop = o.offsetTop;
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
2297 o = oParent
2298 }
2299 return { x:oLeft, y:oTop };
2300 }