2 * the mvPlayList object code
3 * only included if playlist object found
5 * part of mwEmbed media projects see:
6 * http://www.mediawiki.org/wiki/Media_Projects_Overview
8 * @author: Michael Dale mdale@wikimedia.org
11 var mv_default_playlist_attributes
= {
12 //playlist attributes :
19 //playlist user controlled features
24 //enable sequencer? (only display top frame no navigation or accompanying text
27 //the call back rate for animations and internal timers in ms: 33 is about 30 frames a second:
28 var MV_ANIMATION_CB_RATE
= 33;
31 //10 possible colors for clips: (can be in hexadecimal)
32 var mv_clip_colors
= new Array('aqua', 'blue', 'fuchsia', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red');
34 //the base url for requesting stream metadata
35 if(typeof wgServer
=='undefined'){
36 var defaultMetaDataProvider
= 'http://metavid.org/overlay/archive_browser/export_cmml?stream_name=';
38 var defaultMetaDataProvider
= wgServer
+ wgScript
+ '?title=Special:MvExportStream&feed_format=roe&stream_name=';
41 * The playlist Object implements ~most~ of embedVideo but we don't inherit (other than to use the control builder)
42 * because pretty much every function has to be changed for the playlist context
44 var mvPlayList = function(element
) {
45 return this.init(element
);
47 //set up the mvPlaylist object
48 mvPlayList
.prototype = {
49 instanceOf
:'mvPlayList',
59 loading_external_data
:true, //if we are loading external data (set to loading by default)
62 playlist_buffer_time
: 20, // how many seconds of future clips we should buffer
64 interface_url
:null, //the interface url
66 default_track
:null, // the default track to add clips to.
67 //the layout for the playlist object
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:
79 //embed object type support system;
85 'volume_control':true,
88 'playlist_swap_loader':true //if the object supports playlist functions
90 init : function(element
){
91 js_log('mvPlayList:init:');
93 this.default_track
=null;
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];
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)");
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)");
110 //make sure height and width are int:
111 this.width
=parseInt(this.width
);
112 this.height
=parseInt(this.height
);
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',''));
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;
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:
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();
136 doOptionsHTML:function(){
137 //grab "options" use current clip:
138 this.cur_clip
.embed
.doOptionsHTML();
140 //pulls up the video editor inline
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>');
148 //@@todo clone the playlist (for faster startup)
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';
156 $j("#sequencer_target").sequencer({
157 "mv_pl_src" : this.src
161 showPlayerselect:function(){
162 this.cur_clip
.embed
.showPlayerselect();
164 closeDisplayedHTML:function(){
165 this.cur_clip
.embed
.closeDisplayedHTML();
167 showDownload:function(){
168 this.cur_clip
.embed
.showDownload();
170 showShare:function(){
171 var embed_code
= '<script type="text/javascript" '+
172 'src="'+mv_embed_path
+'mv_embed.js"></script> '+"\n" +
173 '<playlist id="'+this.id
+'" ';
175 embed_code
+='src="'+this.src
+'" />';
177 embed_code
+='>'+"\n";
178 embed_code
+= this.data
.htmlEntities();
179 embed_code
+='<playlist/>';
181 this.cur_clip
.embed
.showShare( embed_code
);
183 getPlaylist:function(){
184 js_log("f:getPlaylist: " + this.srcType
);
185 //@@todo lazy load plLib
186 eval('var plObj = '+this.srcType
+'Playlist;');
187 //import methods from the plObj to this
188 for(var method
in plObj
){
189 //js parent preservation for local overwritten methods
190 if(this[method
])this['parent_' + method
] = this[method
];
191 this[method
]=plObj
[method
];
192 js_log('inherit:'+ method
);
195 if(typeof this.doParse
!= 'function'){
196 js_log('error: method doParse not found in plObj'+ this.srcType
);
200 if(typeof this.doParse
== 'function'){
201 if( this.doParse() ){
202 this.doWhenParseDone();
204 js_log("error: failed to parse playlist");
206 //error or parse needs to do ajax requests
210 doNativeWarningCheck:function(){
211 var clip
= this.default_track
.clips
[0];
213 return clip
.embed
.doNativeWarningCheck();
216 doWhenParseDone:function(){
217 js_log('f:doWhenParseDone');
218 //do additional init for clips:
221 _this
.clip_ready_count
=0;
222 for( var i
in this.default_track
.clips
){
223 var clip
= this.default_track
.clips
[i
];
224 if(clip
.embed
.load_error
){
225 var error
= clip
.embed
.load_error
;
226 //break on any clip we can't playback:
229 if( clip
.embed
.ready_to_play
){
230 _this
.clip_ready_count
++;
233 //js_log('clip sources count: '+ clip.embed.media_element.sources.length);
234 clip
.embed
.on_dom_swap();
235 if( clip
.embed
.loading_external_data
==false &&
236 clip
.embed
.init_with_sources_loadedDone
==false){
237 clip
.embed
.init_with_sources_loaded();
241 //@@todo for some plugins we have to conform types of clips
242 // ie vlc can play flash _followed_by_ ogg _followed_by_ whatever
244 // native ff 3.1a2 can only play ogg
246 this.load_error
=error
;
248 }else if( _this
.clip_ready_count
== _this
.getClipCount() ){
249 js_log("done init all clips: " + _this
.clip_ready_count
+ ' = ' + _this
.getClipCount());
250 this.doWhenClipLoadDone();
252 js_log("only "+ _this
.clip_ready_count
+" clips done, scheduling callback:");
253 var doParseDoneCheck = function(){
254 _this
.doWhenParseDone();
256 if( !mvJsLoader
.load_error
) //re-issue request if no load error:
257 setTimeout(doParseDoneCheck
, 100);
260 doWhenClipLoadDone:function(){
261 js_log('mvPlaylist:doWhenClipLoadDone');
262 this.ready_to_play
= true;
263 this.loading
= false;
266 getDuration:function( regen
){
267 //js_log("GET PL DURRATION for : "+ this.tracks[this.default_track_id].clips.length + 'clips');
268 if(!regen
&& this.pl_duration
)
269 return this.pl_duration
;
272 $j
.each( this.default_track
.clips
, function( i
, clip
){
274 clip
.dur_offset
= durSum
;
275 //only calculate the solo Duration if a smil clip that could contain a transition:
276 if( clip
.instanceOf
== 'mvSMILClip' ){
277 //don't include transition time (for playlist_swap_loader compatible clips)
278 durSum
+= clip
.getSoloDuration();
280 durSum
+= clip
.getDuration();
283 js_log("ERROR: clip " +clip
.id
+ " not ready");
286 this.pl_duration
=durSum
;
287 //js_log("return dur: " + this.pl_duration);
288 return this.pl_duration
;
290 getTimeReq:function(){
291 //playlist does not really support time request atm ( in theory in the future we could embed playlists with temporal urls)
292 return '0:0:0/' + seconds2npt( this.getDuration() );
294 getDataSource:function(){
295 js_log("f:getDataSource "+ this.src
);
296 //determine the type / first is it m3u or xml?
297 var pl_parent
= this;
298 this.makeURLAbsolute();
300 do_request(this.src
, function(data
){
302 pl_parent
.getSourceType();
306 getSourceType:function(){
307 js_log('data type of: '+ this.src
+ ' = ' + typeof (this.data
) + "\n"+ this.data
);
309 //if not external use different detection matrix
310 if(this.loading_external_data
){
311 if( typeof this.data
== 'object' ){
313 //object assume xml (either xspf or rss)
314 plElm
= this.data
.getElementsByTagName('playlist')[0];
316 if(plElm
.getAttribute('xmlns')=='http://xspf.org/ns/0/'){
317 this.srcType
='xspf';
320 //check itunes style rss "items"
321 rssElm
= this.data
.getElementsByTagName('rss')[0];
323 if(rssElm
.getAttribute('xmlns:itunes')=='http://www.itunes.com/dtds/podcast-1.0.dtd'){
324 this.srcType
='itunes';
327 //check for smil tag:
328 smilElm
= this.data
.getElementsByTagName('smil')[0];
330 //don't check dtd yet.. (have not defined the smil subset)
333 }else if(typeof this.data
== 'string'){
335 //look at the first line:
336 var first_line
= this.data
.substring(0, this.data
.indexOf("\n"));
337 js_log('first line: '+ first_line
);
339 if(first_line
.indexOf('#EXTM3U')!=-1){
340 this.srcType
= 'm3u';
341 }else if(first_line
.indexOf('<smil')!=-1){
342 //@@todo parse string
343 this.srcType
= 'smil';
348 js_log('is of type:'+ this.srcType
);
351 //unknown playlist type
352 js_log('unknown playlist type?');
354 this.innerHTML
= 'error: unknown playlist type at url:<br> ' + this.src
;
356 this.innerHTML
='error: unset src or unknown inline playlist data<br>';
360 //simple function to make a path into an absolute url if its not already
361 makeURLAbsolute:function(){
363 if(this.src
.indexOf('://')==-1){
364 var purl
= parseUri(document
.URL
);
365 if(this.src
.charAt(0)=='/'){
366 this.src
= purl
.protocol
+'://'+ purl
.host
+ this.src
;
368 this.src
= purl
.protocol
+'://'+ purl
.host
+ purl
.directory
+ this.src
;
373 //set up minimal media_element emulation:
376 supports_url_time_encoding
:true
379 //@@todo needs to update for multi-track clip counts
380 getClipCount:function(){
381 return this.default_track
.clips
.length
;
384 //takes in the playlist
385 // inherits all the properties
386 // swaps in the playlist object html/interface div
388 js_log('mvPlaylist:getHTML: loading:' + this.loading
);
390 $j('#'+this.id
).html('loading playlist<blink>...</blink>');
391 if( this.loading_external_data
){
392 //load the data source chain of functions (to update the innerHTML)
393 this.getDataSource();
395 //detect datatype and parse directly:
396 this.getSourceType();
399 //check for empty playlist otherwise renderDisplay:
400 if(this.default_track
.getClipCount()==0){
401 $j(this).html('empty playlist');
404 this.renderDisplay();
408 renderDisplay:function(){
409 js_log('mvPlaylist:renderDisplay:: track length: ' +this.default_track
.getClipCount() );''
412 //setup layout for title and dc_ clip container
415 //add the playlist controls:
417 //append container and videoPlayer;
418 $j(this).html('<div id="dc_'+this.id
+'" style="width:'+this.width
+'px;' +
419 'height:'+(this.height
+this.pl_layout
.title_bar_height
+ this.pl_layout
.control_height
)+'px;position:relative;">' +
421 if(this.controls
==true){
422 //append title & controler:
423 $j('#dc_'+_this
.id
).append(
424 '<div style="font-size:13px;border:solid thin;width:'+this.width
+'px;" id="ptitle_'+this.id
+'"></div>' +
425 '<div class="videoPlayer" style="position:absolute;top:'+(_this
.height
+_this
.pl_layout
.title_bar_height
+4)+'px">' +
426 '<div id="mv_embedded_controls_'+_this
.id
+'" class="ui-widget ui-corner-bottom ui-state-default controls" '+
427 'style="width:' + _this
.width
+ 'px" >' +
428 _this
.getControlsHTML() +
433 //add the play button:
434 $j('#dc_'+_this
.id
).append(
435 this.cur_clip
.embed
.getPlayButton()
437 //once the controls are in the DOM add hooks:
438 ctrlBuilder
.addControlHooks(this);
440 //just append the video:
441 $j('#dc_'+_this
.id
).append(
442 '<div class="videoPlayer" style="position:absolute;top:'+(_this
.height
+_this
.pl_layout
.title_bar_height
+4)+'px"></div>'
445 this.setupClipDisplay();
447 //update the title and status bar
448 this.updateBaseStatus();
450 setupClipDisplay:function(){
451 js_log('mvPlaylist:setupClipDisplay:: clip len:'+ this.default_track
.clips
.length
);
453 $j
.each(this.default_track
.clips
, function(i
, clip
){
454 var cout
= '<div class="clip_container cc_" id="clipDesc_'+clip
.id
+'" '+
455 'style="display:none;position:absolute;text-align: center;width:'+_this
.width
+ 'px;'+
456 'height:'+(_this
.height
)+'px;'+
457 'top:' + this.title_bar_height
+ 'px;left:0px;';
459 cout
+='border:solid thin black;';
462 $j('#dc_'+_this
.id
).append( cout
);
463 //update the embed html:
464 clip
.embed
.height
=_this
.height
;
465 clip
.embed
.width
=_this
.width
;
466 clip
.embed
.play_button
=false;
468 clip
.embed
.getHTML();//get the thubnails for everything
471 'position':"absolute",
475 if($j('#clipDesc_'+clip
.id
).length
!= 0){
476 js_log("should set: #clipDesc_"+clip
.id
+ ' to: ' + $j(clip
.embed
).html() )
477 $j('#clipDesc_'+clip
.id
).append( clip
.embed
);
479 js_log('cound not find: clipDesc_'+clip
.id
);
483 $j('#clipDesc_'+this.cur_clip
.id
).css( { display
:'inline' } );
485 updateThumbPerc:function( perc
){
487 var float_sec
= ( this.getDuration() * perc
);
488 this.updateThumbTime( float_sec
);
490 updateThumbTime:function( float_sec
){
491 //update display & cur_clip:
493 var clip_float_sec
=0;
494 //js_log('seeking clip: ');
495 for(var i
in this.default_track
.clips
){
496 var clip
= this.default_track
.clips
[i
];
497 if( (clip
.getDuration() + pl_sum_time
) >= float_sec
){
498 if(this.cur_clip
.id
!= clip
.id
){
499 $j('#clipDesc_'+this.cur_clip
.id
).hide();
500 this.cur_clip
= clip
;
501 $j('#clipDesc_'+this.cur_clip
.id
).show();
505 pl_sum_time
+=clip
.getDuration();
508 //issue thumbnail update request: (if plugin supports it will render out frame
509 // if not then we do a call to the server to get a new jpeg thumbnail
510 this.cur_clip
.embed
.updateThumbTime( float_sec
- pl_sum_time
);
512 this.cur_clip
.embed
.currentTime
= (float_sec
-pl_sum_time
) + this.cur_clip
.embed
.start_offset
;
513 this.cur_clip
.embed
.seek_time_sec
= (float_sec
-pl_sum_time
);
515 //render effects ontop: (handled by doSmilActions)
516 this.doSmilActions( single_line
= true );
518 updateBaseStatus:function(){
520 js_log('Playlist:updateBaseStatus');
521 $j('#ptitle_'+this.id
).html(''+
522 '<b>' + this.title
+ '</b> '+
523 this.getClipCount()+' clips, <i>'+
524 seconds2npt( this.getDuration() ) + '</i>');
526 //only show the inline edit button if mediaWiki write API is enabled:
528 //should probably be based on if we have a provider api url
529 if( typeof wgEnableWriteAPI
!= 'undefined'){
530 $j( $j
.btnHtml('edit', 'editBtn_'+this.id
, 'pencil',
531 {'style':'position:absolute;right:0;;font-size:x-small;height:10px;margin-bottom:0;padding-bottom:7px;padding-top:0;'} )
536 }).appendTo('#ptitle_'+this.id
);
537 $j('.editBtn_'+this.id
).btnBind();
539 //render out the dividers on the timeline:
540 this.colorPlayHead();
542 this.setStatus( '0:0:00/' + seconds2npt( this.getDuration() ) );
544 /*setStatus override (could call the jquery directly) */
545 setStatus:function(value
){
546 $j('#mv_time_'+this.id
).html( value
);
548 setSliderValue:function(value
){
549 //slider is on 1000 scale:
550 var val
= parseInt( value
*1000 );
551 $j('#mv_play_head_' + this.id
).slider('value', val
);
553 getPlayHeadPos: function(prec_done
){
555 if($j('#mv_seeker_'+this.id
).length
==0){
556 //js_log('no playhead so we can\'t get playhead pos' );
559 var track_len
= $j('#mv_seeker_'+this.id
).css('width').replace(/px/, '');
560 //assume the duration is static and present at .duration during playback
561 var clip_perc
= this.cur_clip
.embed
.duration
/ this.getDuration();
562 var perc_offset
=time_offset
= 0;
563 for(var i
in this.default_track
.clips
){
564 var clip
= this.default_track
.clips
[i
];
565 if(this.cur_clip
.id
==clip
.id
)break;
566 perc_offset
+=(clip
.embed
.duration
/ _this
.getDuration());
567 time_offset
+=clip
.embed
.duration
;
569 //run any update time line hooks:
570 if(this.update_tl_hook
){
571 var cur_time_ms
= time_offset
+ Math
.round(this.cur_clip
.embed
.duration
*prec_done
);
572 if(typeof update_tl_hook
=='function'){
573 this.update_tl_hook(cur_time_ms
);
575 //string type passed use eval:
576 eval(this.update_tl_hook
+'('+cur_time_ms
+');');
580 //handle offset hack @@todo fix so this is not needed:
581 if(perc_offset
> .66)
582 perc_offset
+=( 8/track_len
);
583 //js_log('perc:'+ perc_offset +' c:'+ clip_perc + '*' + prec_done + ' v:'+(clip_perc*prec_done));
584 return perc_offset
+ ( clip_perc
* prec_done
);
586 //attempts to load the embed object with the playlist
587 loadEmbedPlaylist: function(){
588 //js_log('load playlist');
590 /** mannages the loading of future clips
591 * called regurally while we are playing clips
593 * load works like so:
594 * if the current clip is full loaded
595 * load clips untill buffredEndTime < playlist_buffer_time load next
597 * this won't work so well with time range loading for smil (need to work on that)
599 loadFutureClips:function(){
600 /*if( this.cur_clip.embed.bufferedPercent == 1){
601 //set the buffer to the currentTime - duration
602 var curBuffredTime = this.cur_clip.getDuration() - this.cur_clip.embed.currentTime;
604 if(curBuffredTime < 0)
607 js_log( "curBuffredTime:: " + curBuffredTime );
608 if( curBuffredTime < this.playlist_buffer_time ){
609 js_log(" we only have " + curBuffredTime + ' buffed but we need: ' + this.playlist_buffer_time);
611 for(var inx = this.cur_clip.order + 1; inx < this.default_track.clips.length; inx++ ){
612 var cClip = this.default_track.getClip( inx );
614 //check if the clip is already loaded (add its duration)
615 if( cClip.embed.bufferedPercent == 1){
616 curBuffredTime += cClip.embed.getDuration();
618 //check if we still have to load a resource:
619 if( curBuffredTime < this.playlist_buffer_time ){
620 //issue the load request
621 if( cClip.embed.networkState==0 ){
624 break; //check back next time
630 //called to play the next clip if done call onClipDone
631 playNext: function(){
632 //advance the playhead to the next clip
633 var next_clip
= this.getNextClip();
636 js_log('play next with no next clip... must be done:');
640 //@@todo where the plugin supports pre_loading future clips and manage that in javascript
642 this.cur_clip
.embed
.stop();
644 this.updateCurrentClip(next_clip
);
646 this.cur_clip
.embed
.play();
648 onClipDone:function(){
649 js_log("pl onClipDone");
650 this.cur_clip
.embed
.stop();
652 updateCurrentClip:function( new_clip
){
653 js_log('f:updateCurrentClip:'+new_clip
.id
);
654 //make sure we are not switching to the current
655 if( this.cur_clip
.id
== new_clip
.id
){
656 js_log('trying to updateCurrentClip to same clip');
660 //keep the active play clip in sync (stop the other clip)
662 if( !this.cur_clip
.embed
.isStoped() )
663 this.cur_clip
.embed
.stop();
664 this.activeClipList
.remove(this.cur_clip
)
667 this.activeClipList
.add( new_clip
);
670 $j('#clipDesc_'+this.cur_clip
.id
).hide();
671 this.cur_clip
=new_clip
;
672 $j('#clipDesc_'+this.cur_clip
.id
).show();
673 //update the playhead:
674 this.setSliderValue( this.cur_clip
.dur_offset
/ this.getDuration() );
676 playPrev: function(){
677 //advance the playhead to the previous clip
678 var prev_clip
= this.getPrevClip();
680 js_log("tried to play PrevClip with no prev Clip.. setting prev_clip to start clip");
681 prev_clip
= this.start_clip
;
683 //@@todo we could do something fancy like use playlist for sets of clips where supported.
684 // or in cases where the player nativly supports the playlist format we can just pass it in (ie m3u or xspf)
685 if(this.cur_clip
.embed
.supports
['playlist_swap_loader']){
686 //where the plugin supports pre_loading future clips and manage that in javascript
688 this.cur_clip
.embed
.pause();
690 this.updateCurrentClip(prev_clip
);
691 this.cur_clip
.embed
.play();
693 js_log('do prev hard embed swap');
694 this.switchPlayingClip(prev_clip
);
697 switchPlayingClip:function(new_clip
){
698 //swap out the existing embed code for next clip embed code
699 $j('#mv_ebct_'+this.id
).empty();
700 new_clip
.embed
.width
=this.width
;
701 new_clip
.embed
.height
=this.height
;
702 //js_log('set embed to: '+ new_clip.embed.getEmbedObj());
703 $j('#mv_ebct_'+this.id
).html( new_clip
.embed
.getEmbedObj() );
704 this.cur_clip
=new_clip
;
706 this.cur_clip
.embed
.pe_postEmbedJS();
712 //hide the playlist play button:
713 $j('#big_play_link_'+this.id
).hide();
715 //un-pause if paused:
719 //update the control:
720 this.start_clip
= this.cur_clip
;
721 this.start_clip_src
= this.cur_clip
.src
;
723 if(this.cur_clip
.embed
.supports
['playlist_swap_loader'] ){
724 //set the cur_clip to active
725 this.activeClipList
.add(this.cur_clip
);
729 // * mv_playlist smil extension, manages transitions animations overlays etc.
730 //js_log('clip obj supports playlist swap_loader (ie playlist controlled playback)');
731 //@@todo pre-load each clip:
732 //play all active clips (playlist_swap_loader can have more than one clip active)
733 $j
.each(this.activeClipList
.getClipList(), function(inx
, clip
){
736 }else if(this.cur_clip
.embed
.supports
['playlist_driver']){
737 //js_log('playlist_driver');
738 //embedObject is feed the playlist info directly and manages next/prev
739 this.cur_clip
.embed
.playMovieAt( this.cur_clip
.order
);
741 //not much playlist support just play the first clip:
742 //js_log('basic play');
744 this.cur_clip
.embed
.play();
746 //start up the playlist monitor
750 * the load function loads all the clips in order
753 //do nothing right now)
755 toggleMute:function(){
756 this.cur_clip
.embed
.toggleMute();
759 //js_log('f:pause: playlist');
761 this.pauseTime
= this.currentTime
;
763 //js_log('pause time: '+ this.pauseTime + ' call embed pause:');
765 //pause all the active clips:
766 $j
.each(this.activeClipList
.getClipList(), function(inx
, clip
){
770 //@@todo mute across all child clips:
771 toggleMute:function(){
772 var this_id
= (this.pc
!=null)?this.pc
.pp
.id
:this.id
;
775 $j('#volume_control_'+this_id
+ ' span').removeClass('ui-icon-volume-off').addClass('ui-icon-volume-on');
776 $j('#volume_bar_'+this_id
).slider('value', 100);
777 this.updateVolumen(1);
780 $j('#volume_control_'+this_id
+ ' span').removeClass('ui-icon-volume-on').addClass('ui-icon-volume-off');
781 $j('#volume_bar_'+this_id
).slider('value', 0);
782 this.updateVolumen(0);
784 js_log('f:toggleMute::' + this.muted
);
786 updateVolumen:function(perc
){
787 js_log('update volume not supported with current playback type');
789 fullscreen:function(){
790 this.cur_clip
.embed
.fullscreen();
792 //playlist stops playback for the current clip (and resets state for start clips)
795 /*js_log("pl stop:"+ this.start_clip.id + ' c:'+this.cur_clip.id);
797 if(this.start_clip.id!=this.cur_clip.id){
798 //restore clipDesc visibility & hide desc for start clip:
799 $j('#clipDesc_'+this.start_clip.id).html('');
800 this.start_clip.getDetail();
801 $j('#clipDesc_'+this.start_clip.id).css({display:'none'});
802 this.start_clip.setBaseEmbedDim(this.start_clip.embed);
803 //equivalent of base stop
804 $j('#'+this.start_clip.embed.id).html(this.start_clip.embed.getThumbnailHTML());
805 this.start_clip.embed.thumbnail_disp=true;
807 //empty the play-back container
808 $j('#mv_ebct_'+this.id).empty();*/
810 //stop all the clips: monitor:
811 window
.clearInterval( this.smil_monitorTimerId
);
812 /*for (var i=0;i<this.clips.length;i++){
813 var clip = this.clips[i];
816 $j('#clipDesc_'+clip.id).hide();
819 //stop, hide and remove all active clips:
820 $j
.each(this.activeClipList
.getClipList(), function(inx
, clip
){
823 $j('#clipDesc_'+clip
.id
).hide();
824 _this
.activeClipList
.remove(clip
);
827 //set the current clip to the first clip:
829 this.cur_clip
= this.start_clip
;
830 //display the first clip thumb:
831 this.cur_clip
.embed
.stop();
833 $j('#'+this.id
+' .clip_container').hide();
834 //show the first/current clip:
835 $j('#clipDesc_'+this.cur_clip
.id
).show();
837 //reset the currentTime:
838 this.currentTime
= 0;
840 this.setSliderValue( 0 );
841 //FIXME still some issues with "stoping" and reseting the playlist
844 js_log('pl:doSeek:' + v
+ ' sts:' + this.seek_time_sec
);
848 //jump to the clip in the current percent.
850 var next_perc_offset
=0;
851 for(var i
in _this
.default_track
.clips
){
852 var clip
= _this
.default_track
.clips
[i
];
853 next_perc_offset
+=( clip
.getDuration() / _this
.getDuration()) ;
854 //js_log('on ' + clip.getDuration() +' next_perc_offset:'+ next_perc_offset);
855 if( next_perc_offset
> v
){
856 //pass along the relative percentage to embed object:
857 //js_log('seek:'+ v +' - '+perc_offset + ') / (' + next_perc_offset +' - '+ perc_offset);
858 var relative_perc
= (v
-perc_offset
) / (next_perc_offset
- perc_offset
);
859 //update the current clip:
860 _this
.updateCurrentClip( clip
);
862 //update the clip relative seek_time_sec
863 _this
.cur_clip
.embed
.doSeek( relative_perc
);
867 perc_offset
= next_perc_offset
;
870 setCurrentTime: function(pos
, callback
){
871 js_log('pl:setCurrentTime:' + pos
+ ' sts:' + this.seek_time_sec
);
875 //jump to the clip at pos
876 var currentOffset
= 0;
878 for (var i
in _this
.default_track
.clips
) {
879 var clip
= _this
.default_track
.clips
[i
];
880 nextTime
= clip
.getDuration();
881 if (currentOffset
+ nextTime
> pos
) {
882 //update the clip relative seek_time_sec
883 clipTime
= pos
- currentOffset
;
884 if (_this
.cur_clip
.id
!= clip
.id
) {
885 _this
.updateCurrentClip( clip
);
887 _this
.cur_clip
.embed
.setCurrentTime(clipTime
, function(){
891 _this
.currentTime
= pos
;
892 _this
.doSmilActions();
894 currentOffset
+= nextTime
;
897 //gets playlist controls large control height for sporting
898 //next prev button and more status display
899 getControlsHTML:function(){
900 //get controls from current clip (add some playlist specific controls:
901 return ctrlBuilder
.getControls( this );
903 //ads colors/dividers between tracks
904 colorPlayHead: function(){
907 if( !_this
.mv_seeker_width
)
908 _this
.mv_seeker_width
= $j('#mv_play_head_'+_this
.id
).width();
910 if( !_this
.track_len
)
911 _this
.track_len
= $j('#mv_play_head_'+_this
.id
).width();
914 var pl_duration
= _this
.getDuration();
919 //js_log("do play head total dur: "+pl_duration );
920 $j
.each(this.default_track
.clips
, function(i
, clip
){
921 //(use getSoloDuration to not include transitions and such)
922 var perc
= ( clip
.getSoloDuration() / pl_duration
);
923 var pwidth
= Math
.round( perc
* _this
.track_len
);
924 //js_log('pstatus:c:'+ clip.getDuration() + ' of '+ pl_duration+' %:' + perc + ' width: '+ pwidth + ' of total: ' + _this.track_len);
925 //var pwidth = Math.round( perc * _this.track_len - (_this.mv_seeker_width*perc) );
927 //add the buffer child indicator:
928 var barHtml
= '<div id="cl_status_' + clip
.embed
.id
+ '" class="cl_status" style="' +
929 'left:'+cur_pixle
+'px;'+
930 'width:'+pwidth
+ 'px;';
931 //set left or right border based on track pos
932 barHtml
+=( i
== _this
.default_track
.getClipCount()-1 )?
933 'border-left:solid thin black;':
934 'border-right:solid thin black;';
935 barHtml
+= 'filter:alpha(opacity=40);'+
936 '-moz-opacity:.40;">';
938 barHtml
+= ctrlBuilder
.getMvBufferHtml();
942 //background:#DDD +clip.getColor();
944 $j('#mv_play_head_'+_this
.id
).append(barHtml
);
946 //js_log('offset:' + cur_pixle +' width:'+pwidth+' add clip'+ clip.id + ' is '+clip.embed.getDuration() +' = ' + perc +' of ' + _this.track_len);
950 //@@todo currently not really in use
951 setUpHover:function(){
952 js_log('Setup Hover');
953 //set up hover for prev,next
955 var tw
= th
*this.pl_layout
.clip_aspect
;
957 $j('#mv_prev_link_'+_this
.id
+',#mv_next_link_'+_this
.id
).hover(function() {
958 var clip
= (this.id
=='mv_prev_link_'+_this
.id
) ? _this
.getPrevClip() : _this
.getNextClip();
960 return js_log('missing clip for Hover');
961 //get the position of #mv_perv|next_link:
962 var loc
= getAbsolutePos(this.id
);
963 //js_log('Hover: x:'+loc.x + ' y:' + loc.y + ' :'+clip.img);
964 $j("body").append('<div id="mv_Athub" style="position:absolute;' +
965 'top:'+loc
.y
+'px;left:'+loc
.x
+'px;width:'+tw
+'px;height:'+th
+'px;">'+
966 '<img style="border:solid 2px '+clip
.getColor()+';position:absolute;top:0px;left:0px;" width="'+tw
+'" height="'+th
+'" src="'+clip
.img
+'"/>'+
969 $j('#mv_Athub').remove();
972 //@@todo we need to move a lot of this track logic like "cur_clip" to the track Obj
973 // and have the playlist just drive the tracks.
974 getNextClip:function( track
){
976 track
= this.default_track
;
977 var tc
= parseInt(this.cur_clip
.order
) + 1;
979 if( tc
> track
.getClipCount() -1 )
980 return false; // out of range
982 return track
.getClip( tc
);
984 getPrevClip:function( track
) {
986 track
= this.default_track
;
987 var tc
= parseInt(this.cur_clip
.order
) - 1;
990 return track
.getClip( tc
);
993 * generic add Clip to ~default~ track
995 addCliptoTrack: function(clipObj
, pos
){
996 if( typeof clipObj
['track_id'] =='undefined'){
997 var track
= this.default_track
;
999 var track
= this.tracks
[ clipObj
.track_id
]
1001 js_log('add clip:' + clipObj
.id
+' to track: at:' + pos
);
1002 //set the first clip to current (maybe deprecated )
1003 if(clipObj
.order
==0){
1004 if(!this.cur_clip
)this.cur_clip
=clipObj
;
1006 track
.addClip(clipObj
, pos
);
1008 swapClipDesc: function(req_clipID
, callback
){
1009 //hide all but the requested
1011 js_log('r:'+req_clipID
+' cur:'+_this
.id
);
1012 if(req_clipID
==_this
.cur_clip
.id
){
1013 js_log('no swap to same clip');
1017 $j
.each(this.default_track
.clips
, function(i
, clip
){
1018 if(clip
.id
!=req_clipID
){
1019 //fade out if display!=none already
1020 if($j('#clipDesc_'+clip
.id
).css('display')!='none'){
1021 $j('#clipDesc_'+clip
.id
).fadeOut("slow");
1027 //fade in requested clip *and set req_clip to current
1028 $j('#clipDesc_'+req_clipID
).fadeIn("slow", function(){
1029 _this
.cur_clip
= req_clip
;
1035 //this is pretty outdated:
1036 getPLControls: function(){
1037 js_log('getPL cont');
1038 return '<a id="mv_prev_link_'+this.id
+'" title="Previus Clip" onclick="document.getElementById(\''+this.id
+'\').playPrev();return false;" href="#">'+
1039 getTransparentPng({id
:'mv_prev_btn_'+this.id
,style
:'float:left',width
:'27', height
:'27', border
:"0",
1040 src
:mv_skin_img_path
+ 'vid_prev_sm.png' }) +
1042 '<a id="mv_next_link_'+this.id
+'" title="Next Clip" onclick="document.getElementById(\''+this.id
+'\').playNext();return false;" href="#">'+
1043 getTransparentPng({id
:'mv_next_btn_'+this.id
,style
:'float:left',width
:'27', height
:'27', border
:"0",
1044 src
:mv_skin_img_path
+ 'vid_next_sm.png' }) +
1047 run_transition: function( clip_inx
, trans_type
){
1048 if(typeof this.default_track
.clips
[ clip_inx
][ trans_type
] == 'undefined')
1049 clearInterval( this.default_track
.clips
[ clip_inx
].timerId
);
1051 this.default_track
.clips
[ clip_inx
][ trans_type
].run_transition();
1053 playerPixelWidth : function()
1055 var player
= $j('#dc_'+this.id
).get(0);
1056 if(typeof player
!='undefined' && player
['offsetWidth'])
1057 return player
.offsetWidth
;
1059 return parseInt(this.width
);
1061 playerPixelHeight : function()
1063 var player
= $j('#dc_'+this.id
).get(0);
1064 if(typeof player
!='undefined' && player
['offsetHeight'])
1065 return player
.offsetHeight
;
1067 return parseInt(this.height
);
1073 * @videoTrack ... stores clips and layer info
1075 * @clip... each clip segment is a clip object.
1077 var mvClip = function(o
) {
1082 //set up the mvPlaylist object
1083 mvClip
.prototype = {
1085 pp
:null, // parent playlist
1086 order
:null, //the order/array key for the current clip
1097 //init object including pointer to parent
1101 js_log('id is: '+ this.id
);
1103 //setup the embed object:
1104 setUpEmbedObj:function(){
1105 js_log('mvClip:setUpEmbedObj()');
1110 //js_log('setup embed for clip '+ this.id + ':id is a function?');
1111 //set up the pl_mv_embed object:
1112 var init_pl_embed
={id
:'e_'+this.id
,
1113 pc
:this, //parent clip
1117 this.setBaseEmbedDim( init_pl_embed
);
1120 //if in sequence mode hide controls / embed links
1121 // init_pl_embed.play_button=false;
1122 init_pl_embed
.controls
=false;
1123 //if(this.pp.sequencer=='true'){
1124 init_pl_embed
.embed_link
=null;
1125 init_pl_embed
.linkback
=null;
1127 if(this.poster
)init_pl_embed
['thumbnail']=this.poster
;
1129 if( this.type
)init_pl_embed
['type'] = this.type
;
1131 this.embed
= new PlMvEmbed( init_pl_embed
);
1133 //js_log('media Duration:' + this.embed.getDuration() );
1134 //js_log('media element:'+ this.embed.media_element.length);
1135 //js_log('type of embed:' + typeof(this.embed) + ' seq:' + this.pp.sequencer+' pb:'+ this.embed.play_button);
1137 doAdjust:function(side
, delta
){
1138 js_log("f:doAdjust: " + side
+ ' , ' + delta
);
1141 var start_offset
=parseInt(this.embed
.start_offset
)+parseInt(delta
*-1);
1142 this.embed
.updateVideoTime( seconds2npt(start_offset
), seconds2npt ( this.embed
.start_offset
+ this.embed
.getDuration() ) );
1143 }else if(side
=='end'){
1144 var end_offset
= parseInt(this.embed
.start_offset
) + parseInt( this.embed
.getDuration() ) + parseInt(delta
);
1145 this.embed
.updateVideoTime( seconds2npt(this.embed
.start_offset
), seconds2npt(end_offset
) );
1147 //update everything:
1149 /*var base_src = this.src.substr(0,this.src.indexOf('?'));
1150 js_log("delta:"+ delta);
1152 //since we adjust start invert the delta:
1153 var start_offset =parseInt(this.embed.start_offset/1000)+parseInt(delta*-1);
1154 this.src = base_src +'?t='+ seconds2npt(start_offset) +'/'+ this.embed.end_ntp;
1155 }else if(side=='end'){
1156 //put back into seconds for adjustment:
1157 var end_offset = parseInt(this.embed.start_offset/1000) + parseInt(this.embed.duration/1000) + parseInt(delta);
1158 this.src = base_src +'?t='+ this.embed.start_ntp +'/'+ seconds2npt(end_offset);
1160 this.embed.updateVideoTime( this.src );
1162 this.duration = this.embed.getDuration();
1163 this.pp.pl_duration=null;
1164 //update playlist stuff:
1165 this.pp.updateTitle();*/
1168 getDuration:function(){
1169 if(!this.embed
)this.setUpEmbedObj();
1170 return this.embed
.getDuration();
1172 setBaseEmbedDim:function(o
){
1174 //o.height=Math.round(pl_layout.clip_desc*this.pp.height)-2;//give it some padding:
1175 //o.width=Math.round(o.height*pl_layout.clip_aspect)-2;
1176 o
.height
= this.pp
.height
;
1177 o
.width
= this.pp
.width
;
1179 //output the detail view:
1181 /*getDetail:function(){
1182 //js_log('get detail:' + this.pp.title);
1183 var th=Math.round( this.pl_layout.clip_desc * this.pp.height );
1184 var tw=Math.round( th * this.pl_layout.clip_aspect );
1186 var twDesc = (this.pp.width-tw)-2;
1188 if(this.title==null)
1189 this.title='clip ' + this.order + ' ' +this.pp.title;
1191 this.desc=this.pp.desc;
1192 //update the embed html:
1193 this.embed.getHTML();
1195 $j(this.embed).css({ 'position':"absolute",'top':"0px", 'left':"0px"});
1197 //js_log('append child to:#clipDesc_'+this.id);
1198 if($j('#clipDesc_'+this.id).get(0)){
1199 $j('#clipDesc_'+this.id).get(0).appendChild(this.embed);
1201 $j('#clipDesc_'+this.id).append(''+
1202 '<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;">'+
1203 '<b>'+this.title+'</b><br>'+
1204 this.desc + '<br>' +
1205 '<b>clip length:</b> '+ seconds2npt( this.embed.getDuration() );
1209 getTitle:function(){
1210 if(typeof this.title
== 'string')
1213 return 'untitled clip ' + this.order
;
1215 getClipImg:function(start_offset
, size
){
1216 js_log('f:getClipImg ' + start_offset
+ ' s:'+size
);
1218 return mv_default_thumb_url
;
1220 if(!size
&& !start_offset
){
1223 //if a metavid image (has request parameters) use size and time args
1224 if(this.img
.indexOf('?')!=-1){
1225 js_log('get with offset: '+ start_offset
);
1226 var time
= seconds2npt( start_offset
+ (this.embed
.start_offset
/1000) );
1227 js_log("time is: " + time
);
1228 this.img
= this.img
.replace(/t\=[^&]*/gi, "t="+time
);
1229 if(this.img
.indexOf('&size=')!=-1){
1230 this.img
= this.img
.replace(/size=[^&]*/gi, "size="+size
);
1232 this.img
+='&size='+size
;
1239 getColor: function(){
1240 //js_log('get color:'+ num +' : '+ num.toString().substr(num.length-1, 1) + ' : '+colors[ num.toString().substr(num.length-1, 1)] );
1241 var num
= this.id
.substr( this.id
.length
-1, 1);
1243 num
=num
.charCodeAt(0);
1245 if(num
>= 10)num
=num
% 10;
1246 return mv_clip_colors
[num
];
1249 /* mv_embed extensions for playlists */
1250 var PlMvEmbed=function(vid_init
){
1251 //js_log('PlMvEmbed: '+ vid_init.id);
1252 //create the div container
1253 var ve
= document
.createElement('div');
1254 //extend ve with all this
1255 this.init(vid_init
);
1256 for(method
in this){
1257 if(method
!='readyState'){
1258 ve
[method
]= this[method
];
1261 js_log('ve src len:'+ ve
.media_element
.sources
.length
);
1264 //all the overwritten and new methods for playlist extension of baseEmbed
1265 PlMvEmbed
.prototype = {
1266 init:function(vid_init
){
1267 //send embed_video a created video element:
1268 ve
= document
.createElement('div');
1269 for(var i
in vid_init
){
1270 //set the parent clip pointer:
1272 this['pc']=vid_init
['pc'];
1274 ve
.setAttribute(i
,vid_init
[i
]);
1277 var videoInterface
= new embedVideo(ve
);
1278 //inherit the videoInterface
1279 for( method
in videoInterface
){
1280 if(method
!='style'){
1281 if( this[ method
] ){
1282 //parent embed method preservation:
1283 this['pe_'+method
]=videoInterface
[method
];
1285 this[method
]=videoInterface
[method
];
1288 //string -> boolean:
1289 if(this[method
]=="false")this[method
]=false;
1290 if(this[method
]=="true")this[method
]=true;
1293 onClipDone:function(){
1294 js_log('pl onClipDone (should go to next)');
1295 //go to next in playlist:
1296 this.pc
.pp
.playNext();
1299 js_log('pl:do stop');
1300 //set up convenience pointer to parent playlist
1301 var _this
= this.pc
.pp
;
1303 var th
=Math
.round( _this
.pl_layout
.clip_desc
* _this
.height
);
1304 var tw
=Math
.round( th
* _this
.pl_layout
.clip_aspect
);
1306 //run the parent stop:
1308 var pl_height
= (_this
.sequencer
=='true')?_this
.height
+27:_this
.height
;
1313 //js_log('pl eb play');
1314 var _this
= this.pc
.pp
;
1315 //check if we are already playing
1316 if( !this.thumbnail_disp
){
1320 mv_lock_vid_updates
=true;
1323 //do post interface operations
1324 postEmbedJS:function(){
1325 //add playlist clips (if plugin supports it)
1326 if(this.pc
.pp
.cur_clip
.embed
.playlistSupport())
1327 this.pc
.pp
.loadEmbedPlaylist();
1328 //color playlist points (if play_head present)
1329 if(this.pc
.pp
.disp_play_head
)
1330 this.pc
.pp
.colorPlayHead();
1331 //setup hover images (for playhead and next/prev buttons)
1332 this.pc
.pp
.setUpHover();
1333 //call the parent postEmbedJS
1334 this.pe_postEmbedJS();
1335 mv_lock_vid_updates
=false;
1337 getPlayButton:function(){
1338 return this.pe_getPlayButton(this.pc
.pp
.id
);
1340 setStatus:function(value
){
1341 //status updates handled by playlist obj
1343 setSliderValue:function(value
){
1344 js_log('PlMvEmbed:setSliderValue:' + value
);
1345 //setSlider value handled by playlist obj
1354 //for each line not # add as clip
1357 //js_log('data:'+ this.data.toString());
1358 $j
.each(this.data
.split("\n"), function(i
,n
){
1359 //js_log('on line '+i+' val:'+n+' len:'+n.length);
1360 if( n
.charAt(0) != '#' ){
1362 //@@todo make sure its a valid url
1363 //js_log('add url: '+i + ' '+ n);
1364 var cur_clip
= new mvClip({type
:'srcClip',id
:'p_'+this_pl
.id
+'_c_'+inx
,pp
:this_pl
,src
:n
,order
:inx
});
1365 //setup the embed object
1366 cur_clip
.setUpEmbedObj();
1367 js_log('m3uPlaylist len:'+ thisClip
.embed
.media_element
.sources
.length
);
1368 this_pl
.addCliptoTrack(cur_clip
);
1377 var itunesPlaylist
= {
1379 var properties
= { title
:'title', linkback
:'link',
1380 author
:'itunes:author',desc
:'description',
1383 for(i
in properties
){
1384 tmpElm
= this.data
.getElementsByTagName(properties
[i
])[0];
1386 this[i
] = tmpElm
.childNodes
[0].nodeValue
;
1387 //js_log('set '+i+' to '+this[i]);
1390 //image src is nested in itunes rss:
1391 tmpElm
= this.data
.getElementsByTagName('image')[0];
1393 imgElm
= tmpElm
.getElementsByTagName('url')[0];
1395 this.img
= imgElm
.childNodes
[0].nodeValue
;
1399 var clips
= this.data
.getElementsByTagName("item");
1400 properties
.src
= 'guid';
1401 for (var i
=0;i
<clips
.length
;i
++){
1402 var cur_clip
= new mvClip({type
:'srcClip',id
:'p_'+this.id
+'_c_'+i
,pp
:this,order
:i
});
1403 for(var j
in properties
){
1404 tmpElm
= clips
[i
].getElementsByTagName( properties
[j
] )[0];
1406 cur_clip
[j
] = tmpElm
.childNodes
[0].nodeValue
;
1407 //js_log('set clip property: ' + j+' to '+cur_clip[j]);
1411 tmpElm
= clips
[i
].getElementsByTagName('image')[0];
1413 imgElm
= tmpElm
.getElementsByTagName('url')[0];
1415 cur_clip
.img
= imgElm
.childNodes
[0].nodeValue
;
1418 //set up the embed object now that all the values have been set
1419 cur_clip
.setUpEmbedObj();
1421 //add the current clip to the clip list
1422 this.addCliptoTrack(cur_clip
);
1430 * http://www.xspf.org/xspf-v1.html
1434 //js_log('do xsfp parse: '+ this.data.innerHTML);
1435 var properties
= { title
:'title', linkback
:'info',
1436 author
:'creator',desc
:'annotation',
1437 poster
:'image', date
:'date' };
1439 //get the first instance of any of the meta tags (ok that may be the meta on the first clip)
1440 //js_log('do loop on properties:' + properties);
1441 for(i
in properties
){
1442 js_log('on property: '+i
);
1443 tmpElm
= this.data
.getElementsByTagName(properties
[i
])[0];
1445 if(tmpElm
.childNodes
[0]){
1446 this[i
] = tmpElm
.childNodes
[0].nodeValue
;
1447 js_log('set pl property: ' + i
+' to '+this[i
]);
1451 var clips
= this.data
.getElementsByTagName("track");
1452 js_log('found clips:'+clips
.length
);
1453 //add any clip specific properties
1454 properties
.src
= 'location';
1455 for (var i
=0;i
<clips
.length
;i
++){
1456 var cur_clip
= new mvClip({id
:'p_'+this.id
+'_c_'+i
,pp
:this,order
:i
});
1457 //js_log('cur clip:'+ cur_clip.id);
1458 for(var j
in properties
){
1459 tmpElm
= clips
[i
].getElementsByTagName( properties
[j
] )[0];
1461 if( tmpElm
.childNodes
.length
!=0){
1462 cur_clip
[j
] = tmpElm
.childNodes
[0].nodeValue
;
1463 js_log('set clip property: ' + j
+' to '+cur_clip
[j
]);
1467 //add mvClip ref from info link:
1468 if(cur_clip
.linkback
){
1471 mvclippos
= cur_clip
.linkback
.indexOf(mvInx
);
1472 if(mvclippos
!==false){
1473 cur_clip
.mvclip
=cur_clip
.linkback
.substr( mvclippos
+mvInx
.length
);
1476 //set up the embed object now that all the values have been set
1477 cur_clip
.setUpEmbedObj();
1478 //add the current clip to the clip list
1479 this.addCliptoTrack(cur_clip
);
1481 //js_log('done with parse');
1485 /*****************************
1486 * SMIL CODE (could be put into another js file / lazy_loaded for improved basic playlist performance / modularity)
1487 *****************************/
1488 /*playlist driver extensions to the playlist object*/
1489 mvPlayList
.prototype.monitor = function(){
1490 //js_log('pl:monitor');
1491 //if paused stop updates
1493 //clearInterval( this.smil_monitorTimerId );
1496 //js_log("pl check: " + this.currentTime + ' > '+this.getDuration());
1497 //check if we should be done:
1498 if( this.currentTime
> this.getDuration() )
1501 //update the playlist current time:
1502 //check for a trsnOut from the previus clip to subtract
1503 this.currentTime
= this.cur_clip
.dur_offset
+ this.cur_clip
.embed
.relativeCurrentTime();
1506 if(!this.userSlide
){
1507 this.setStatus(seconds2npt(this.currentTime
) + '/' + seconds2npt(this.getDuration()) );
1508 this.setSliderValue( this.currentTime
/ this.getDuration() );
1510 //pre-load any future clips:
1511 this.loadFutureClips();
1514 //status updates are handled by children clips ... playlist mostly manages smil actions
1515 this.doSmilActions();
1517 if( ! this.smil_monitorTimerId
){
1518 if(document
.getElementById(this.id
)){
1519 this.smil_monitorTimerId
= setInterval('$j(\'#'+this.id
+'\').get(0).monitor()', 250);
1523 //handles the rendering of overlays load of future clips (if necessary)
1524 //@@todo could be lazy loaded if necessary
1525 mvPlayList
.prototype.doSmilActions = function( single_frame
){
1526 //js_log('f:doSmilActions: ' + this.cur_clip.id + ' tid: ' + this.cur_clip.transOut );
1527 var offSetTime
= 0; //offset time should let us start a transition later on if we have to.
1528 var _clip
= this.cur_clip
; //setup a local pointer to cur_clip
1531 //do any smil time actions that may change the current clip
1532 if( this.userSlide
){
1533 //current clip set is set via updateThumbTime function
1535 //assume playing and go to next:
1536 if( _clip
.dur
<= _clip
.embed
.currentTime
1537 && _clip
.order
!= _clip
.pp
.getClipCount()-1 ){
1539 js_log('order:' + _clip
.order
+ ' != count:' + ( _clip
.pp
.getClipCount()-1 ) +
1540 ' smil dur: ' + _clip
.dur
+ ' <= curTime: ' + _clip
.embed
.currentTime
+ ' go to next clip..');
1542 _clip
.pp
.playNext();
1545 //@@todo could maybe generalize transIn with trasOut into one "flow" with a few scattered if statements
1546 //update/setup all transitions (will render current transition state)
1548 //pretty similar actions per transition types so group into a loop:
1549 var tran_types
= {'transIn':true,'transOut':true};
1550 for(var tid
in tran_types
){
1551 eval('var tObj = _clip.'+tid
);
1554 //js_log('f:doSmilActions: ' + _clip.id + ' tid:'+tObj.id + ' tclip_id:'+ tObj.pClip.id);
1555 //make sue we are in range:
1556 if( tid
=='transIn' )
1557 in_range
= (_clip
.embed
.currentTime
<= tObj
.dur
)?true:false;
1559 if( tid
=='transOut' )
1560 in_range
= (_clip
.embed
.currentTime
>= (_clip
.dur
- tObj
.dur
))?true:false;
1563 if( this.userSlide
|| single_frame
){
1564 if( tid
=='transIn' )
1565 mvTransLib
.doUpdate(tObj
, (_clip
.embed
.currentTime
/ tObj
.dur
) );
1567 if( tid
=='transOut' )
1568 mvTransLib
.doUpdate(tObj
, (_clip
.embed
.currentTime
-(_clip
.dur
- tObj
.dur
)) /tObj
.dur
);
1571 if( tObj
.animation_state
==0 ){
1572 js_log('init/run_transition ');
1573 tObj
.run_transition();
1577 //close up transition if done & still onDispaly
1578 if( tObj
.overlay_selector_id
){
1579 js_log('close up transition :'+tObj
.overlay_selector_id
);
1580 mvTransLib
.doCloseTransition( tObj
);
1587 * mvTransLib library of transitions
1588 * a single object called to initiate transition effects can easily be extended in separate js file
1589 * /mvTransLib is a all static object no instances of mvTransLib/
1590 * (that way a limited feature set "sequence" need not include a _lot_ of js unless necessary )
1592 * Smil Transition Effects see:
1593 * http://www.w3.org/TR/SMIL3/smil-transitions.html#TransitionEffects-TransitionAttribute
1597 * function doTransition lookups up the transition in the mvTransLib obj
1598 * and init the transition if its available
1599 * @param tObj transition attribute object
1600 * @param offSetTime default value 0 if we need to start rendering from a given time
1602 doInitTransition:function(tObj
){
1603 js_log('mvTransLib:f:doInitTransition');
1605 js_log('transition is missing type attribute');
1610 js_log('transition is missing subtype attribute');
1614 if(!this['type'][tObj
.type
]){
1615 js_log('mvTransLib does not support type: '+tObj
.type
);
1619 if(!this['type'][tObj
.type
][tObj
.subtype
]){
1620 js_log('mvTransLib does not support subType: '+tObj
.subtype
);
1624 //setup overlay_selector_id
1625 if(tObj
.subtype
=='crossfade'){
1626 if(tObj
.transAttrType
=='transIn')
1627 var other_pClip
= tObj
.pClip
.pp
.getPrevClip();
1628 if(tObj
.transAttrType
=='transOut')
1629 var other_pClip
= tObj
.pClip
.pp
.getNextClip();
1631 if(typeof(other_pClip
)=='undefined' || other_pClip
=== false || other_pClip
.id
== tObj
.pClip
.pp
.cur_clip
.id
)
1632 js_log('Error: crossfade without target media asset');
1633 //if not sliding start playback:
1634 if(!tObj
.pClip
.pp
.userSlide
){
1635 other_pClip
.embed
.play();
1636 //manualy ad the extra layer to the activeClipList
1637 tObj
.pClip
.pp
.activeClipList
.add( other_pClip
);
1639 tObj
.overlay_selector_id
= 'clipDesc_'+other_pClip
.id
;
1641 tObj
.overlay_selector_id
=this.getOverlaySelector(tObj
);
1644 //all good call function with tObj param
1645 js_log('should call: '+tObj
.type
+ ' ' + tObj
.subtype
);
1646 this['type'][tObj
.type
][tObj
.subtype
].init(tObj
);
1648 doCloseTransition:function(tObj
){
1649 if(tObj
.subtype
=='crossfade'){
1650 //close up crossfade
1651 js_log("close up crossfade");
1653 $j('#'+tObj
.overlay_selector_id
).remove();
1656 tObj
.overlay_selector_id
=null;
1658 getOverlaySelector:function(tObj
){
1659 var overlay_selector_id
= tObj
.transAttrType
+ tObj
.pClip
.id
;
1660 js_log('f:getOverlaySelector: '+overlay_selector_id
+ ' append to: ' +'#videoPlayer_'+tObj
.pClip
.embed
.id
);
1661 //make sure overlay_selector_id not already here:
1662 if( $j('#'+overlay_selector_id
).length
== 0 ){
1663 $j('#videoPlayer_'+tObj
.pClip
.embed
.id
).prepend(''+
1664 '<div id="'+overlay_selector_id
+'" ' +
1665 'style="position:absolute;top:0px;left:0px;' +
1666 'height:'+parseInt(tObj
.pClip
.pp
.height
)+'px;'+
1667 'width:'+parseInt(tObj
.pClip
.pp
.width
)+'px;' +
1671 return overlay_selector_id
;
1673 doUpdate:function(tObj
, percent
){
1674 //init the transition if nessesary:
1675 if(!tObj
.overlay_selector_id
)
1676 this.doInitTransition(tObj
);
1678 //@@todo we should ensure visability outside of doUpate loop
1679 if(!$j('#'+tObj
.overlay_selector_id
).is(':visible'))
1680 $j('#'+tObj
.overlay_selector_id
).show();
1683 /*js_log('doing update for: '+ tObj.pClip.id +
1684 ' type:' + tObj.transAttrType +
1685 ' t_type:'+ tObj.type +
1686 ' subypte:'+ tObj.subtype +
1687 ' percent:' + percent);*/
1689 this['type'][tObj
.type
][tObj
.subtype
].u(tObj
,percent
);
1691 getTransitionIcon:function( type
, subtype
){
1692 return mv_embed_path
+'/skins/'+mwConfig
['skin_name']+'/transition_images/'+ type
+'_'+ subtype
+ '.png';
1695 * mvTransLib: functional library mapping:
1701 'attr':['fadeColor'],
1702 'init':function(tObj
){
1703 //js_log('f:fadeFromColor: '+tObj.overlay_selector_id +' to color: '+ tObj.fadeColor);
1705 js_log('missing fadeColor');
1706 if($j('#'+tObj
.overlay_selector_id
).length
==0){
1707 js_log("ERROR can't find: "+ tObj
.overlay_selector_id
);
1709 //set the initial state
1710 $j('#'+tObj
.overlay_selector_id
).css({
1711 'background-color':tObj
.fadeColor
,
1715 'u':function(tObj
, percent
){
1716 //js_log(':fadeFromColor:update: '+ percent);
1717 //fade from color (invert the percent)
1718 var percent
= 1- percent
;
1719 $j('#'+tObj
.overlay_selector_id
).css({
1727 "init":function(tObj
){
1728 js_log('f:crossfade: '+tObj
.overlay_selector_id
);
1729 if($j('#'+tObj
.overlay_selector_id
).length
==0)
1730 js_log("ERROR overlay selector not found: "+tObj
.overlay_selector_id
);
1732 //set the initial state show the zero opacity animation
1733 $j('#'+tObj
.overlay_selector_id
).css({'opacity':0}).show();
1735 'u':function(tObj
, percent
){
1736 $j('#'+tObj
.overlay_selector_id
).css({
1745 /* object to manage embedding html with smil timings
1746 * grabs settings from parent clip
1748 var transitionObj = function(element
) {
1751 transitionObj
.prototype = {
1752 supported_attributes
: new Array(
1759 transAttrType
:null, //transIn or transOut
1760 overlay_selector_id
:null,
1763 animation_state
:0, //can be 0=unset, 1=running, 2=done
1764 interValCount
:0, //inter-intervalCount for animating between time updates
1765 dur
:2, //default duration of 2
1766 init:function(element
){
1767 //load supported attributes:
1769 $j
.each(this.supported_attributes
, function(i
, attr
){
1770 if(element
.getAttribute(attr
))
1771 _this
[attr
]= element
.getAttribute(attr
);
1773 //@@todo process duration (for now just strip s) per:
1774 //http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
1776 _this
.dur
= smilParseTime(_this
.dur
);
1779 * returns a visual representation of the transition
1781 getIconSrc:function(opt
){
1782 //@@todo support some arguments
1783 return mvTransLib
.getTransitionIcon(this.type
, this.subtype
);
1785 getDuration:function(){
1788 //returns the values of supported_attributes:
1789 getAttributeObj:function(){
1791 for(var i
in this.supported_attributes
){
1792 var attr
= this.supported_attributes
[i
];
1794 elmObj
[ attr
] = this[attr
];
1799 * the main animation loop called every MV_ANIMATION_CB_RATE or 34ms ~around 30frames per second~
1801 run_transition:function(){
1802 //js_log('f:run_transition:' + this.interValCount);
1804 //update the time from the video if native:
1805 if(typeof this.pClip
.embed
.vid
!='undefined'){
1806 this.interValCount
=0;
1807 this.pClip
.embed
.currentTime
= this.pClip
.embed
.vid
.currentTime
;
1811 //relay on currentTime update grabs (every 250ms or so) (ie for images)
1812 // if(this.prev_curtime!=this.pClip.embed.currentTime){
1813 // this.prev_curtime = this.pClip.embed.currentTime;
1814 // this.interValCount=0;
1817 //start_time =asigned by doSmilActions
1818 //base_cur_time = pClip.embed.currentTime;
1819 //dur = asigned by attribute
1820 if(this.animation_state
==0){
1821 mvTransLib
.doInitTransition(this);
1822 this.animation_state
=1;
1824 //set percentage include difrence of currentTime to prev_curTime
1825 // ie updated in-between currentTime updates)
1827 if(this.transAttrType
=='transIn')
1828 var percentage
= ( this.pClip
.embed
.currentTime
+
1829 ( (this.interValCount
*MV_ANIMATION_CB_RATE
)/1000 )
1832 if(this.transAttrType
=='transOut')
1833 var percentage
= (this.pClip
.embed
.currentTime
+
1834 ( (this.interValCount
*MV_ANIMATION_CB_RATE
)/1000 )
1835 - (this.pClip
.dur
- this.dur
)
1838 /*js_log('percentage = ct:'+this.pClip.embed.currentTime + ' + ic:'+this.interValCount +' * cb:'+MV_ANIMATION_CB_RATE +
1839 ' / ' + this.dur + ' = ' + percentage );
1842 //js_log('cur percentage of transition: '+percentage);
1843 //update state based on current time + cur_time_offset (for now just use pClip.embed.currentTime)
1844 mvTransLib
.doUpdate(this, percentage
);
1846 if( percentage
>= 1 ){
1847 js_log("transition done update with percentage "+percentage
);
1848 this.animation_state
=2;
1849 clearInterval(this.timerId
);
1850 mvTransLib
.doCloseTransition(this)
1854 this.interValCount
++;
1855 //setInterval in we are still in running state and user is not using the playhead
1856 if( this.animation_state
==1 ){
1858 this.timerId
= setInterval('document.getElementById(\'' + this.pClip
.pp
.id
+ '\').'+
1859 'run_transition(\'' + this.pClip
.pp
.cur_clip
.order
+ '\','+
1860 '\''+ this.transAttrType
+ '\')',
1861 MV_ANIMATION_CB_RATE
);
1864 clearInterval(this.timerId
);
1869 var cObj
= new this.constructor();
1876 //very limited smile feature set more details soon:
1877 //region="video_region" transIn="fromGreen" begin="2s"
1878 //http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#edef-ref
1883 js_log('f:doParse smilPlaylist');
1884 //@@todo get/parse meta that we are intersted in:
1885 var meta_tags
= this.data
.getElementsByTagName('meta');
1894 $j
.each(meta_tags
, function(i
,meta_elm
){
1895 //js_log( "on META tag: "+ $j(meta_elm).attr('name') );
1896 if( $j(meta_elm
).attr('name') in metaNames
){
1897 _this
[ $j(meta_elm
).attr('name') ] = $j(meta_elm
).attr('content');
1899 //special check for wikiDesc
1900 if( $j(meta_elm
).attr('name') == 'wikiDesc'){
1901 if(meta_elm
.firstChild
)
1902 _this
.wikiDesc
= meta_elm
.firstChild
.nodeValue
;
1905 //add transition objects:
1906 var transition_tags
= this.data
.getElementsByTagName('transition');
1907 $j
.each(transition_tags
, function( i
, trans_elm
){
1908 if( $j(trans_elm
).attr("id") ){
1909 _this
.transitions
[ $j(trans_elm
).attr("id")]= new transitionObj( trans_elm
);
1911 js_log('skipping transition: (missing id) ' + trans_elm
);
1914 js_log('loaded transitions:' + _this
.transitions
.length
);
1915 //add seq (latter we will have support more than one seq tag) / more than one "track"
1916 var seq_tags
= this.data
.getElementsByTagName('seq');
1917 $j
.each(seq_tags
, function(i
,seq_elm
){
1919 //get all the clips for the given seq:
1920 $j
.each(seq_elm
.childNodes
, function(i
, mediaElement
){
1921 //~complex~ @@todo to handlde a lot like "switch" "region" etc
1922 //js_log('process: ' + mediaElemnt.tagName);
1923 if(typeof mediaElement
.tagName
!='undefined'){
1924 if( _this
.tryAddMedia( mediaElement
, inx
) ){
1930 js_log("done proc seq tags");
1933 tryAddMediaObj:function(mConfig
, order
, track_id
){
1934 js_log('tryAddMediaObj::');
1935 var mediaElement
= document
.createElement('div');
1936 for(var i
=0; i
< mv_smil_ref_supported_attributes
.length
;i
++){
1937 var attr
= mv_smil_ref_supported_attributes
[i
];
1939 $j(mediaElement
).attr( attr
, mConfig
[attr
] );
1941 this.tryAddMedia(mediaElement
, order
, track_id
);
1943 tryAddMedia:function(mediaElement
, order
, track_id
){
1944 js_log('SMIL:tryAddMedia:' + mediaElement
);
1946 //set up basic mvSMILClip send it the mediaElemnt & mvClip init:
1949 "id":'p_' + _this
.id
+ '_c_' + order
,
1950 "pp":this, //set the parent playlist object pointer
1953 var clipObj
= new mvSMILClip(mediaElement
, cConfig
);
1955 //set optional params track
1956 if( typeof track_id
!= 'undefined')
1957 clipObj
["track_id"] = track_id
;
1962 clipObj
.setUpEmbedObj();
1963 //inhreit embedObject (only called on "new media"
1964 clipObj
.embed
.init_with_sources_loaded();
1965 //add clip to track:
1966 this.addCliptoTrack( clipObj
, order
);
1970 //@@todo we could throw error details here once we integrate try catches everywhere :P
1974 //http://www.w3.org/TR/2007/WD-SMIL3-20070713/smil-extended-media-object.html#smilMediaNS-BasicMedia
1975 //and added resource description elements
1976 //@@ supporting the "ID" attribute turns out to be kind of tricky since we use it internally
1977 // (for now don't include)
1978 var mv_smil_ref_supported_attributes
= new Array(
1987 //some custom attributes:
1992 /* extension to mvClip to support smil properties */
1993 var mvSMILClip=function(sClipElm
, mvClipInit
){
1994 return this.init(sClipElm
, mvClipInit
);
1996 //all the overwritten and new methods for SMIL extension of mv_embed
1997 mvSMILClip
.prototype = {
1998 instanceOf
:'mvSMILClip',
1999 params
: {}, //support param as child of ref clips per SMIL spec
2000 init:function(sClipElm
, mvClipInit
){
2003 //make new mvCLip with ClipInit vals
2004 var myMvClip
= new mvClip( mvClipInit
);
2006 for(var method
in myMvClip
){
2007 if(typeof this[method
] != 'undefined' ){
2008 this['parent_'+method
]=myMvClip
[method
];
2010 this[method
] = myMvClip
[method
];
2014 //get supported media attr init non-set
2015 for(var i
=0; i
< mv_smil_ref_supported_attributes
.length
;i
++){
2016 var attr
= mv_smil_ref_supported_attributes
[i
];
2017 if( $j(sClipElm
).attr(attr
)){
2018 _this
[attr
] = $j(sClipElm
).attr(attr
);
2021 this['tagName'] = sClipElm
.tagName
;
2023 if( sClipElm
.firstChild
){
2024 this['wholeText'] = sClipElm
.firstChild
.nodeValue
;
2025 js_log("SET wholeText for: " + this['tagName'] + ' '+ this['wholeText']);
2028 //mv_embed specific property:
2029 if( $j(sClipElm
).attr('poster') )
2030 this['img'] = $j(sClipElm
).attr('poster');
2032 //lookup and assign copies of transitions
2033 // (since transition needs to hold some per-instance state info)
2034 if(this.transIn
&& this.pp
.transitions
[ this.transIn
]){
2035 this.transIn
= this.pp
.transitions
[ this.transIn
].clone();
2036 this.transIn
.pClip
= _this
;
2037 this.transIn
.transAttrType
='transIn';
2040 if(this.transOut
&& this.pp
.transitions
[ this.transOut
]){
2041 this.transOut
= this.pp
.transitions
[ this.transOut
].clone();
2042 this.transOut
.pClip
= _this
;
2043 this.transOut
.transAttrType
= 'transOut';
2045 //parse duration / begin times:
2047 this.dur
= smilParseTime( this.dur
);
2048 //parse the media duration hint ( the source media length)
2049 if( this.durationHint
)
2050 this.durationHint
= smilParseTime( this.durationHint
);
2052 //conform type to vido/ogg:
2053 if( this.type
== 'application/ogg' )
2054 this.type
= 'video/ogg'; //conform to 'video/ogg' type
2056 //if unset type and we have innerHTML assume text/html type
2057 if( !this.type
&& this.wholeText
){
2058 this.type
= 'text/html';
2060 //also grab andy child param elements if present:
2061 if( sClipElm
.getElementsByTagName('param')[0] ){
2062 for(var i
=0; i
< sClipElm
.getElementsByTagName('param').length
; i
++){
2063 this.params
[ sClipElm
.getElementsByTagName('param')[i
].getAttribute("name") ] =
2064 sClipElm
.getElementsByTagName('param')[i
].firstChild
.nodeValue
;
2069 //returns the values of supported_attributes:
2070 getAttributeObj:function(){
2072 for(var i
=0; i
< mv_smil_ref_supported_attributes
.length
; i
++){
2073 var attr
= mv_smil_ref_supported_attributes
[i
];
2075 elmObj
[ attr
] = this[attr
];
2081 * @returns duration in int
2083 getDuration:function(){
2084 //check for smil dur:
2087 return this.embed
.getDuration();
2089 //gets the duration of the clip subracting transitions
2090 getSoloDuration:function(){
2091 var fulldur
= this.getDuration();
2092 //see if we need to subtract from time eating transitions (transOut)
2094 fulldur
-= this.transOut
.getDuration();
2096 //js_log("getSoloDuration:: td: " + this.getDuration() + ' sd:' + fulldur);
2099 //gets the duration of the original media asset (usefull for bounding setting of in-out-points)
2100 getSourceDuration:function(){
2101 if( this.durationHint
)
2102 return this.durationHint
;
2103 //if we have no source duration just return the media dur:
2104 return this.getDuration();
2109 * @time_str input time string
2110 * returns time in seconds
2112 * @@todo process duration (for now just srip s) per:
2113 * http://www.w3.org/TR/SMIL3/smil-timing.html#Timing-ClockValueSyntax
2114 * (probably have to use a Time object to fully support the smil spec
2116 function smilParseTime( time_str
){
2117 time_str
= time_str
+ '';
2118 //first check for hh:mm:ss time:
2119 if(time_str
.split(':').length
== 3){
2120 return npt2seconds(time_str
);
2122 //assume 34s secconds representation
2123 return parseInt(time_str
.replace('s', ''));
2126 //stores a list pointers to active clips (maybe this should just be a property of clips (but results in lots of seeks)
2127 var activeClipList = function(){
2130 activeClipList
.prototype = {
2132 this.clipList
= new Array();
2134 add:function( clip
){
2135 //make sure the clip is not already active:
2136 for(var i
=0;i
< this.clipList
.lenght
; i
++){
2137 var active_clip
= this.clipList
[i
];
2138 if(clip
.id
== active_clip
.id
) //clip already active:
2141 this.clipList
.push( clip
);
2144 remove:function( clip
){
2145 for(var i
= 0; i
< this.clipList
.length
; i
++){
2146 var active_clip
= this.clipList
[i
];
2147 if(clip
.id
== active_clip
.id
){
2148 this.clipList
.splice(i
, 1);
2154 getClipList:function(){
2155 return this.clipList
;
2158 var trackObj = function( iObj
){
2159 return this.init( iObj
);
2161 var supported_track_attr
=
2162 trackObj
.prototype = {
2163 //should be something like "seq" per SMIL spec
2164 //http://www.w3.org/TR/SMIL3/smil-timing.html#edef-seq
2165 // but we don't really support anywhere near the full concept of seq containers yet either
2166 supported_attributes
: new Array(
2171 disp_mode
:'timeline_thumb',
2172 init : function(iObj
){
2175 //make sure clips is new:
2176 this.clips
= new Array();
2179 $j
.each(this.supported_attributes
, function(i
, attr
){
2181 _this
[attr
] = iObj
[attr
];
2184 //returns the values of supported_attributes:
2185 getAttributeObj:function(){
2187 for(var i
in this.supported_attributes
){
2188 var attr
= this.supported_attributes
[i
];
2190 elmObj
[ attr
] = this[attr
];
2194 addClip:function(clipObj
, pos
){
2195 js_log('pl_Track: AddClip at:' + pos
+ ' clen: ' + this.clips
.length
);
2196 if( typeof pos
== 'undefined' )
2197 pos
= this.clips
.length
;
2198 //get everything after pos
2199 this.clips
.splice(pos
, 0, clipObj
);
2200 //keep the clip order values accurate:
2201 this.reOrderClips();
2202 js_log("did add now cLen: " + this.clips
.length
);
2204 getClip:function( inx
){
2205 if( !this.clips
[inx
] )
2207 return this.clips
[inx
];
2209 reOrderClips:function(){
2210 for(var k
in this.clips
){
2211 this.clips
[k
].order
=k
;
2214 getClipCount:function(){
2215 return this.clips
.length
;
2217 inheritEmbedObj: function(){
2218 $j
.each(this.clips
, function(i
, clip
){
2219 clip
.embed
.inheritEmbedObj();
2224 /* utility functions
2225 * (could be combined with other stuff)
2227 function getAbsolutePos(objectId
) {
2228 // Get an object left position from the upper left viewport corner
2229 o
= document
.getElementById(objectId
);
2230 oLeft
= o
.offsetLeft
; // Get left position from the parent object
2231 while(o
.offsetParent
!=null) { // Parse the parent hierarchy up to the document element
2232 oParent
= o
.offsetParent
// Get parent object reference
2233 oLeft
+= oParent
.offsetLeft
// Add parent left position
2236 o
= document
.getElementById(objectId
);
2238 while(o
.offsetParent
!=null) { // Parse the parent hierarchy up to the document element
2239 oParent
= o
.offsetParent
// Get parent object reference
2240 oTop
+= oParent
.offsetTop
// Add parent top position
2243 return {x
:oLeft
,y
:oTop
};
2245 String
.prototype.htmlEntities = function(){
2246 var chars
= new Array ('&','à','á','â','ã','ä','å','æ','ç','è','é',
2247 'ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô',
2248 'õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','À',
2249 'Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë',
2250 'Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö',
2251 'Ø','Ù','Ú','Û','Ü','Ý','Þ','€','\"','ß','<',
2252 '>','¢','£','¤','¥','¦','§','¨','©','ª','«',
2253 '¬','','®','¯','°','±','²','³','´','µ','¶',
2254 '·','¸','¹','º','»','¼','½','¾');
2256 var entities
= new Array ('amp','agrave','aacute','acirc','atilde','auml','aring',
2257 'aelig','ccedil','egrave','eacute','ecirc','euml','igrave',
2258 'iacute','icirc','iuml','eth','ntilde','ograve','oacute',
2259 'ocirc','otilde','ouml','oslash','ugrave','uacute','ucirc',
2260 'uuml','yacute','thorn','yuml','Agrave','Aacute','Acirc',
2261 'Atilde','Auml','Aring','AElig','Ccedil','Egrave','Eacute',
2262 'Ecirc','Euml','Igrave','Iacute','Icirc','Iuml','ETH','Ntilde',
2263 'Ograve','Oacute','Ocirc','Otilde','Ouml','Oslash','Ugrave',
2264 'Uacute','Ucirc','Uuml','Yacute','THORN','euro','quot','szlig',
2265 'lt','gt','cent','pound','curren','yen','brvbar','sect','uml',
2266 'copy','ordf','laquo','not','shy','reg','macr','deg','plusmn',
2267 'sup2','sup3','acute','micro','para','middot','cedil','sup1',
2268 'ordm','raquo','frac14','frac12','frac34');
2271 for (var i
= 0; i
< chars
.length
; i
++)
2273 myRegExp
= new RegExp();
2274 myRegExp
.compile(chars
[i
],'g')
2275 newString
= newString
.replace (myRegExp
, '&' + entities
[i
] + ';');