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