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