some sequence / playlist / add-media bug fixes
[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 mv_embed:
6 * http://metavid.org/wiki/index.php/Mv_embed
7 */
8 var mv_default_playlist_attributes = {
9 //playlist attributes :
10 "id":null,
11 "title":null,
12 "width":400,
13 "height":300,
14 "desc":'',
15 "controls":true,
16 //playlist user controlled features
17 "linkback":null,
18 "src":null,
19 "embed_link":true,
20
21 //enable sequencer? (only display top frame no navigation or accompanying text
22 "sequencer":false
23 }
24 //the call back rate for animations and internal timers in ms: 33 is about 30 frames a second:
25 var MV_ANIMATION_CB_RATE = 33;
26
27 //globals:
28 //10 possible colors for clips: (can be in hexadecimal)
29 var mv_clip_colors = new Array('aqua', 'blue', 'fuchsia', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red');
30 //the base url for requesting stream metadata
31 if(typeof wgServer=='undefined'){
32 var defaultMetaDataProvider = 'http://metavid.org/overlay/archive_browser/export_cmml?stream_name=';
33 }else{
34 var defaultMetaDataProvider = wgServer + wgScript + '?title=Special:MvExportStream&feed_format=roe&stream_name=';
35 }
36 /*
37 * The playlist Object implements ~most~ of embedVideo but we don't inherit (other than to use the control builder)
38 * because pretty much every function has to be changed for the playlist context
39 */
40 var mvPlayList = function(element) {
41 return this.init(element);
42 };
43 //set up the mvPlaylist object
44 mvPlayList.prototype = {
45 instanceOf:'mvPlayList',
46 pl_duration:null,
47 update_tl_hook:null,
48 clip_ready_count:0,
49 cur_clip:null,
50 start_clip:null,
51 start_clip_src:null,
52 disp_play_head:null,
53 userSlide:false,
54 loading:true,
55 loading_external_data:true, //if we are loading external data (set to loading by default)
56
57 activeClipList:null,
58 playlist_buffer_time: 20, // how many seconds of future clips we should buffer
59
60 interface_url:null, //the interface url
61 tracks:{},
62 default_track:null, // the default track to add clips to.
63 //the layout for the playlist object
64 pl_layout : {
65 seq_title:.1,
66 clip_desc:.63, //displays the clip description
67 clip_aspect:1.33, // 4/3 video aspect ratio
68 seq:.25, //display clip thumbnails
69 seq_thumb:.25, //size for thumbnails (same as seq by default)
70 seq_nav:0, //for a nav bar at the base (currently disabled)
71 //some pl_layout info:
72 title_bar_height:17,
73 control_height:29
74 },
75 //embed object type support system;
76 supports: {
77 'play_head':true,
78 'pause':true,
79 'fullscreen':false,
80 'time_display':true,
81 'volume_control':true,
82
83 'overlays':true,
84 'playlist_swap_loader':true //if the object supports playlist functions
85 },
86 init : function(element){
87 js_log('mvPlayList:init:');
88 this.tracks={};
89 this.default_track=null;
90
91 this.activeClipList = new activeClipList();
92 //add default track & default track pointer:
93 this.tracks[0]= new trackObj({'inx':0});
94 this.default_track = this.tracks[0];
95
96 //get all the attributes:
97 for(var attr in mv_default_playlist_attributes){
98 if( element.getAttribute(attr) ){
99 this[attr]=element.getAttribute(attr);
100 //js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+'elm_val:' + element.getAttribute(attr) + "\n (set by elm)");
101 }else{
102 this[attr]=mv_default_playlist_attributes[attr];
103 //js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+ 'elm_val:' + element.getAttribute(attr) + "\n (set by attr)");
104 }
105 }
106 //make sure height and width are int:
107 this.width =parseInt(this.width);
108 this.height=parseInt(this.height);
109
110 //if style is set override width and height
111 if(element.style.width)this.width = parseInt(element.style.width.replace('px',''));
112 if(element.style.height)this.height = parseInt(element.style.height.replace('px',''));
113
114 //if controls=false hide the title and the controls:
115 if(this.controls===false){
116 this.pl_layout.control_height=0;
117 this.pl_layout.title_bar_height=0;
118 }
119 },
120 //the element has now been swapped into the dom:
121 on_dom_swap:function(){
122 js_log('pl: dom swap');
123 //get and load the html:
124 this.getHTML();
125 },
126 //run inheritEmbedObj on every clip (we have changed the playback method)
127 inheritEmbedObj:function(){
128 $j.each(this.tracks, function(i,track){
129 track.inheritEmbedObj();
130 });
131 },
132 doOptionsHTML:function(){
133 //grab "options" use current clip:
134 this.cur_clip.embed.doOptionsHTML();
135 },
136 //pulls up the video editor inline
137 doEditor:function(){
138 //black out the page:
139 //$j('body').append('<div id="ui-widget-overlay"/> <div id="modalbox" class="ui-widget ui-widget-content ui-corner-all modal_editor">' );
140 $j('body').append('<div class="ui-widget-overlay" style="width: 100%; height: 100%px; z-index: 10;"></div>');
141 $j('body').append('<div id="sequencer_target" style="z-index:11;position:fixed;top:10px;left:10px;right:10px;bottom:10px;" ' +
142 'class="ui-widget ui-widget-content ui-corner-all"></div>');
143
144 //@@todo clone the playlist (for faster startup)
145 /*
146 * var this_plObj_Clone = $j('#'+this.id).get(0).cloneNode(true);
147 * this_plObj_Clone.sequencer=true;
148 * this_plObj_Clone.id= 'seq_plobj';
149 * debugger;
150 */
151 //load sequencer:
152 $j("#sequencer_target").sequencer({
153 "mv_pl_src" : this.src
154 });
155
156 },
157 selectPlaybackMethod:function(){
158 this.cur_clip.embed.selectPlaybackMethod();
159 },
160 closeDisplayedHTML:function(){
161 this.cur_clip.embed.closeDisplayedHTML();
162 },
163 showVideoDownload:function(){
164 this.cur_clip.embed.showVideoDownload();
165 },
166 showEmbedCode:function(){
167 var embed_code = '&lt;script type=&quot;text/javascript&quot; '+
168 'src=&quot;'+mv_embed_path+'mv_embed.js&quot;&gt;&lt;/script&gt '+"\n" +
169 '&lt;playlist id=&quot;'+this.id+'&quot; ';
170 if(this.src){
171 embed_code+='src=&quot;'+this.src+'&quot; /&gt;';
172 }else{
173 embed_code+='&gt;'+"\n";
174 embed_code+= this.data.htmlEntities();
175 embed_code+='&lt;playlist/&gt;';
176 }
177 this.cur_clip.embed.showEmbedCode(embed_code);
178 },
179 getPlaylist:function(){
180 js_log("f:getPlaylist: " + this.srcType );
181 //@@todo lazy load plLib
182 eval('var plObj = '+this.srcType+'Playlist;');
183 //import methods from the plObj to this
184 for(var method in plObj){
185 //js parent preservation for local overwritten methods
186 if(this[method])this['parent_' + method] = this[method];
187 this[method]=plObj[method];
188 js_log('inherit:'+ method);
189 }
190
191 if(typeof this.doParse != 'function'){
192 js_log('error: method doParse not found in plObj'+ this.srcType);
193 return false;
194 }
195
196 if(typeof this.doParse == 'function'){
197 if( this.doParse() ){
198 this.doWhenParseDone();
199 }else{
200 js_log("error: failed to parse playlist");
201 return false;
202 //error or parse needs to do ajax requests
203 }
204 }
205 },
206 doNativeWarningCheck:function(){
207 var clip = this.default_track.clips[0];
208 return clip.embed.doNativeWarningCheck();
209 },
210 doWhenParseDone:function(){
211 js_log('f:doWhenParseDone');
212 //do additional init for clips:
213 var _this = this;
214 var error=false;
215 _this.clip_ready_count=0;
216 for( var i in this.default_track.clips ){
217 var clip = this.default_track.clips[i];
218 if(clip.embed.load_error){
219 var error = clip.embed.load_error;
220 //break on any clip we can't playback:
221 break;
222 }
223 if( clip.embed.ready_to_play ){
224 _this.clip_ready_count++;
225 continue;
226 }
227 //js_log('clip sources count: '+ clip.embed.media_element.sources.length);
228 clip.embed.on_dom_swap();
229 if( clip.embed.loading_external_data==false &&
230 clip.embed.init_with_sources_loadedDone==false){
231 clip.embed.init_with_sources_loaded();
232 }
233 }
234
235 //@@todo for some plugins we have to conform types of clips
236 // ie vlc can play flash _followed_by_ ogg _followed_by_ whatever
237 // but
238 // native ff 3.1a2 can only play ogg
239 if( error){
240 this.load_error=error;
241 this.is_ready=false;
242 }else if( _this.clip_ready_count == _this.getClipCount() ){
243 js_log("done init all clips: " + _this.clip_ready_count + ' = ' + _this.getClipCount());
244 this.doWhenClipLoadDone();
245 }else{
246 js_log("only "+ _this.clip_ready_count +" clips done, scheduling callback:");
247 var doParseDoneCheck = function(){
248 _this.doWhenParseDone();
249 }
250 if( !mvJsLoader.load_error ) //re-issue request if no load error:
251 setTimeout(doParseDoneCheck, 100);
252 }
253 },
254 doWhenClipLoadDone:function(){
255 js_log('mvPlaylist:doWhenClipLoadDone');
256 this.ready_to_play = true;
257 this.loading = false;
258 this.getHTML();
259 },
260 getDuration:function( regen ){
261 //js_log("GET PL DURRATION for : "+ this.tracks[this.default_track_id].clips.length + 'clips');
262 if(!regen && this.pl_duration)
263 return this.pl_duration;
264
265 var durSum=0;
266 $j.each( this.default_track.clips, function( i, clip ){
267 if( clip.embed ){
268 clip.dur_offset = durSum;
269 //only calculate the solo Duration if a smil clip that could contain a transition:
270 if( clip.instanceOf == 'mvSMILClip' ){
271 //don't include transition time (for playlist_swap_loader compatible clips)
272 durSum += clip.getSoloDuration();
273 }else{
274 durSum += clip.getDuration();
275 }
276 }else{
277 js_log("ERROR: clip " +clip.id + " not ready");
278 }
279 });
280 this.pl_duration=durSum;
281 //js_log("return dur: " + this.pl_duration);
282 return this.pl_duration;
283 },
284 getTimeReq:function(){
285 //playlist does not really support time request atm ( in theory in the future we could embed playlists with temporal urls)
286 return '0:0:0/' + seconds2npt( this.getDuration() );
287 },
288 getDataSource:function(){
289 js_log("f:getDataSource "+ this.src);
290 //determine the type / first is it m3u or xml?
291 var pl_parent = this;
292 this.makeURLAbsolute();
293 if(this.src!=null){
294 do_request(this.src, function(data){
295 pl_parent.data=data;
296 pl_parent.getSourceType();
297 });
298 }
299 },
300 getSourceType:function(){
301 js_log('data type of: '+ this.src + ' = ' + typeof (this.data) + "\n"+ this.data);
302 this.srcType =null;
303 //if not external use different detection matrix
304 if(this.loading_external_data){
305 if( typeof this.data == 'object' ){
306 js_log('object');
307 //object assume xml (either xspf or rss)
308 plElm = this.data.getElementsByTagName('playlist')[0];
309 if( plElm ){
310 if(plElm.getAttribute('xmlns')=='http://xspf.org/ns/0/'){
311 this.srcType ='xspf';
312 }
313 }
314 //check itunes style rss "items"
315 rssElm = this.data.getElementsByTagName('rss')[0];
316 if(rssElm){
317 if(rssElm.getAttribute('xmlns:itunes')=='http://www.itunes.com/dtds/podcast-1.0.dtd'){
318 this.srcType='itunes';
319 }
320 }
321 //check for smil tag:
322 smilElm = this.data.getElementsByTagName('smil')[0];
323 if(smilElm){
324 //don't check dtd yet.. (have not defined the smil subset)
325 this.srcType='smil';
326 }
327 }else if(typeof this.data == 'string'){
328 js_log('String');
329 //look at the first line:
330 var first_line = this.data.substring(0, this.data.indexOf("\n"));
331 js_log('first line: '+ first_line);
332 //string
333 if(first_line.indexOf('#EXTM3U')!=-1){
334 this.srcType = 'm3u';
335 }else if(first_line.indexOf('<smil')!=-1){
336 //@@todo parse string
337 this.srcType = 'smil';
338 }
339 }
340 }
341 if(this.srcType){
342 js_log('is of type:'+ this.srcType);
343 this.getPlaylist();
344 }else{
345 //unknown playlist type
346 js_log('unknown playlist type?');
347 if(this.src){
348 this.innerHTML= 'error: unknown playlist type at url:<br> ' + this.src;
349 }else{
350 this.innerHTML='error: unset src or unknown inline playlist data<br>';
351 }
352 }
353 },
354 //simple function to make a path into an absolute url if its not already
355 makeURLAbsolute:function(){
356 if(this.src){
357 if(this.src.indexOf('://')==-1){
358 var purl = parseUri(document.URL);
359 if(this.src.charAt(0)=='/'){
360 this.src = purl.protocol +'://'+ purl.host + this.src;
361 }else{
362 this.src= purl.protocol +'://'+ purl.host + purl.directory + this.src;
363 }
364 }
365 }
366 },
367 //set up minimal media_element emulation:
368 media_element:{
369 selected_source:{
370 supports_url_time_encoding:true
371 }
372 },
373 //@@todo needs to update for multi-track clip counts
374 getClipCount:function(){
375 return this.default_track.clips.length;
376 },
377 //},
378 //takes in the playlist
379 // inherits all the properties
380 // swaps in the playlist object html/interface div
381 getHTML:function(){
382 js_log('mvPlaylist:getHTML: loading:' + this.loading);
383 if(this.loading){
384 $j('#'+this.id).html('loading playlist<blink>...</blink>');
385 if( this.loading_external_data ){
386 //load the data source chain of functions (to update the innerHTML)
387 this.getDataSource();
388 }else{
389 //detect datatype and parse directly:
390 this.getSourceType();
391 }
392 }else{
393 //check for empty playlist otherwise renderDisplay:
394 if(this.default_track.getClipCount()==0){
395 $j(this).html('empty playlist');
396 return ;
397 }else{
398 this.renderDisplay();
399 }
400 }
401 },
402 renderDisplay:function(){
403 js_log('mvPlaylist:renderDisplay:: track length: ' +this.default_track.getClipCount() );''
404
405 var _this=this;
406 //setup layout for title and dc_ clip container
407
408
409 //add the playlist controls:
410
411 //append container and videoPlayer;
412 $j(this).html('<div id="dc_'+this.id+'" style="width:'+this.width+'px;' +
413 'height:'+(this.height+this.pl_layout.title_bar_height + this.pl_layout.control_height)+'px;position:relative;">' +
414 '</div>');
415 if(this.controls==true){
416 //append title & controler:
417 $j('#dc_'+_this.id).append(
418 '<div style="font-size:13px;border:solid thin;width:'+this.width+'px;" id="ptitle_'+this.id+'"></div>' +
419 '<div class="videoPlayer" style="position:absolute;top:'+(_this.height+_this.pl_layout.title_bar_height+4)+'px">' +
420 '<div id="mv_embedded_controls_'+_this.id+'" class="ui-widget ui-corner-bottom ui-state-default controls" '+
421 'style="width:' + _this.width + 'px" >' +
422 _this.getControlsHTML() +
423 '</div>'+
424 '</div>'
425 );
426
427 //add the play button:
428 $j('#dc_'+_this.id).append(
429 this.cur_clip.embed.getPlayButton()
430 );
431 //once the controls are in the DOM add hooks:
432 ctrlBuilder.addControlHooks(this);
433 }else{
434 //just append the video:
435 $j('#dc_'+_this.id).append(
436 '<div class="videoPlayer" style="position:absolute;top:'+(_this.height+_this.pl_layout.title_bar_height+4)+'px"></div>'
437 );
438 }
439 this.setupClipDisplay();
440
441 //update the title and status bar
442 this.updateBaseStatus();
443 },
444 setupClipDisplay:function(){
445 js_log('mvPlaylist:setupClipDisplay:: clip len:'+ this.default_track.clips.length);
446 var _this = this;
447 $j.each(this.default_track.clips, function(i, clip){
448 var cout = '<div class="clip_container cc_" id="clipDesc_'+clip.id+'" '+
449 'style="display:none;position:absolute;text-align: center;width:'+_this.width + 'px;'+
450 'height:'+(_this.height )+'px;'+
451 'top:' + this.title_bar_height + 'px;left:0px;';
452 if(_this.controls){
453 cout+='border:solid thin black;';
454 }
455 cout+='"></div>';
456 $j('#dc_'+_this.id).append( cout );
457 //update the embed html:
458 clip.embed.height=_this.height;
459 clip.embed.width=_this.width;
460 clip.embed.play_button=false;
461
462 clip.embed.getHTML();//get the thubnails for everything
463
464 $j(clip.embed).css({
465 'position':"absolute",
466 'top':"0px",
467 'left':"0px"
468 });
469 if($j('#clipDesc_'+clip.id).length != 0){
470 js_log("should set: #clipDesc_"+clip.id + ' to: ' + $j(clip.embed).html() )
471 $j('#clipDesc_'+clip.id).append( clip.embed );
472 }else{
473 js_log('cound not find: clipDesc_'+clip.id);
474 }
475 });
476 if(this.cur_clip)
477 $j('#clipDesc_'+this.cur_clip.id).css( { display:'inline' } );
478 },
479 updateThumbPerc:function( perc ){
480 //get float seconds:
481 var float_sec = ( this.getDuration() * perc );
482 this.updateThumbTime( float_sec );
483 },
484 updateThumbTime:function( float_sec ){
485 //update display & cur_clip:
486 var pl_sum_time =0;
487 var clip_float_sec=0;
488 //js_log('seeking clip: ');
489 for(var i in this.default_track.clips){
490 var clip = this.default_track.clips[i];
491 if( (clip.getDuration() + pl_sum_time) >= float_sec ){
492 if(this.cur_clip.id != clip.id){
493 $j('#clipDesc_'+this.cur_clip.id).hide();
494 this.cur_clip = clip;
495 $j('#clipDesc_'+this.cur_clip.id).show();
496 }
497 break;
498 }
499 pl_sum_time+=clip.getDuration();
500 }
501
502 //issue thumbnail update request: (if plugin supports it will render out frame
503 // if not then we do a call to the server to get a new jpeg thumbnail
504 this.cur_clip.embed.updateThumbTime( float_sec - pl_sum_time );
505
506 this.cur_clip.embed.currentTime = (float_sec -pl_sum_time) + this.cur_clip.embed.start_offset ;
507 this.cur_clip.embed.seek_time_sec = (float_sec -pl_sum_time );
508
509 //render effects ontop: (handled by doSmilActions)
510 this.doSmilActions( single_line = true );
511 },
512 updateBaseStatus:function(){
513 var _this = this;
514 js_log('f:updateBaseStatus');
515 $j('#ptitle_'+this.id).html(''+
516 '<b>' + this.title + '</b> '+
517 this.getClipCount()+' clips, <i>'+
518 seconds2npt( this.getDuration() ) + '</i>');
519
520 //only show the inline edit button if mediaWiki write API is enabled:
521
522 //should probably be based on if we have a provider api url
523 if( typeof wgEnableWriteAPI != 'undefined'){
524 $j( $j.btnHtml('edit', 'editBtn_'+this.id, 'pencil',
525 {'style':'position:absolute;right:0;;font-size:x-small;height:10px;margin-bottom:0;padding-bottom:7px;padding-top:0;'} )
526 ).click(function(){
527 _this.stop();
528 _this.doEditor();
529 return false;
530 }).appendTo('#ptitle_'+this.id);
531 $j('.editBtn_'+this.id).btnBind();
532 }
533 //render out the dividers on the timeline:
534 this.colorPlayHead();
535 //update status:
536 this.setStatus( '0:0:00/' + seconds2npt( this.getDuration() ) );
537 },
538 /*setStatus override (could call the jquery directly) */
539 setStatus:function(value){
540 $j('#mv_time_'+this.id).html( value );
541 },
542 setSliderValue:function(value){
543 if( this.controls ){
544 //slider is on 1000 scale:
545 var val = parseInt( value *1000 );
546 $j('#mv_play_head_' + this.id).slider('value', val);
547 }
548 },
549 getPlayHeadPos: function(prec_done){
550 var _this = this;
551 if($j('#mv_seeker_'+this.id).length==0){
552 //js_log('no playhead so we can\'t get playhead pos' );
553 return 0;
554 }
555 var track_len = $j('#mv_seeker_'+this.id).css('width').replace(/px/, '');
556 //assume the duration is static and present at .duration during playback
557 var clip_perc = this.cur_clip.embed.duration / this.getDuration();
558 var perc_offset =time_offset = 0;
559 for(var i in this.default_track.clips){
560 var clip = this.default_track.clips[i];
561 if(this.cur_clip.id ==clip.id)break;
562 perc_offset+=(clip.embed.duration / _this.getDuration());
563 time_offset+=clip.embed.duration;
564 }
565 //run any update time line hooks:
566 if(this.update_tl_hook){
567 var cur_time_ms = time_offset + Math.round(this.cur_clip.embed.duration*prec_done);
568 if(typeof update_tl_hook =='function'){
569 this.update_tl_hook(cur_time_ms);
570 }else{
571 //string type passed use eval:
572 eval(this.update_tl_hook+'('+cur_time_ms+');');
573 }
574 }
575
576 //handle offset hack @@todo fix so this is not needed:
577 if(perc_offset > .66)
578 perc_offset+=( 8/track_len );
579 //js_log('perc:'+ perc_offset +' c:'+ clip_perc + '*' + prec_done + ' v:'+(clip_perc*prec_done));
580 return perc_offset + ( clip_perc * prec_done );
581 },
582 //attempts to load the embed object with the playlist
583 loadEmbedPlaylist: function(){
584 //js_log('load playlist');
585 },
586 /** mannages the loading of future clips
587 * called regurally while we are playing clips
588 *
589 * load works like so:
590 * if the current clip is full loaded
591 * load clips untill buffredEndTime < playlist_buffer_time load next
592 *
593 * this won't work so well with time range loading for smil (need to work on that)
594 */
595 loadFutureClips:function(){
596 /*if( this.cur_clip.embed.bufferedPercent == 1){
597 //set the buffer to the currentTime - duration
598 var curBuffredTime = this.cur_clip.getDuration() - this.cur_clip.embed.currentTime;
599
600 if(curBuffredTime < 0)
601 curBuffredTime = 0;
602
603 js_log( "curBuffredTime:: " + curBuffredTime );
604 if( curBuffredTime < this.playlist_buffer_time ){
605 js_log(" we only have " + curBuffredTime + ' buffed but we need: ' + this.playlist_buffer_time);
606
607 for(var inx = this.cur_clip.order + 1; inx < this.default_track.clips.length; inx++ ){
608 var cClip = this.default_track.getClip( inx );
609
610 //check if the clip is already loaded (add its duration)
611 if( cClip.embed.bufferedPercent == 1){
612 curBuffredTime += cClip.embed.getDuration();
613 }
614 //check if we still have to load a resource:
615 if( curBuffredTime < this.playlist_buffer_time ){
616 //issue the load request
617 if( cClip.embed.networkState==0 ){
618 cClip.embed.load();
619 }
620 break; //check back next time
621 }
622 }
623 }
624 }*/
625 },
626 //called to play the next clip if done call onClipDone
627 playNext: function(){
628 //advance the playhead to the next clip
629 var next_clip = this.getNextClip();
630
631 if( !next_clip ){
632 js_log('play next with no next clip... must be done:');
633 this.onClipDone();
634 return ;
635 }
636 //@@todo where the plugin supports pre_loading future clips and manage that in javascript
637 //stop current clip
638 this.cur_clip.embed.stop();
639
640 this.updateCurrentClip(next_clip);
641
642 this.cur_clip.embed.play();
643 },
644 onClipDone:function(){
645 js_log("pl onClipDone");
646 this.cur_clip.embed.stop();
647 },
648 updateCurrentClip:function( new_clip ){
649 js_log('f:updateCurrentClip:'+new_clip.id);
650 //make sure we are not switching to the current
651 if( this.cur_clip.id == new_clip.id )
652 return js_log('trying to updateCurrentClip to same clip');
653
654 //keep the active play clip in sync (stop the other clip)
655 if( this.cur_clip ){
656 if( !this.cur_clip.embed.isStoped() )
657 this.cur_clip.embed.stop();
658 this.activeClipList.remove(this.cur_clip )
659 }
660
661 this.activeClipList.add( new_clip );
662
663 //do swap:
664 $j('#clipDesc_'+this.cur_clip.id).hide();
665 this.cur_clip=new_clip;
666 $j('#clipDesc_'+this.cur_clip.id).show();
667 //update the playhead:
668 this.setSliderValue( this.cur_clip.dur_offset / this.getDuration() );
669 },
670 playPrev: function(){
671 //advance the playhead to the previous clip
672 var prev_clip = this.getPrevClip();
673 if(!prev_clip){
674 js_log("tried to play PrevClip with no prev Clip.. setting prev_clip to start clip");
675 prev_clip = this.start_clip;
676 }
677 //@@todo we could do something fancy like use playlist for sets of clips where supported.
678 // or in cases where the player nativly supports the playlist format we can just pass it in (ie m3u or xspf)
679 if(this.cur_clip.embed.supports['playlist_swap_loader']){
680 //where the plugin supports pre_loading future clips and manage that in javascript
681 //pause current clip
682 this.cur_clip.embed.pause();
683 //do swap:
684 this.updateCurrentClip(prev_clip);
685 this.cur_clip.embed.play();
686 }else{
687 js_log('do prev hard embed swap');
688 this.switchPlayingClip(prev_clip);
689 }
690 },
691 switchPlayingClip:function(new_clip){
692 //swap out the existing embed code for next clip embed code
693 $j('#mv_ebct_'+this.id).empty();
694 new_clip.embed.width=this.width;
695 new_clip.embed.height=this.height;
696 //js_log('set embed to: '+ new_clip.embed.getEmbedObj());
697 $j('#mv_ebct_'+this.id).html( new_clip.embed.getEmbedObj() );
698 this.cur_clip=new_clip;
699 //run js code:
700 this.cur_clip.embed.pe_postEmbedJS();
701 },
702 //playlist play
703 play: function(){
704 var _this=this;
705 //js_log('pl play');
706 //hide the playlist play button:
707 $j('#big_play_link_'+this.id).hide();
708
709 //un-pause if paused:
710 if(this.paused)
711 this.paused=false;
712
713 //update the control:
714 this.start_clip = this.cur_clip;
715 this.start_clip_src= this.cur_clip.src;
716
717 if(this.cur_clip.embed.supports['playlist_swap_loader'] ){
718 //set the cur_clip to active
719 this.activeClipList.add(this.cur_clip);
720
721 //navtive support:
722 // * pre-loads clips
723 // * mv_playlist smil extension, manages transitions animations overlays etc.
724 //js_log('clip obj supports playlist swap_loader (ie playlist controlled playback)');
725 //@@todo pre-load each clip:
726 //play all active clips (playlist_swap_loader can have more than one clip active)
727 $j.each(this.activeClipList.getClipList(), function(inx, clip){
728 clip.embed.play();
729 });
730 }else if(this.cur_clip.embed.supports['playlist_driver']){
731 //js_log('playlist_driver');
732 //embedObject is feed the playlist info directly and manages next/prev
733 this.cur_clip.embed.playMovieAt( this.cur_clip.order );
734 }else{
735 //not much playlist support just play the first clip:
736 //js_log('basic play');
737 //play cur_clip
738 this.cur_clip.embed.play();
739 }
740 //start up the playlist monitor
741 this.monitor();
742 },
743 /*
744 * the load function loads all the clips in order
745 */
746 load:function(){
747 //do nothing right now)
748 },
749 toggleMute:function(){
750 this.cur_clip.embed.toggleMute();
751 },
752 pause:function(){
753 //js_log('f:pause: playlist');
754 var ct = new Date();
755 this.pauseTime = this.currentTime;
756 this.paused=true;
757 //js_log('pause time: '+ this.pauseTime + ' call embed pause:');
758
759 //pause all the active clips:
760 $j.each(this.activeClipList.getClipList(), function(inx, clip){
761 clip.embed.pause();
762 });
763 },
764 //@@todo mute across all child clips:
765 toggleMute:function(){
766 var this_id = (this.pc!=null)?this.pc.pp.id:this.id;
767 if(this.muted){
768 this.muted=false;
769 $j('#volume_control_'+this_id + ' span').removeClass('ui-icon-volume-off').addClass('ui-icon-volume-on');
770 $j('#volume_bar_'+this_id).slider('value', 100);
771 this.updateVolumen(1);
772 }else{
773 this.muted=true;
774 $j('#volume_control_'+this_id + ' span').removeClass('ui-icon-volume-on').addClass('ui-icon-volume-off');
775 $j('#volume_bar_'+this_id).slider('value', 0);
776 this.updateVolumen(0);
777 }
778 js_log('f:toggleMute::' + this.muted);
779 },
780 updateVolumen:function(perc){
781 js_log('update volume not supported with current playback type');
782 },
783 fullscreen:function(){
784 this.cur_clip.embed.fullscreen();
785 },
786 //playlist stops playback for the current clip (and resets state for start clips)
787 stop:function(){
788 var _this = this;
789 /*js_log("pl stop:"+ this.start_clip.id + ' c:'+this.cur_clip.id);
790 //if start clip
791 if(this.start_clip.id!=this.cur_clip.id){
792 //restore clipDesc visibility & hide desc for start clip:
793 $j('#clipDesc_'+this.start_clip.id).html('');
794 this.start_clip.getDetail();
795 $j('#clipDesc_'+this.start_clip.id).css({display:'none'});
796 this.start_clip.setBaseEmbedDim(this.start_clip.embed);
797 //equivalent of base stop
798 $j('#'+this.start_clip.embed.id).html(this.start_clip.embed.getThumbnailHTML());
799 this.start_clip.embed.thumbnail_disp=true;
800 }
801 //empty the play-back container
802 $j('#mv_ebct_'+this.id).empty();*/
803
804 //stop all the clips: monitor:
805 window.clearInterval( this.smil_monitorTimerId );
806 /*for (var i=0;i<this.clips.length;i++){
807 var clip = this.clips[i];
808 if(clip){
809 clip.embed.stop();
810 $j('#clipDesc_'+clip.id).hide();
811 }
812 }*/
813 //stop, hide and remove all active clips:
814 $j.each(this.activeClipList.getClipList(), function(inx, clip){
815 if(clip){
816 clip.embed.stop();
817 $j('#clipDesc_'+clip.id).hide();
818 _this.activeClipList.remove(clip);
819 }
820 });
821 //set the current clip to the first clip:
822 if(this.start_clip){
823 this.cur_clip = this.start_clip;
824 //display the first clip thumb:
825 this.cur_clip.embed.stop();
826 //hide other clips:
827 $j('#'+this.id+' .clip_container').hide();
828 //show the first/current clip:
829 $j('#clipDesc_'+this.cur_clip.id).show();
830 }
831 //reset the currentTime:
832 this.currentTime = 0;
833 //rest the sldier
834 this.setSliderValue( 0 );
835 //FIXME still some issues with "stoping" and reseting the playlist
836 },
837 doSeek:function(v){
838 js_log('pl:doSeek:' + v + ' sts:' + this.seek_time_sec );
839 var _this = this;
840 var prevClip=null;
841
842 //jump to the clip in the current percent.
843 var perc_offset=0;
844 var next_perc_offset=0;
845 for(var i in _this.default_track.clips){
846 var clip = _this.default_track.clips[i];
847 next_perc_offset+=( clip.getDuration() / _this.getDuration()) ;
848 //js_log('on ' + clip.getDuration() +' next_perc_offset:'+ next_perc_offset);
849 if( next_perc_offset > v ){
850 //pass along the relative percentage to embed object:
851 //js_log('seek:'+ v +' - '+perc_offset + ') / (' + next_perc_offset +' - '+ perc_offset);
852 var relative_perc = (v -perc_offset) / (next_perc_offset - perc_offset);
853 //update the current clip:
854 _this.updateCurrentClip( clip );
855
856 //update the clip relative seek_time_sec
857 _this.cur_clip.embed.doSeek( relative_perc );
858 this.play();
859 return '';
860 }
861 perc_offset = next_perc_offset;
862 }
863 },
864 setCurrentTime: function(pos, callback){
865 js_log('pl:setCurrentTime:' + pos + ' sts:' + this.seek_time_sec );
866 var _this = this;
867 var prevClip=null;
868
869 //jump to the clip at pos
870 var currentOffset = 0;
871 var nextTime = 0;
872 for (var i in _this.default_track.clips) {
873 var clip = _this.default_track.clips[i];
874 nextTime = clip.getDuration();
875 if (currentOffset + nextTime > pos) {
876 //update the clip relative seek_time_sec
877 clipTime = pos - currentOffset;
878 if (_this.cur_clip.id != clip.id) {
879 _this.updateCurrentClip( clip );
880 }
881 _this.cur_clip.embed.setCurrentTime(clipTime, function(){
882 if(callback)
883 callback();
884 });
885 _this.currentTime = pos;
886 _this.doSmilActions();
887 }
888 currentOffset += nextTime;
889 }
890 },
891 //gets playlist controls large control height for sporting
892 //next prev button and more status display
893 getControlsHTML:function(){
894 //get controls from current clip (add some playlist specific controls:
895 return ctrlBuilder.getControls( this );
896 },
897 //ads colors/dividers between tracks
898 colorPlayHead: function(){
899 var _this = this;
900
901 if( !_this.mv_seeker_width)
902 _this.mv_seeker_width = $j('#mv_play_head_'+_this.id).width();
903
904 if( !_this.track_len )
905 _this.track_len = $j('#mv_play_head_'+_this.id).width();
906
907 //total duration:
908 var pl_duration = _this.getDuration();
909
910 var cur_pixle=0;
911 //set up _this
912
913 //js_log("do play head total dur: "+pl_duration );
914 $j.each(this.default_track.clips, function(i, clip){
915 //(use getSoloDuration to not include transitions and such)
916 var perc = ( clip.getSoloDuration() / pl_duration );
917 var pwidth = Math.round( perc * _this.track_len);
918 //js_log('pstatus:c:'+ clip.getDuration() + ' of '+ pl_duration+' %:' + perc + ' width: '+ pwidth + ' of total: ' + _this.track_len);
919 //var pwidth = Math.round( perc * _this.track_len - (_this.mv_seeker_width*perc) );
920
921 //add the buffer child indicator:
922 var barHtml= '<div id="cl_status_' + clip.embed.id + '" class="cl_status" style="' +
923 'left:'+cur_pixle +'px;'+
924 'width:'+pwidth + 'px;';
925 //set left or right border based on track pos
926 barHtml+=( i == _this.default_track.getClipCount()-1 )?
927 'border-left:solid thin black;':
928 'border-right:solid thin black;';
929 barHtml+= 'filter:alpha(opacity=40);'+
930 '-moz-opacity:.40;">';
931
932 barHtml+= ctrlBuilder.getMvBufferHtml();
933
934 barHtml+='</div>';
935
936 //background:#DDD +clip.getColor();
937
938 $j('#mv_play_head_'+_this.id).append(barHtml);
939
940 //js_log('offset:' + cur_pixle +' width:'+pwidth+' add clip'+ clip.id + ' is '+clip.embed.getDuration() +' = ' + perc +' of ' + _this.track_len);
941 cur_pixle+=pwidth;
942 });
943 },
944 //@@todo currently not really in use
945 setUpHover:function(){
946 js_log('Setup Hover');
947 //set up hover for prev,next
948 var th = 50;
949 var tw = th*this.pl_layout.clip_aspect;
950 var _this = this;
951 $j('#mv_prev_link_'+_this.id+',#mv_next_link_'+_this.id).hover(function() {
952 var clip = (this.id=='mv_prev_link_'+_this.id) ? _this.getPrevClip() : _this.getNextClip();
953 if(!clip)
954 return js_log('missing clip for Hover');
955 //get the position of #mv_perv|next_link:
956 var loc = getAbsolutePos(this.id);
957 //js_log('Hover: x:'+loc.x + ' y:' + loc.y + ' :'+clip.img);
958 $j("body").append('<div id="mv_Athub" style="position:absolute;' +
959 'top:'+loc.y+'px;left:'+loc.x+'px;width:'+tw+'px;height:'+th+'px;">'+
960 '<img style="border:solid 2px '+clip.getColor()+';position:absolute;top:0px;left:0px;" width="'+tw+'" height="'+th+'" src="'+clip.img+'"/>'+
961 '</div>');
962 }, function() {
963 $j('#mv_Athub').remove();
964 });
965 },
966 //@@todo we need to move a lot of this track logic like "cur_clip" to the track Obj
967 // and have the playlist just drive the tracks.
968 getNextClip:function( track ){
969 if(!track)
970 track = this.default_track;
971 var tc = parseInt(this.cur_clip.order) + 1;
972 var cat = track;
973 if( tc > track.getClipCount() -1 )
974 return false; // out of range
975
976 return track.getClip( tc );
977 },
978 getPrevClip:function( track ) {
979 if(!track)
980 track = this.default_track;
981 var tc = parseInt(this.cur_clip.order) - 1;
982 if( tc < 0 )
983 return false;
984 return track.getClip( tc );
985 },
986 /*
987 * generic add Clip to ~default~ track
988 */
989 addCliptoTrack: function(clipObj, pos){
990 if( typeof clipObj['track_id'] =='undefined'){
991 var track = this.default_track;
992 }else{
993 var track = this.tracks[ clipObj.track_id ]
994 }
995 js_log('add clip:' + clipObj.id +' to track: at:' + pos);
996 //set the first clip to current (maybe deprecated )
997 if(clipObj.order==0){
998 if(!this.cur_clip)this.cur_clip=clipObj;
999 }
1000 track.addClip(clipObj, pos);
1001 },
1002 swapClipDesc: function(req_clipID, callback){
1003 //hide all but the requested
1004 var _this=this;
1005 js_log('r:'+req_clipID+' cur:'+_this.id);
1006 if(req_clipID==_this.cur_clip.id){
1007 js_log('no swap to same clip');
1008 }else{
1009 //fade out clips
1010 req_clip=null;
1011 $j.each(this.default_track.clips, function(i, clip){
1012 if(clip.id!=req_clipID){
1013 //fade out if display!=none already
1014 if($j('#clipDesc_'+clip.id).css('display')!='none'){
1015 $j('#clipDesc_'+clip.id).fadeOut("slow");
1016 }
1017 }else{
1018 req_clip =clip;
1019 }
1020 });
1021 //fade in requested clip *and set req_clip to current
1022 $j('#clipDesc_'+req_clipID).fadeIn("slow", function(){
1023 _this.cur_clip = req_clip;
1024 if(callback)
1025 callback();
1026 });
1027 }
1028 },
1029 //this is pretty outdated:
1030 getPLControls: function(){
1031 js_log('getPL cont');
1032 return '<a id="mv_prev_link_'+this.id+'" title="Previus Clip" onclick="document.getElementById(\''+this.id+'\').playPrev();return false;" href="#">'+
1033 getTransparentPng({id:'mv_prev_btn_'+this.id,style:'float:left',width:'27', height:'27', border:"0",
1034 src:mv_skin_img_path + 'vid_prev_sm.png' }) +
1035 '</a>'+
1036 '<a id="mv_next_link_'+this.id+'" title="Next Clip" onclick="document.getElementById(\''+this.id+'\').playNext();return false;" href="#">'+
1037 getTransparentPng({id:'mv_next_btn_'+this.id,style:'float:left',width:'27', height:'27', border:"0",
1038 src:mv_skin_img_path + 'vid_next_sm.png' }) +
1039 '</a>';
1040 },
1041 run_transition: function( clip_inx, trans_type){
1042 if(typeof this.default_track.clips[ clip_inx ][ trans_type ] == 'undefined')
1043 clearInterval( this.default_track.clips[ clip_inx ].timerId );
1044 else
1045 this.default_track.clips[ clip_inx ][ trans_type ].run_transition();
1046 },
1047 playerPixelWidth : function()
1048 {
1049 var player = $j('#mv_embedded_player_'+this.id).get(0);
1050 if(typeof player!='undefined' && player['offsetWidth'])
1051 return player.offsetWidth;
1052 else
1053 return parseInt(this.width);
1054 },
1055 playerPixelHeight : function()
1056 {
1057 var player = $j('#mv_embedded_player_'+this.id).get(0);
1058 if(typeof player!='undefined' && player['offsetHeight'])
1059 return player.offsetHeight;
1060 else
1061 return parseInt(this.height);
1062 }
1063 }
1064
1065 /* Object Stubs:
1066 *
1067 * @videoTrack ... stores clips and layer info
1068 *
1069 * @clip... each clip segment is a clip object.
1070 * */
1071 var mvClip = function(o) {
1072 if(o)
1073 this.init(o);
1074 return this;
1075 };
1076 //set up the mvPlaylist object
1077 mvClip.prototype = {
1078 id:null, //clip id
1079 pp:null, // parent playlist
1080 order:null, //the order/array key for the current clip
1081 src:null,
1082 info:null,
1083 title:null,
1084 mvclip:null,
1085 type:null,
1086 img:null,
1087 duration:null,
1088 loading:false,
1089 isAnimating:false,
1090 init:function(o){
1091 //init object including pointer to parent
1092 for(var i in o){
1093 this[i]=o[i];
1094 };
1095 js_log('id is: '+ this.id);
1096 },
1097 //setup the embed object:
1098 setUpEmbedObj:function(){
1099 js_log('mvClip:setUpEmbedObj()');
1100 //init:
1101 //debugger;
1102
1103
1104 this.embed=null;
1105 //js_log('setup embed for clip '+ this.id + ':id is a function?');
1106 //set up the pl_mv_embed object:
1107 var init_pl_embed={id:'e_'+this.id,
1108 pc:this, //parent clip
1109 src:this.src
1110 };
1111
1112 this.setBaseEmbedDim(init_pl_embed);
1113 //always display controls for playlists:
1114
1115 //if in sequence mode hide controls / embed links
1116 // init_pl_embed.play_button=false;
1117 init_pl_embed.controls=false;
1118 //if(this.pp.sequencer=='true'){
1119 init_pl_embed.embed_link=null;
1120 init_pl_embed.linkback=null;
1121
1122 if(this.poster)init_pl_embed['thumbnail']=this.poster;
1123
1124 if( this.type )init_pl_embed['type'] = this.type;
1125
1126 this.embed = new PlMvEmbed( init_pl_embed );
1127
1128 //js_log('media Duration:' + this.embed.getDuration() );
1129 //js_log('media element:'+ this.embed.media_element.length);
1130 //js_log('type of embed:' + typeof(this.embed) + ' seq:' + this.pp.sequencer+' pb:'+ this.embed.play_button);
1131 },
1132 doAdjust:function(side, delta){
1133 js_log("f:doAdjust: " + side + ' , ' + delta);
1134 if(this.embed){
1135 if(side=='start'){
1136 var start_offset =parseInt(this.embed.start_offset)+parseInt(delta*-1);
1137 this.embed.updateVideoTime( seconds2npt(start_offset), seconds2npt ( this.embed.start_offset + this.embed.getDuration() ) );
1138 }else if(side=='end'){
1139 var end_offset = parseInt(this.embed.start_offset) + parseInt( this.embed.getDuration() ) + parseInt(delta);
1140 this.embed.updateVideoTime( seconds2npt(this.embed.start_offset), seconds2npt(end_offset) );
1141 }
1142 //update everything:
1143 this.pp.refresh();
1144 /*var base_src = this.src.substr(0,this.src.indexOf('?'));
1145 js_log("delta:"+ delta);
1146 if(side=='start'){
1147 //since we adjust start invert the delta:
1148 var start_offset =parseInt(this.embed.start_offset/1000)+parseInt(delta*-1);
1149 this.src = base_src +'?t='+ seconds2npt(start_offset) +'/'+ this.embed.end_ntp;
1150 }else if(side=='end'){
1151 //put back into seconds for adjustment:
1152 var end_offset = parseInt(this.embed.start_offset/1000) + parseInt(this.embed.duration/1000) + parseInt(delta);
1153 this.src = base_src +'?t='+ this.embed.start_ntp +'/'+ seconds2npt(end_offset);
1154 }
1155 this.embed.updateVideoTime( this.src );
1156 //update values
1157 this.duration = this.embed.getDuration();
1158 this.pp.pl_duration=null;
1159 //update playlist stuff:
1160 this.pp.updateTitle();*/
1161 }
1162 },
1163 getDuration:function(){
1164 if(!this.embed)this.setUpEmbedObj();
1165 return this.embed.getDuration();
1166 },
1167 setBaseEmbedDim:function(o){
1168 if(!o)o=this;
1169 //o.height=Math.round(pl_layout.clip_desc*this.pp.height)-2;//give it some padding:
1170 //o.width=Math.round(o.height*pl_layout.clip_aspect)-2;
1171 o.height= this.pp.height;
1172 o.width = this.pp.width;
1173 },
1174 //output the detail view:
1175 //@@todo
1176 /*getDetail:function(){
1177 //js_log('get detail:' + this.pp.title);
1178 var th=Math.round( this.pl_layout.clip_desc * this.pp.height );
1179 var tw=Math.round( th * this.pl_layout.clip_aspect );
1180
1181 var twDesc = (this.pp.width-tw)-2;
1182
1183 if(this.title==null)
1184 this.title='clip ' + this.order + ' ' +this.pp.title;
1185 if(this.desc==null)
1186 this.desc=this.pp.desc;
1187 //update the embed html:
1188 this.embed.getHTML();
1189
1190 $j(this.embed).css({ 'position':"absolute",'top':"0px", 'left':"0px"});
1191
1192 //js_log('append child to:#clipDesc_'+this.id);
1193 if($j('#clipDesc_'+this.id).get(0)){
1194 $j('#clipDesc_'+this.id).get(0).appendChild(this.embed);
1195
1196 $j('#clipDesc_'+this.id).append(''+
1197 '<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;">'+
1198 '<b>'+this.title+'</b><br>'+
1199 this.desc + '<br>' +
1200 '<b>clip length:</b> '+ seconds2npt( this.embed.getDuration() );
1201 '</div>');
1202 }
1203 },*/
1204 getTitle:function(){
1205 if(typeof this.title == 'string')
1206 return this.title
1207
1208 return 'untitled clip ' + this.order;
1209 },
1210 getClipImg:function(start_offset, size){
1211 js_log('f:getClipImg ' + start_offset + ' s:'+size);
1212 if( !this.img){
1213 return mv_default_thumb_url;
1214 }else{
1215 if(!size && !start_offset){
1216 return this.img;
1217 }else{
1218 //if a metavid image (has request parameters) use size and time args
1219 if(this.img.indexOf('?')!=-1){
1220 js_log('get with offset: '+ start_offset);
1221 var time = seconds2npt( start_offset+ (this.embed.start_offset/1000) );
1222 js_log("time is: " + time);
1223 this.img = this.img.replace(/t\=[^&]*/gi, "t="+time);
1224 if(this.img.indexOf('&size=')!=-1){
1225 this.img = this.img.replace(/size=[^&]*/gi, "size="+size);
1226 }else{
1227 this.img+='&size='+size;
1228 }
1229 }
1230 return this.img;
1231 }
1232 }
1233 },
1234 getColor: function(){
1235 //js_log('get color:'+ num +' : '+ num.toString().substr(num.length-1, 1) + ' : '+colors[ num.toString().substr(num.length-1, 1)] );
1236 var num = this.id.substr( this.id.length-1, 1);
1237 if(!isNaN(num)){
1238 num=num.charCodeAt(0);
1239 }
1240 if(num >= 10)num=num % 10;
1241 return mv_clip_colors[num];
1242 }
1243 }
1244 /* mv_embed extensions for playlists */
1245 var PlMvEmbed=function(vid_init){
1246 //js_log('PlMvEmbed: '+ vid_init.id);
1247 //create the div container
1248 var ve = document.createElement('div');
1249 //extend ve with all this
1250 this.init(vid_init);
1251 for(method in this){
1252 if(method!='readyState'){
1253 ve[method]= this[method];
1254 }
1255 }
1256 js_log('ve src len:'+ ve.media_element.sources.length);
1257 return ve;
1258 }
1259 //all the overwritten and new methods for playlist extension of baseEmbed
1260 PlMvEmbed.prototype = {
1261 init:function(vid_init){
1262 //send embed_video a created video element:
1263 ve = document.createElement('div');
1264 for(var i in vid_init){
1265 //set the parent clip pointer:
1266 if(i=='pc'){
1267 this['pc']=vid_init['pc'];
1268 }else{
1269 ve.setAttribute(i,vid_init[i]);
1270 }
1271 }
1272 var videoInterface = new embedVideo(ve);
1273 //inherit the videoInterface
1274 for( method in videoInterface ){
1275 if(method!='style'){
1276 if( this[ method ] ){
1277 //parent embed method preservation:
1278 this['pe_'+method]=videoInterface[method];
1279 }else{
1280 this[method]=videoInterface[method];
1281 }
1282 }
1283 //string -> boolean:
1284 if(this[method]=="false")this[method]=false;
1285 if(this[method]=="true")this[method]=true;
1286 }
1287 },
1288 onClipDone:function(){
1289 js_log('pl onClipDone (should go to next)');
1290 //go to next in playlist:
1291 this.pc.pp.playNext();
1292 },
1293 stop:function(){
1294 js_log('pl:do stop');
1295 //set up convenience pointer to parent playlist
1296 var _this = this.pc.pp;
1297
1298 var th=Math.round( _this.pl_layout.clip_desc * _this.height );
1299 var tw=Math.round( th * _this.pl_layout.clip_aspect );
1300 //run the parent stop:
1301 this.pe_stop();
1302 var pl_height = (_this.sequencer=='true')?_this.height+27:_this.height;
1303
1304 this.getHTML();
1305
1306 },
1307 play:function(){
1308 //js_log('pl eb play');
1309 var _this = this.pc.pp;
1310 //check if we are already playing
1311 if( !this.thumbnail_disp ){
1312 this.pe_play();
1313 return '';
1314 }
1315 mv_lock_vid_updates=true;
1316 this.pe_play();
1317 },
1318 //do post interface operations
1319 postEmbedJS:function(){
1320 //add playlist clips (if plugin supports it)
1321 if(this.pc.pp.cur_clip.embed.playlistSupport())
1322 this.pc.pp.loadEmbedPlaylist();
1323 //color playlist points (if play_head present)
1324 if(this.pc.pp.disp_play_head)
1325 this.pc.pp.colorPlayHead();
1326 //setup hover images (for playhead and next/prev buttons)
1327 this.pc.pp.setUpHover();
1328 //call the parent postEmbedJS
1329 this.pe_postEmbedJS();
1330 mv_lock_vid_updates=false;
1331 },
1332 getPlayButton:function(){
1333 return this.pe_getPlayButton(this.pc.pp.id);
1334 },
1335 setStatus:function(value){
1336 //status updates handled by playlist obj
1337 },
1338 setSliderValue:function(value){
1339 //setSlider value handled by playlist obj
1340 }
1341 }
1342
1343 /*
1344 * m3u parse
1345 */
1346 var m3uPlaylist = {
1347 doParse:function(){
1348 //for each line not # add as clip
1349 var inx =0;
1350 var this_pl = this;
1351 //js_log('data:'+ this.data.toString());
1352 $j.each(this.data.split("\n"), function(i,n){
1353 //js_log('on line '+i+' val:'+n+' len:'+n.length);
1354 if(n.charAt(0)!='#'){
1355 if(n.length>3){
1356 //@@todo make sure its a valid url
1357 //js_log('add url: '+i + ' '+ n);
1358 var cur_clip = new mvClip({type:'srcClip',id:'p_'+this_pl.id+'_c_'+inx,pp:this_pl,src:n,order:inx});
1359 //setup the embed object
1360 cur_clip.setUpEmbedObj();
1361 js_log('m3uPlaylist len:'+ thisClip.embed.media_element.sources.length);
1362 this_pl.addCliptoTrack(cur_clip);
1363 inx++;
1364 }
1365 }
1366 });
1367 return true;
1368 }
1369 }
1370
1371 var itunesPlaylist = {
1372 doParse:function(){
1373 var properties = { title:'title', linkback:'link',
1374 author:'itunes:author',desc:'description',
1375 date:'pubDate' };
1376 var tmpElm = null;
1377 for(i in properties){
1378 tmpElm = this.data.getElementsByTagName(properties[i])[0];
1379 if(tmpElm){
1380 this[i] = tmpElm.childNodes[0].nodeValue;
1381 //js_log('set '+i+' to '+this[i]);
1382 }
1383 }
1384 //image src is nested in itunes rss:
1385 tmpElm = this.data.getElementsByTagName('image')[0];
1386 if(tmpElm){
1387 imgElm = tmpElm.getElementsByTagName('url')[0];
1388 if(imgElm){
1389 this.img = imgElm.childNodes[0].nodeValue;
1390 }
1391 }
1392 //get the clips:
1393 var clips = this.data.getElementsByTagName("item");
1394 properties.src = 'guid';
1395 for (var i=0;i<clips.length;i++){
1396 var cur_clip = new mvClip({type:'srcClip',id:'p_'+this.id+'_c_'+i,pp:this,order:i});
1397 for(var j in properties){
1398 tmpElm = clips[i].getElementsByTagName( properties[j] )[0];
1399 if(tmpElm!=null){
1400 cur_clip[j] = tmpElm.childNodes[0].nodeValue;
1401 //js_log('set clip property: ' + j+' to '+cur_clip[j]);
1402 }
1403 }
1404 //image is nested
1405 tmpElm = clips[i].getElementsByTagName('image')[0];
1406 if(tmpElm){
1407 imgElm = tmpElm.getElementsByTagName('url')[0];
1408 if(imgElm){
1409 cur_clip.img = imgElm.childNodes[0].nodeValue;
1410 }
1411 }
1412 //set up the embed object now that all the values have been set
1413 cur_clip.setUpEmbedObj();
1414
1415 //add the current clip to the clip list
1416 this.addCliptoTrack(cur_clip);
1417 }
1418 return true;
1419 }
1420 }
1421
1422 /*
1423 * parse xsfp:
1424 * http://www.xspf.org/xspf-v1.html
1425 */
1426 var xspfPlaylist ={
1427 doParse:function(){
1428 //js_log('do xsfp parse: '+ this.data.innerHTML);
1429 var properties = { title:'title', linkback:'info',
1430 author:'creator',desc:'annotation',
1431 poster:'image', date:'date' };
1432 var tmpElm = null;
1433 //get the first instance of any of the meta tags (ok that may be the meta on the first clip)
1434 //js_log('do loop on properties:' + properties);
1435 for(i in properties){
1436 js_log('on property: '+i);
1437 tmpElm = this.data.getElementsByTagName(properties[i])[0];
1438 if(tmpElm){
1439 if(tmpElm.childNodes[0]){
1440 this[i] = tmpElm.childNodes[0].nodeValue;
1441 js_log('set pl property: ' + i+' to '+this[i]);
1442 }
1443 }
1444 }
1445 var clips = this.data.getElementsByTagName("track");
1446 js_log('found clips:'+clips.length);
1447 //add any clip specific properties
1448 properties.src = 'location';
1449 for (var i=0;i<clips.length;i++){
1450 var cur_clip = new mvClip({id:'p_'+this.id+'_c_'+i,pp:this,order:i});
1451 //js_log('cur clip:'+ cur_clip.id);
1452 for(var j in properties){
1453 tmpElm = clips[i].getElementsByTagName( properties[j] )[0];
1454 if(tmpElm!=null){
1455 if( tmpElm.childNodes.length!=0){
1456 cur_clip[j] = tmpElm.childNodes[0].nodeValue;
1457 js_log('set clip property: ' + j+' to '+cur_clip[j]);
1458 }
1459 }
1460 }
1461 //add mvClip ref from info link:
1462 if(cur_clip.linkback){
1463 //if mv linkback
1464 mvInx = 'Stream:';
1465 mvclippos = cur_clip.linkback.indexOf(mvInx);
1466 if(mvclippos!==false){
1467 cur_clip.mvclip=cur_clip.linkback.substr( mvclippos+mvInx.length );
1468 }
1469 }
1470 //set up the embed object now that all the values have been set
1471 cur_clip.setUpEmbedObj();
1472 //add the current clip to the clip list
1473 this.addCliptoTrack(cur_clip);
1474 }
1475 //js_log('done with parse');
1476 return true;
1477 }
1478 }
1479 /*****************************
1480 * SMIL CODE (could be put into another js file / lazy_loaded for improved basic playlist performance / modularity)
1481 *****************************/
1482 /*playlist driver extensions to the playlist object*/
1483 mvPlayList.prototype.monitor = function(){
1484 //js_log('pl:monitor');
1485 //if paused stop updates
1486 if( this.paused ){
1487 //clearInterval( this.smil_monitorTimerId );
1488 return ;
1489 }
1490 //js_log("pl check: " + this.currentTime + ' > '+this.getDuration());
1491 //check if we should be done:
1492 if( this.currentTime > this.getDuration() )
1493 this.stop();
1494
1495 //update the playlist current time:
1496 //check for a trsnOut from the previus clip to subtract
1497 this.currentTime = this.cur_clip.dur_offset + this.cur_clip.embed.relativeCurrentTime();
1498
1499 //update slider:
1500 if(!this.userSlide){
1501 this.setStatus(seconds2npt(this.currentTime) + '/' + seconds2npt(this.getDuration()) );
1502 this.setSliderValue( this.currentTime / this.getDuration() );
1503 }
1504 //pre-load any future clips:
1505 this.loadFutureClips();
1506
1507
1508 //status updates are handled by children clips ... playlist mostly manages smil actions
1509 this.doSmilActions();
1510
1511 if( ! this.smil_monitorTimerId ){
1512 if(document.getElementById(this.id)){
1513 this.smil_monitorTimerId = setInterval('$j(\'#'+this.id+'\').get(0).monitor()', 250);
1514 }
1515 }
1516 }
1517 //handles the rendering of overlays load of future clips (if necessary)
1518 //@@todo could be lazy loaded if necessary
1519 mvPlayList.prototype.doSmilActions = function( single_frame ){
1520 //js_log('f:doSmilActions: ' + this.cur_clip.id + ' tid: ' + this.cur_clip.transOut );
1521 var offSetTime = 0; //offset time should let us start a transition later on if we have to.
1522 var _clip = this.cur_clip; //setup a local pointer to cur_clip
1523
1524
1525 //do any smil time actions that may change the current clip
1526 if( this.userSlide ){
1527 //current clip set is set via updateThumbTime function
1528 }else{
1529 //assume playing and go to next:
1530 if( _clip.dur <= _clip.embed.currentTime
1531 && _clip.order != _clip.pp.getClipCount()-1 ){
1532 //force next clip
1533 js_log('order:' + _clip.order + ' != count:' + ( _clip.pp.getClipCount()-1 ) +
1534 ' smil dur: ' + _clip.dur + ' <= curTime: ' + _clip.embed.currentTime + ' go to next clip..');
1535 //do a _play next:
1536 _clip.pp.playNext();
1537 }
1538 }
1539 //@@todo could maybe generalize transIn with trasOut into one "flow" with a few scattered if statements
1540 //update/setup all transitions (will render current transition state)
1541 var in_range=false;
1542 //pretty similar actions per transition types so group into a loop:
1543 var tran_types = {'transIn':true,'transOut':true};
1544 for(var tid in tran_types ){
1545 eval('var tObj = _clip.'+tid);
1546 if(!tObj)
1547 continue;
1548 //js_log('f:doSmilActions: ' + _clip.id + ' tid:'+tObj.id + ' tclip_id:'+ tObj.pClip.id);
1549 //make sue we are in range:
1550 if( tid=='transIn' )
1551 in_range = (_clip.embed.currentTime <= tObj.dur)?true:false;
1552
1553 if( tid=='transOut' )
1554 in_range = (_clip.embed.currentTime >= (_clip.dur - tObj.dur))?true:false;
1555
1556 if( in_range ){
1557 if( this.userSlide || single_frame ){
1558 if( tid=='transIn' )
1559 mvTransLib.doUpdate(tObj, (_clip.embed.currentTime / tObj.dur) );
1560
1561 if( tid=='transOut' )
1562 mvTransLib.doUpdate(tObj, (_clip.embed.currentTime-(_clip.dur - tObj.dur)) /tObj.dur);
1563
1564 }else{
1565 if( tObj.animation_state==0 ){
1566 js_log('init/run_transition ');
1567 tObj.run_transition();
1568 }
1569 }
1570 }else{
1571 //close up transition if done & still onDispaly
1572 if( tObj.overlay_selector_id ){
1573 js_log('close up transition :'+tObj.overlay_selector_id);
1574 mvTransLib.doCloseTransition( tObj );
1575 }
1576 }
1577 }
1578 }
1579
1580 /*
1581 * mvTransLib library of transitions
1582 * a single object called to initiate transition effects can easily be extended in separate js file
1583 * /mvTransLib is a all static object no instances of mvTransLib/
1584 * (that way a limited feature set "sequence" need not include a _lot_ of js unless necessary )
1585 *
1586 * Smil Transition Effects see:
1587 * http://www.w3.org/TR/SMIL3/smil-transitions.html#TransitionEffects-TransitionAttribute
1588 */
1589 var mvTransLib = {
1590 /*
1591 * function doTransition lookups up the transition in the mvTransLib obj
1592 * and init the transition if its available
1593 * @param tObj transition attribute object
1594 * @param offSetTime default value 0 if we need to start rendering from a given time
1595 */
1596 doInitTransition:function(tObj){
1597 js_log('mvTransLib:f:doInitTransition');
1598 if(!tObj.type){
1599 js_log('transition is missing type attribute');
1600 return false;
1601 }
1602
1603 if(!tObj.subtype){
1604 js_log('transition is missing subtype attribute');
1605 return false;
1606 }
1607
1608 if(!this['type'][tObj.type]){
1609 js_log('mvTransLib does not support type: '+tObj.type);
1610 return false;
1611 }
1612
1613 if(!this['type'][tObj.type][tObj.subtype]){
1614 js_log('mvTransLib does not support subType: '+tObj.subtype);
1615 return false;
1616 }
1617
1618 //setup overlay_selector_id
1619 if(tObj.subtype=='crossfade'){
1620 if(tObj.transAttrType=='transIn')
1621 var other_pClip = tObj.pClip.pp.getPrevClip();
1622 if(tObj.transAttrType=='transOut')
1623 var other_pClip = tObj.pClip.pp.getNextClip();
1624
1625 if(typeof(other_pClip)=='undefined' || other_pClip === false || other_pClip.id == tObj.pClip.pp.cur_clip.id)
1626 js_log('Error: crossfade without target media asset');
1627 //if not sliding start playback:
1628 if(!tObj.pClip.pp.userSlide){
1629 other_pClip.embed.play();
1630 //manualy ad the extra layer to the activeClipList
1631 tObj.pClip.pp.activeClipList.add( other_pClip );
1632 }
1633 tObj.overlay_selector_id = 'clipDesc_'+other_pClip.id;
1634 }else{
1635 tObj.overlay_selector_id =this.getOverlaySelector(tObj);
1636 }
1637
1638 //all good call function with tObj param
1639 js_log('should call: '+tObj.type + ' ' + tObj.subtype );
1640 this['type'][tObj.type][tObj.subtype].init(tObj);
1641 },
1642 doCloseTransition:function(tObj){
1643 if(tObj.subtype=='crossfade'){
1644 //close up crossfade
1645 js_log("close up crossfade");
1646 }else{
1647 $j('#'+tObj.overlay_selector_id).remove();
1648 }
1649 //null selector:
1650 tObj.overlay_selector_id=null;
1651 },
1652 getOverlaySelector:function(tObj){
1653 var overlay_selector_id= tObj.transAttrType + tObj.pClip.id;
1654 js_log('f:getOverlaySelector: '+overlay_selector_id + ' append to: ' +'#videoPlayer_'+tObj.pClip.embed.id );
1655 //make sure overlay_selector_id not already here:
1656 if( $j('#'+overlay_selector_id).length == 0 ){
1657 $j('#videoPlayer_'+tObj.pClip.embed.id).prepend(''+
1658 '<div id="'+overlay_selector_id+'" ' +
1659 'style="position:absolute;top:0px;left:0px;' +
1660 'height:'+parseInt(tObj.pClip.pp.height)+'px;'+
1661 'width:'+parseInt(tObj.pClip.pp.width)+'px;' +
1662 'z-index:2">' +
1663 '</div>');
1664 }
1665 return overlay_selector_id;
1666 },
1667 doUpdate:function(tObj, percent){
1668 //init the transition if nessesary:
1669 if(!tObj.overlay_selector_id)
1670 this.doInitTransition(tObj);
1671
1672 //@@todo we should ensure visability outside of doUpate loop
1673 if(!$j('#'+tObj.overlay_selector_id).is(':visible'))
1674 $j('#'+tObj.overlay_selector_id).show();
1675
1676 //do update:
1677 /*js_log('doing update for: '+ tObj.pClip.id +
1678 ' type:' + tObj.transAttrType +
1679 ' t_type:'+ tObj.type +
1680 ' subypte:'+ tObj.subtype +
1681 ' percent:' + percent);*/
1682
1683 this['type'][tObj.type][tObj.subtype].u(tObj,percent);
1684 },
1685 getTransitionIcon:function( type, subtype){
1686 return mv_embed_path +'/skins/'+mv_skin_name+'/transition_images/'+ type+'_'+ subtype+ '.png';
1687 },
1688 /*
1689 * mvTransLib: functional library mapping:
1690 */
1691 type:{
1692 //types:
1693 fade:{
1694 fadeFromColor:{
1695 'attr':['fadeColor'],
1696 'init':function(tObj){
1697 //js_log('f:fadeFromColor: '+tObj.overlay_selector_id +' to color: '+ tObj.fadeColor);
1698 if(!tObj.fadeColor)
1699 js_log('missing fadeColor');
1700 if($j('#'+tObj.overlay_selector_id).length==0){
1701 js_log("ERROR can't find: "+ tObj.overlay_selector_id);
1702 }
1703 //set the initial state
1704 $j('#'+tObj.overlay_selector_id).css({
1705 'background-color':tObj.fadeColor,
1706 'opacity':"1"
1707 });
1708 },
1709 'u':function(tObj, percent){
1710 //js_log(':fadeFromColor:update: '+ percent);
1711 //fade from color (invert the percent)
1712 var percent = 1- percent;
1713 $j('#'+tObj.overlay_selector_id).css({
1714 "opacity" : percent
1715 });
1716 }
1717 },
1718 //corssFade
1719 crossfade:{
1720 "attr":[],
1721 "init":function(tObj){
1722 js_log('f:crossfade: '+tObj.overlay_selector_id);
1723 if($j('#'+tObj.overlay_selector_id).length==0)
1724 js_log("ERROR overlay selector not found: "+tObj.overlay_selector_id);
1725
1726 //set the initial state show the zero opacity animation
1727 $j('#'+tObj.overlay_selector_id).css({'opacity':0}).show();
1728 },
1729 'u':function(tObj, percent){
1730 $j('#'+tObj.overlay_selector_id).css({
1731 "opacity" : percent
1732 });
1733 }
1734 }
1735 }
1736 }
1737 }
1738
1739 /* object to manage embedding html with smil timings
1740 * grabs settings from parent clip
1741 */
1742 var transitionObj = function(element) {
1743 this.init(element);
1744 };
1745 transitionObj.prototype = {
1746 supported_attributes : new Array(
1747 'id',
1748 'type',
1749 'subtype',
1750 'fadeColor',
1751 'dur'
1752 ),
1753 transAttrType:null, //transIn or transOut
1754 overlay_selector_id:null,
1755 pClip:null,
1756 timerId:null,
1757 animation_state:0, //can be 0=unset, 1=running, 2=done
1758 interValCount:0, //inter-intervalCount for animating between time updates
1759 dur:2, //default duration of 2
1760 init:function(element){
1761 //load supported attributes:
1762 var _this = this;
1763 $j.each(this.supported_attributes, function(i, attr){
1764 if(element.getAttribute(attr))
1765 _this[attr]= element.getAttribute(attr);
1766 });
1767 //@@todo process duration (for now just strip s) per:
1768 //http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
1769 if(_this.dur)
1770 _this.dur = smilParseTime(_this.dur);
1771 },
1772 /*
1773 * returns a visual representation of the transition
1774 */
1775 getIconSrc:function(opt){
1776 //@@todo support some arguments
1777 return mvTransLib.getTransitionIcon(this.type, this.subtype);
1778 },
1779 getDuration:function(){
1780 return this.dur;
1781 },
1782 //returns the values of supported_attributes:
1783 getAttributeObj:function(){
1784 var elmObj = {};
1785 for(var i in this.supported_attributes){
1786 var attr = this.supported_attributes[i];
1787 if(this[attr])
1788 elmObj[ attr ] = this[attr];
1789 }
1790 return elmObj;
1791 },
1792 /*
1793 * the main animation loop called every MV_ANIMATION_CB_RATE or 34ms ~around 30frames per second~
1794 */
1795 run_transition:function(){
1796 //js_log('f:run_transition:' + this.interValCount);
1797
1798 //update the time from the video if native:
1799 if(typeof this.pClip.embed.vid !='undefined'){
1800 this.interValCount=0;
1801 this.pClip.embed.currentTime = this.pClip.embed.vid.currentTime;
1802 }
1803
1804 //}else{
1805 //relay on currentTime update grabs (every 250ms or so) (ie for images)
1806 // if(this.prev_curtime!=this.pClip.embed.currentTime){
1807 // this.prev_curtime = this.pClip.embed.currentTime;
1808 // this.interValCount=0;
1809 // }
1810 //}
1811 //start_time =asigned by doSmilActions
1812 //base_cur_time = pClip.embed.currentTime;
1813 //dur = asigned by attribute
1814 if(this.animation_state==0){
1815 mvTransLib.doInitTransition(this);
1816 this.animation_state=1;
1817 }
1818 //set percentage include difrence of currentTime to prev_curTime
1819 // ie updated in-between currentTime updates)
1820
1821 if(this.transAttrType=='transIn')
1822 var percentage = ( this.pClip.embed.currentTime +
1823 ( (this.interValCount*MV_ANIMATION_CB_RATE)/1000 )
1824 ) / this.dur ;
1825
1826 if(this.transAttrType=='transOut')
1827 var percentage = (this.pClip.embed.currentTime +
1828 ( (this.interValCount*MV_ANIMATION_CB_RATE)/1000 )
1829 - (this.pClip.dur - this.dur)
1830 ) /this.dur ;
1831
1832 /*js_log('percentage = ct:'+this.pClip.embed.currentTime + ' + ic:'+this.interValCount +' * cb:'+MV_ANIMATION_CB_RATE +
1833 ' / ' + this.dur + ' = ' + percentage );
1834 */
1835
1836 //js_log('cur percentage of transition: '+percentage);
1837 //update state based on current time + cur_time_offset (for now just use pClip.embed.currentTime)
1838 mvTransLib.doUpdate(this, percentage);
1839
1840 if( percentage >= 1 ){
1841 js_log("transition done update with percentage "+percentage);
1842 this.animation_state=2;
1843 clearInterval(this.timerId);
1844 mvTransLib.doCloseTransition(this)
1845 return true;
1846 }
1847
1848 this.interValCount++;
1849 //setInterval in we are still in running state and user is not using the playhead
1850 if( this.animation_state==1 ){
1851 if(!this.timerId){
1852 this.timerId = setInterval('document.getElementById(\'' + this.pClip.pp.id + '\').'+
1853 'run_transition(\'' + this.pClip.pp.cur_clip.order + '\','+
1854 '\''+ this.transAttrType + '\')',
1855 MV_ANIMATION_CB_RATE);
1856 }
1857 }else{
1858 clearInterval(this.timerId);
1859 }
1860 return true;
1861 },
1862 clone:function(){
1863 var cObj = new this.constructor();
1864 for(var i in this)
1865 cObj[i]=this[i];
1866 return cObj;
1867 }
1868 }
1869
1870 //very limited smile feature set more details soon:
1871 //region="video_region" transIn="fromGreen" begin="2s"
1872 //http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#edef-ref
1873 var smilPlaylist ={
1874 transitions:{},
1875 doParse:function(){
1876 var _this = this;
1877 js_log('f:doParse smilPlaylist');
1878 //@@todo get/parse meta that we are intersted in:
1879 var meta_tags = this.data.getElementsByTagName('meta');
1880 var metaNames = {
1881 'title':'',
1882 'interface_url':"",
1883 'linkback':"",
1884 'mTitle':"",
1885 'mTalk':"",
1886 'mTouchedTime':""
1887 };
1888 $j.each(meta_tags, function(i,meta_elm){
1889 //js_log( "on META tag: "+ $j(meta_elm).attr('name') );
1890 if( $j(meta_elm).attr('name') in metaNames){
1891 _this[ $j(meta_elm).attr('name') ] = $j(meta_elm).attr('content');
1892 }
1893 //special check for wikiDesc
1894 if( $j(meta_elm).attr('name') == 'wikiDesc'){
1895 if(meta_elm.firstChild)
1896 _this.wikiDesc = meta_elm.firstChild.nodeValue;
1897 }
1898 });
1899 //add transition objects:
1900 var transition_tags = this.data.getElementsByTagName('transition');
1901 $j.each(transition_tags, function( i, trans_elm ){
1902 if( $j(trans_elm).attr("id") ){
1903 _this.transitions[ $j(trans_elm).attr("id")]= new transitionObj( trans_elm );
1904 }else{
1905 js_log('skipping transition: (missing id) ' + trans_elm );
1906 }
1907 });
1908 js_log('loaded transitions:' + _this.transitions.length);
1909 //add seq (latter we will have support more than one seq tag) / more than one "track"
1910 var seq_tags = this.data.getElementsByTagName('seq');
1911 $j.each(seq_tags, function(i,seq_elm){
1912 var inx = 0;
1913 //get all the clips for the given seq:
1914 $j.each(seq_elm.childNodes, function(i, mediaElement){
1915 //~complex~ @@todo to handlde a lot like "switch" "region" etc
1916 //js_log('process: ' + mediaElemnt.tagName);
1917 if(typeof mediaElement.tagName!='undefined'){
1918 if( _this.tryAddMedia( mediaElement, inx ) ){
1919 inx++;
1920 }
1921 }
1922 });
1923 });
1924 js_log("done proc seq tags");
1925 return true;
1926 },
1927 tryAddMediaObj:function(mConfig, order, track_id){
1928 var mediaElement = document.createElement('ref');
1929 for(var i =0; i < mv_smil_ref_supported_attributes.length;i++){
1930 var attr = mv_smil_ref_supported_attributes[i];
1931 if(mConfig[attr])
1932 $j(mediaElement).attr(attr, mConfig[attr]);
1933 }
1934 this.tryAddMedia(mediaElement, order, track_id);
1935 },
1936 tryAddMedia:function(mediaElement, order, track_id){
1937 js_log('SMIL:tryAddMedia:' + mediaElement);
1938 var _this = this;
1939 //set up basic mvSMILClip send it the mediaElemnt & mvClip init:
1940 var clipObj = {};
1941 var cConfig = {
1942 "id":'p_' + _this.id + '_c_' + order,
1943 "pp":this, //set the parent playlist object pointer
1944 "order": order
1945 };
1946 var clipObj = new mvSMILClip(mediaElement, cConfig );
1947
1948 //set optional params track
1949 if( typeof track_id != 'undefined')
1950 clipObj["track_id"] = track_id;
1951
1952 //debugger;
1953 if ( clipObj ){
1954 //set up embed:
1955 clipObj.setUpEmbedObj();
1956 //add clip to track:
1957 this.addCliptoTrack( clipObj , order);
1958 return true;
1959 }
1960 //@@todo we could throw error details here once we integrate try catches everywhere :P
1961 return false;
1962 }
1963 }
1964 //http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#smilMediaNS-BasicMedia
1965 //and added resource description elements
1966 //@@ supporting the "ID" attribute turns out to be kind of tricky since we use it internally
1967 // (for now don't include)
1968 var mv_smil_ref_supported_attributes = new Array(
1969 'src',
1970 'type',
1971 'region',
1972 'transIn',
1973 'transOut',
1974 'fill',
1975 'dur',
1976 'title',
1977
1978 'uri',
1979 'poster'
1980 );
1981 /* extension to mvClip to support smil properties */
1982 var mvSMILClip=function(sClipElm, mvClipInit){
1983 return this.init(sClipElm, mvClipInit);
1984 }
1985 //all the overwritten and new methods for SMIL extension of mv_embed
1986 mvSMILClip.prototype = {
1987 instanceOf:'mvSMILClip',
1988 params : {}, //support param as child of ref clips per SMIL spec
1989 init:function(sClipElm, mvClipInit){
1990 _this = this;
1991 this.params = {};
1992 //make new mvCLip with ClipInit vals
1993 var myMvClip = new mvClip( mvClipInit );
1994
1995 //inherit mvClip
1996 for(var method in myMvClip){
1997 if(typeof this[method] != 'undefined' ){
1998 this['parent_'+method]=myMvClip[method];
1999 }else{
2000 this[method] = myMvClip[method];
2001 }
2002 }
2003
2004 //get supported media attr init non-set
2005 for(var i =0; i < mv_smil_ref_supported_attributes.length;i++){
2006 var attr = mv_smil_ref_supported_attributes[i];
2007 if( $j(sClipElm).attr(attr)){
2008 _this[attr] = $j(sClipElm).attr(attr);
2009 }
2010 }
2011 this['tagName'] = sClipElm.tagName;
2012
2013 if( sClipElm.firstChild ){
2014 this['wholeText'] = sClipElm.firstChild.nodeValue;
2015 js_log("SET wholeText for: " + this['tagName'] + ' '+ this['wholeText']);
2016 }
2017 //debugger;
2018 //mv_embed specific property:
2019 if( $j(sClipElm).attr('poster') )
2020 this['img'] = $j(sClipElm).attr('poster');
2021
2022 //lookup and assign copies of transitions
2023 // (since transition needs to hold some per-instance state info)
2024 if(this.transIn && this.pp.transitions[ this.transIn ]){
2025 this.transIn = this.pp.transitions[ this.transIn ].clone();
2026 this.transIn.pClip = _this;
2027 this.transIn.transAttrType='transIn';
2028 }
2029
2030 if(this.transOut && this.pp.transitions[ this.transOut ]){
2031 this.transOut = this.pp.transitions[ this.transOut ].clone();
2032 this.transOut.pClip = _this;
2033 this.transOut.transAttrType = 'transOut';
2034 }
2035 //parse duration / begin times:
2036 if( this.dur )
2037 this.dur = smilParseTime( this.dur );
2038
2039 //conform type to vido/ogg:
2040 if( this.type == 'application/ogg' )
2041 this.type = 'video/ogg'; //conform to 'video/ogg' type
2042
2043 //if unset type and we have innerHTML assume text/html type
2044 if( !this.type && this.wholeText ){
2045 this.type = 'text/html';
2046 }
2047 //also grab andy child param elements if present:
2048 if( sClipElm.getElementsByTagName('param')[0] ){
2049 for(var i=0; i< sClipElm.getElementsByTagName('param').length; i++){
2050 this.params[ sClipElm.getElementsByTagName('param')[i].getAttribute("name") ] =
2051 sClipElm.getElementsByTagName('param')[i].firstChild.nodeValue;
2052 }
2053 }
2054 return this;
2055 },
2056 //returns the values of supported_attributes:
2057 getAttributeObj:function(){
2058 var elmObj = {};
2059 for(var i=0; i < mv_smil_ref_supported_attributes.length; i++){
2060 var attr = mv_smil_ref_supported_attributes[i];
2061 if(this[attr])
2062 elmObj[ attr ] = this[attr];
2063 }
2064 return elmObj;
2065 },
2066 /*
2067 * getDuration
2068 * @returns duration in int
2069 */
2070 getDuration:function(){
2071 //check for smil dur:
2072 if( this.dur )
2073 return this.dur;
2074 return this.embed.getDuration();
2075 },
2076 //gets the duration of the clip subracting transitions
2077 getSoloDuration:function(){
2078 var fulldur = this.getDuration();
2079 //see if we need to subtract from time eating transitions (transOut)
2080 if(this.transOut)
2081 fulldur -= this.transOut.getDuration();
2082
2083 //js_log("getSoloDuration:: td: " + this.getDuration() + ' sd:' + fulldur);
2084 return fulldur;
2085 }
2086 }
2087 /*
2088 * takes an input
2089 * @time_str input time string
2090 * returns time in seconds
2091 *
2092 * @@todo process duration (for now just srip s) per:
2093 * http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
2094 * (probably have to use a Time object to fully support the smil spec
2095 */
2096 function smilParseTime(time_str){
2097 //first check for hh:mm:ss time:
2098 if(time_str.split(':').length == 3){
2099 return npt2seconds(time_str);
2100 }else{
2101 //assume 34s secconds representation
2102 return parseInt(time_str.replace('s', ''));
2103 }
2104 }
2105 //stores a list pointers to active clips (maybe this should just be a property of clips (but results in lots of seeks)
2106 var activeClipList = function(){
2107 return this.init();
2108 }
2109 activeClipList.prototype = {
2110 init:function(){
2111 this.clipList = new Array();
2112 },
2113 add:function( clip ){
2114 //make sure the clip is not already active:
2115 for(var i =0;i < this.clipList.lenght; i++){
2116 var active_clip = this.clipList[i];
2117 if(clip.id == active_clip.id) //clip already active:
2118 return false;
2119 }
2120 this.clipList.push( clip );
2121 return true;
2122 },
2123 remove:function( clip ){
2124 for(var i = 0; i < this.clipList.length; i++){
2125 var active_clip = this.clipList[i];
2126 if(clip.id == active_clip.id){
2127 this.clipList.splice(i, 1);
2128 return true;
2129 }
2130 }
2131 return false;
2132 },
2133 getClipList:function(){
2134 return this.clipList;
2135 }
2136 }
2137 var trackObj = function( iObj ){
2138 return this.init( iObj );
2139 }
2140 var supported_track_attr =
2141 trackObj.prototype = {
2142 //should be something like "seq" per SMIL spec
2143 //http://www.w3.org/TR/SMIL3/smil-timing.html#edef-seq
2144 // but we don't really support anywhere near the full concept of seq containers yet either
2145 supported_attributes: new Array(
2146 'title',
2147 'desc',
2148 'inx'
2149 ),
2150 disp_mode:'timeline_thumb',
2151 init : function(iObj){
2152 if(!iObj)
2153 iObj={};
2154 //make sure clips is new:
2155 this.clips = new Array();
2156
2157 var _this = this;
2158 $j.each(this.supported_attributes, function(i, attr){
2159 if(iObj[attr])
2160 _this[attr] = iObj[attr];
2161 });
2162 },
2163 //returns the values of supported_attributes:
2164 getAttributeObj:function(){
2165 var elmObj = {};
2166 for(var i in this.supported_attributes){
2167 var attr = this.supported_attributes[i];
2168 if(this[attr])
2169 elmObj[ attr ] = this[attr];
2170 }
2171 return elmObj;
2172 },
2173 addClip:function(clipObj, pos){
2174 js_log('pl_Track: AddClip at:' + pos + ' clen: ' + this.clips.length);
2175 if( typeof pos == 'undefined' )
2176 pos = this.clips.length;
2177 //get everything after pos
2178 this.clips.splice(pos, 0, clipObj);
2179 //keep the clip order values accurate:
2180 this.reOrderClips();
2181 js_log("did add now cLen: " + this.clips.length);
2182 },
2183 getClip:function( inx ){
2184 if( !this.clips[inx] )
2185 return false;
2186 return this.clips[inx];
2187 },
2188 reOrderClips:function(){
2189 for(var k in this.clips){
2190 this.clips[k].order=k;
2191 }
2192 },
2193 getClipCount:function(){
2194 return this.clips.length;
2195 },
2196 inheritEmbedObj: function(){
2197 $j.each(this.clips, function(i, clip){
2198 clip.embed.inheritEmbedObj();
2199 });
2200 }
2201 };
2202
2203 /* utility functions
2204 * (could be combined with other stuff)
2205 */
2206 function getAbsolutePos(objectId) {
2207 // Get an object left position from the upper left viewport corner
2208 o = document.getElementById(objectId);
2209 oLeft = o.offsetLeft; // Get left position from the parent object
2210 while(o.offsetParent!=null) { // Parse the parent hierarchy up to the document element
2211 oParent = o.offsetParent // Get parent object reference
2212 oLeft += oParent.offsetLeft // Add parent left position
2213 o = oParent
2214 }
2215 o = document.getElementById(objectId);
2216 oTop = o.offsetTop;
2217 while(o.offsetParent!=null) { // Parse the parent hierarchy up to the document element
2218 oParent = o.offsetParent // Get parent object reference
2219 oTop += oParent.offsetTop // Add parent top position
2220 o = oParent
2221 }
2222 return {x:oLeft,y:oTop};
2223 }
2224 String.prototype.htmlEntities = function(){
2225 var chars = new Array ('&','à','á','â','ã','ä','å','æ','ç','è','é',
2226 'ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô',
2227 'õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','À',
2228 'Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë',
2229 'Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö',
2230 'Ø','Ù','Ú','Û','Ü','Ý','Þ','€','\"','ß','<',
2231 '>','¢','£','¤','¥','¦','§','¨','©','ª','«',
2232 '¬','­','®','¯','°','±','²','³','´','µ','¶',
2233 '·','¸','¹','º','»','¼','½','¾');
2234
2235 var entities = new Array ('amp','agrave','aacute','acirc','atilde','auml','aring',
2236 'aelig','ccedil','egrave','eacute','ecirc','euml','igrave',
2237 'iacute','icirc','iuml','eth','ntilde','ograve','oacute',
2238 'ocirc','otilde','ouml','oslash','ugrave','uacute','ucirc',
2239 'uuml','yacute','thorn','yuml','Agrave','Aacute','Acirc',
2240 'Atilde','Auml','Aring','AElig','Ccedil','Egrave','Eacute',
2241 'Ecirc','Euml','Igrave','Iacute','Icirc','Iuml','ETH','Ntilde',
2242 'Ograve','Oacute','Ocirc','Otilde','Ouml','Oslash','Ugrave',
2243 'Uacute','Ucirc','Uuml','Yacute','THORN','euro','quot','szlig',
2244 'lt','gt','cent','pound','curren','yen','brvbar','sect','uml',
2245 'copy','ordf','laquo','not','shy','reg','macr','deg','plusmn',
2246 'sup2','sup3','acute','micro','para','middot','cedil','sup1',
2247 'ordm','raquo','frac14','frac12','frac34');
2248
2249 newString = this;
2250 for (var i = 0; i < chars.length; i++)
2251 {
2252 myRegExp = new RegExp();
2253 myRegExp.compile(chars[i],'g')
2254 newString = newString.replace (myRegExp, '&' + entities[i] + ';');
2255 }
2256 return newString;
2257 };