refactored get token to mv_embed global function
[lhc/web/wiklou.git] / js2 / mwEmbed / libEmbedVideo / embedVideo.js
1 /* the base video control JSON object with default attributes
2 * for supported attribute details see README
3 */
4
5 loadGM({
6 "loading_plugin" : "loading plugin<blink>...</blink>",
7
8 "select_playback" : "Set Playback Preference",
9 "link_back" : "Link Back",
10 "error_load_lib" : "mv_embed: Unable to load required javascript libraries\n insert script via DOM has failed, try reloading? ",
11
12 "error_swap_vid" : "Error:mv_embed was unable to swap the video tag for the mv_embed interface",
13
14 "add_to_end_of_sequence" : "Add to End of Sequence",
15
16 "missing_video_stream" : "The video file for this stream is missing",
17
18 "play_clip" : "Play Clip",
19 "pause_clip": "Pause Clip",
20 "volume_control": "Volume Control",
21 "player_options": "Player Options",
22 "closed_captions": "Close Captions",
23 "player_fullscreen": "Fullscreen",
24
25 "next_clip_msg" : "Play Next Clip",
26 "prev_clip_msg" : "Play Previous Clip",
27 "current_clip_msg" : "Continue Playing this Clip",
28 "seek_to" : "Seek to",
29
30 "download_segment" : "Download Selection:",
31 "download_full" : "Download Full Video File:",
32 "download_right_click": "To download right click and select <i>save target as</i>",
33 "download_clip" : "Download the Clip",
34 "download_text" : "Download Text (<a style=\"color:white\" title=\"cmml\" href=\"http://wiki.xiph.org/index.php/CMML\">cmml</a> xml):",
35
36 "clip_linkback" : "Clip Source Page",
37
38 "mv_ogg-player-vlc-mozilla" : "VLC Plugin",
39 "mv_ogg-player-videoElement" : "Native Ogg Video Support",
40 "mv_ogg-player-vlc-activex" : "VLC ActiveX",
41 "mv_ogg-player-oggPlugin" : "Generic Ogg Plugin",
42 "mv_ogg-player-quicktime-mozilla" : "Quicktime Plugin",
43 "mv_ogg-player-quicktime-activex" : "Quicktime ActiveX",
44 "mv_ogg-player-cortado" : "Java Cortado",
45 "mv_ogg-player-flowplayer" : "Flowplayer",
46 "mv_ogg-player-selected" : " (selected)",
47 "mv_ogg-player-omtkplayer" : "OMTK Flash Vorbis",
48 "mv_generic_missing_plugin" : "You browser does not appear to support playback type: <b>$1</b><br> visit the <a href=\"http://commons.wikimedia.org/wiki/Commons:Media_help\">Playback Methods</a> page to download a player<br>",
49
50 "mv_for_best_experience": "For a better video playback experience we recommend <b><a href=\"http://www.mozilla.com/en-US/firefox/upgrade.html?from=mv_embed\">Firefox 3.5</a></b>",
51 "mv_do_not_warn_again": "Do not warn me again."
52
53 });
54
55 var default_video_attributes = {
56 "id":null,
57 "class":null,
58 "style":null,
59 "name":null,
60 "innerHTML":null,
61 "width":"320",
62 "height":"240",
63
64 //video attributes:
65 "src":null,
66 "autoplay":false,
67 "start":0,
68 "end":null,
69 "controls":true,
70 "muted":false,
71
72 //roe url (for xml based metadata)
73 "roe":null,
74 //if roe includes metadata tracks we can expose a link to metadata
75 "show_meta_link":true,
76
77 //default state attributes per html5 spec:
78 //http://www.whatwg.org/specs/web-apps/current-work/#video)
79 "paused":true,
80 "readyState":0, //http://www.whatwg.org/specs/web-apps/current-work/#readystate
81 "currentTime":0, //current playback position (should be updated by plugin)
82 "duration":null, //media duration (read from file or the temporal url)
83 "networkState":0,
84
85 "startOffset":null, //if serving an ogg_chop segment use this to offset the presentation time
86
87 //custom attributes for mv_embed:
88 "play_button":true,
89 "thumbnail":null,
90 "linkback":null,
91 "embed_link":true,
92 "download_link":true,
93 "type":null //the content type of the media
94 };
95 /*
96 * the base source attibute checks
97 */
98 var mv_default_source_attr= new Array(
99 'id',
100 'src',
101 'title',
102 'URLTimeEncoding', //boolean if we support temporal url requests on the source media
103 'startOffset',
104 'durationHint',
105 'start',
106 'end',
107 'default',
108 'lang'
109 );
110 /*
111 * Converts all occurrences of <video> tag into video object
112 */
113 function mv_video_embed(swap_done_callback, force_id){
114 mvEmbed.init( swap_done_callback, force_id );
115 }
116 mvEmbed = {
117 //flist stores the set of functions to run after the video has been swaped in.
118 flist:new Array(),
119 init:function( swap_done_callback, force_id ){
120
121 if(swap_done_callback)
122 mvEmbed.flist.push( swap_done_callback );
123
124 //get mv_embed location if it has not been set
125 js_log('mv_embed ' + MV_EMBED_VERSION);
126
127 var loadPlaylistLib=false;
128
129 var eAction = function(this_elm){
130 js_log( "Do SWAP: " + $j(this_elm).attr("id") + ' tag: '+ this_elm.tagName.toLowerCase() );
131
132 if( $j(this_elm).attr("id") == '' ){
133 $j(this_elm).attr("id", 'v'+ global_player_list.length);
134 }
135 //stre a global reference to the id
136 global_player_list.push( $j(this_elm).attr("id") );
137 //if video doSwap
138 switch( this_elm.tagName.toLowerCase()){
139 case 'video':
140 var videoInterface = new embedVideo(this_elm);
141 mvEmbed.swapEmbedVideoElement( this_elm, videoInterface );
142 break;
143 case 'audio':
144 var videoInterface = new embedVideo(this_elm);
145 videoInterface.type ='audio';
146 mvEmbed.swapEmbedVideoElement( this_elm, videoInterface );
147 break;
148 case 'playlist':
149 loadPlaylistLib=true;
150 break;
151 }
152 }
153
154 if( force_id == null && force_id != '' ){
155 var j_selector = 'video,audio,playlist';
156 }else{
157 var j_selector = '#'+force_id;
158 }
159 //process selected elements:
160 //ie8 does not play well with the jQuery video,audio,playlist selector use native:
161 if($j.browser.msie && $j.browser.version >= 8){
162 jtags = j_selector.split(',');
163 for( var i=0; i < jtags.length; i++){
164 $j( document.getElementsByTagName( jtags[i] )).each(function(){
165 eAction(this);
166 });
167 }
168 }else{
169 $j( j_selector ).each(function(){
170 eAction(this);
171 });
172 }
173 if(loadPlaylistLib){
174 mvJsLoader.doLoad([
175 'mvPlayList',
176 '$j.ui', //include dialog for pop-ing up thigns
177 '$j.ui.dialog'
178 ], function(){
179 $j('playlist').each(function(){
180 //create new playlist interface:
181 var plObj = new mvPlayList( this );
182 mvEmbed.swapEmbedVideoElement(this, plObj);
183 var added_height = plObj.pl_layout.title_bar_height + plObj.pl_layout.control_height;
184 //move into a blocking display container with height + controls + title height:
185 $j('#'+plObj.id).wrap('<div style="display:block;height:' + (plObj.height + added_height) + 'px;"></div>');
186 });
187 });
188 }
189 this.checkClipsReady();
190 },
191 /*
192 * swapEmbedVideoElement
193 * takes a video element as input and swaps it out with
194 * an embed video interface based on the video_elements attributes
195 */
196 swapEmbedVideoElement:function(video_element, videoInterface){
197 js_log('do swap ' + videoInterface.id + ' for ' + video_element);
198 embed_video = document.createElement('div');
199 //make sure our div has a hight/width set:
200
201 $j(embed_video).css({
202 'width':videoInterface.width,
203 'height':videoInterface.height
204 }).html( mv_get_loading_img() );
205 //inherit the video interface
206 for(var method in videoInterface){ //for in loop oky in Element context
207 if(method!='readyState'){ //readyState crashes IE
208 if(method=='style'){
209 embed_video.setAttribute('style', videoInterface[method]);
210 }else if(method=='class'){
211 if( $j.browser.msie )
212 embed_video.setAttribute("className", videoInterface['class']);
213 else
214 embed_video.setAttribute("class", videoInterface['class']);
215 }else{
216 //normal inherit:
217 embed_video[method]=videoInterface[method];
218 }
219 }
220 //string -> boolean:
221 if(embed_video[method]=="false")embed_video[method]=false;
222 if(embed_video[method]=="true")embed_video[method]=true;
223 }
224 ///js_log('did vI style');
225 //now swap out the video element for the embed_video obj:
226 $j(video_element).after(embed_video).remove();
227 //js_log('did swap');
228 $j('#'+embed_video.id).get(0).on_dom_swap();
229 // now that "embed_video" is stable, do more initialization (if we are ready)
230 if($j('#'+embed_video.id).get(0).loading_external_data==false &&
231 $j('#'+embed_video.id).get(0).init_with_sources_loadedDone==false){
232 //load and set ready state since source are available:
233 $j('#'+embed_video.id).get(0).init_with_sources_loaded();
234 }
235 js_log('done with child: ' + embed_video.id + ' len:' + global_player_list.length);
236 return true;
237 },
238 //this should not be needed.
239 checkClipsReady : function(){
240 //js_log('checkClipsReady');
241 var is_ready=true;
242 for(var i=0; i < global_player_list.length; i++){
243 if( $j('#'+global_player_list[i]).length !=0){
244 var cur_vid = $j('#'+global_player_list[i]).get(0);
245 is_ready = ( cur_vid.ready_to_play ) ? is_ready : false;
246 if( !is_ready && cur_vid.load_error ){
247 is_ready=true;
248 $j(cur_vid).html( cur_vid.load_error );
249 }
250 }
251 }
252 if( is_ready ){
253 mvEmbed.allClipsReady = true;
254 // run queued functions
255 //js_log('run queded functions:' + mvEmbed.flist[0]);
256 mvEmbed.runFlist();
257 }else{
258 setTimeout( 'mvEmbed.checkClipsReady()', 25 );
259 }
260 },
261 runFlist:function(){
262 while (this.flist.length){
263 this.flist.shift()();
264 }
265 }
266 }
267
268 /*
269 * controlsBuilder:
270 *
271 */
272 var ctrlBuilder = {
273 height:29,
274 supports:{
275 'options':true,
276 'borders':true
277 },
278 getControls:function( embedObj ){
279 js_log('f:controlsBuilder:: opt:' + this.options);
280 this.id = (embedObj.pc)?embedObj.pc.pp.id:embedObj.id;
281 this.available_width = embedObj.playerPixelWidth();
282 //make pointer to the embedObj
283 this.embedObj =embedObj;
284 var _this = this;
285 $j.each( embedObj.supports, function( i, sup ){
286 _this.supports[i] = embedObj.supports[i];
287 });
288
289 //special case vars:
290 if( ( embedObj.roe || embedObj.media_element.timedTextSources() )
291 && embedObj.show_meta_link )
292 this.supports['closed_captions']=true;
293
294
295 //append options to body (if not already there)
296 if($j('#mv_vid_options_'+ctrlBuilder.id).length==0)
297 $j('body').append( this.components['mv_embedded_options'].o() );
298
299 var o='';
300 for( var i in this.components ){
301 if( this.supports[i] ){
302 if( this.available_width > this.components[i].w ){
303 //special case with playhead don't add unless we have 60px
304 if( i == 'play_head' && ctrlBuilder.available_width < 60 )
305 continue;
306 o+=this.components[i].o();
307 this.available_width -= this.components[i].w;
308 }else{
309 js_log('not enough space for control component:' + i);
310 }
311 }
312 }
313 return o;
314 },
315 /*
316 * addControlHooks
317 * to be run once controls are attached to the dom
318 */
319 addControlHooks:function(embedObj){
320 //add in drag/seek hooks:
321 if(!embedObj.base_seeker_slider_offset && $j('#mv_seeker_slider_'+embedObj.id).get(0))
322 embedObj.base_seeker_slider_offset = $j('#mv_seeker_slider_'+embedObj.id).get(0).offsetLeft;
323
324 //js_log('looking for: #mv_seeker_slider_'+embedObj.id + "\n " +
325 // 'start sec: '+embedObj.start_time_sec + ' base offset: '+embedObj.base_seeker_slider_offset);
326
327 //add play hook:
328 $j('#mv_play_pause_button_' + embedObj.id).unbind().btnBind().click(function(){
329 $j('#' + embedObj.id).get(0).play();
330 })
331
332 //big_play_link_ play binding:
333 $j('#big_play_link_' + embedObj.id).unbind().click(function(){
334 $j('#' + embedObj.id).get(0).play();
335 });
336
337 //add recomend firefox if non-native playback:
338 if( embedObj.doNativeWarningCheck() ){
339 $j('#dc_'+ embedObj.id).hover(
340 function(){
341 if($j('#gnp_' + embedObj.id).length==0){
342 $j(this).append('<div id="gnp_' + embedObj.id + '" class="ui-state-highlight ui-corner-all" ' +
343 'style="position:absolute;display:none;background:#FFF;top:10px;left:10px;right:10px;height:60px;">' +
344 gM('mv_for_best_experience') +
345 '<br><input id="ffwarn_'+embedObj.id+'" type=\"checkbox\">' +
346 gM('mv_do_not_warn_again') +
347 '</div>');
348 $j('#ffwarn_'+embedObj.id).click(function(){
349 if( $j(this).checked ){
350 $j.cookie('dismissNativeWarn', true);
351 }else{
352 $j.cookie('dismissNativeWarn', false);
353 }
354
355 });
356 }
357 if( $j.cookie('dismissNativeWarn')!== true){
358 $j('#gnp_' + embedObj.id).fadeIn('slow');
359 }
360 },
361 function(){
362 $j('#gnp_' + embedObj.id).fadeOut('slow');
363 }
364 );
365 }
366
367 if( $j.browser.msie && $j.browser.version <= 6){
368 $j('#big_play_link_' + embedObj.id).pngFix();
369 }
370
371
372 //captions binding:
373 $j('#timed_text_' + embedObj.id).unbind().btnBind().click(function(){
374 $j('#' + embedObj.id).get(0).showTextInterface();
375 });
376
377 //options binding:
378 $j('#options_button_' + embedObj.id).unbind().btnBind().click(function(){
379 $j('#' +embedObj.id).get(0).doOptionsHTML();
380 });
381
382 //fullscreen binding:
383 $j('#fullscreen_'+embedObj.id).unbind().btnBind().click(function(){
384 $j('#' +embedObj.id).get(0).fullscreen();
385 });
386
387 js_log(" should add slider binding: " + $j('#mv_play_head_'+embedObj.id).length) ;
388 $j('#mv_play_head_'+embedObj.id).slider({
389 range: "min",
390 value: 0,
391 min: 0,
392 max: 1000,
393 start: function(event, ui){
394 var id = (embedObj.pc!=null)?embedObj.pc.pp.id:embedObj.id;
395 embedObj.userSlide=true;
396 $j('#big_play_link_'+id).fadeOut('fast');
397 //if playlist always start at 0
398 embedObj.start_time_sec = (embedObj.instanceOf == 'mvPlayList')?0:
399 npt2seconds(embedObj.getTimeReq().split('/')[0]);
400 },
401 slide: function(event, ui) {
402 var perc = ui.value/1000;
403 embedObj.jump_time = seconds2npt( parseFloat( parseFloat(embedObj.getDuration()) * perc ) + embedObj.start_time_sec);
404 //js_log('perc:' + perc + ' * ' + embedObj.getDuration() + ' jt:'+ this.jump_time);
405 embedObj.setStatus( gM('seek_to')+' '+embedObj.jump_time );
406 //update the thumbnail / frame
407 if(embedObj.isPlaying==false){
408 embedObj.updateThumbPerc( perc );
409 }
410 },
411 change:function(event, ui){
412 //only run the onChange event if done by a user slide:
413 if(embedObj.userSlide){
414 embedObj.userSlide=false;
415 embedObj.seeking=true;
416 //stop the monitor timer (if we can)
417 if(embedObj.stopMonitor)
418 embedObj.stopMonitor();
419
420 var perc = ui.value/1000;
421 //set seek time (in case we have to do a url seek)
422 embedObj.seek_time_sec = npt2seconds( embedObj.jump_time, true );
423 js_log('do jump to: '+embedObj.jump_time + ' perc:' +perc + ' sts:' + embedObj.seek_time_sec);
424 embedObj.doSeek(perc);
425 }
426 }
427 });
428 //up the z-index of the default status indicator:
429 $j('#mv_play_head_'+embedObj.id + ' .ui-slider-handle').css('z-index', 4);
430 $j('#mv_play_head_'+embedObj.id + ' .ui-slider-range').addClass('ui-corner-all').css('z-index', 2);
431 //extended class list for jQuery ui themeing (we can probably refactor this with custom buffering highliter)
432 $j('#mv_play_head_'+embedObj.id).append( ctrlBuilder.getMvBufferHtml() );
433
434 //videoOptions:
435 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_selection').click(function(){
436 embedObj.selectPlaybackMethod();
437 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
438 return false;
439 });
440 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_download').click(function(){
441 embedObj.showVideoDownload();
442 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
443 return false;
444 })
445 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_showcode').click(function(){
446 embedObj.showEmbedCode();
447 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
448 return false;
449 });
450
451 //volume binding:
452 var hoverOverDelay=false;
453 $j('#volume_control_'+embedObj.id).unbind().btnBind().click(function(){
454 $j('#' +embedObj.id).get(0).toggleMute();
455 }).hover(
456 function(){
457 $j('#vol_container_' + embedObj.id).addClass('vol_container_top');
458 //set to "below" if playing and embedType != native
459 if(embedObj && embedObj.isPlaying() && !embedObj.supports['overlays']){
460 $j('#vol_container_' + embedObj.id).removeClass('vol_container_top').addClass('vol_container_below');
461 }
462
463 $j('#vol_container_' + embedObj.id).fadeIn('fast');
464 hoverOverDelay = true;
465 },
466 function(){
467 hoverOverDelay= false;
468 setTimeout(function doHideVolume(){
469 if(!hoverOverDelay){
470 $j('#vol_container_' + embedObj.id).fadeOut('fast');
471 }
472 }, 500);
473 }
474 );
475 //Volumen Slider
476 $j('#volume_bar_'+embedObj.id).slider({
477 orientation: "vertical",
478 range: "min",
479 value: 80,
480 min: 0,
481 max: 100,
482 slide: function(event, ui) {
483 var perc = ui.value/100;
484 //js_log('update volume:' + perc);
485 embedObj.updateVolumen(perc);
486 },
487 change:function(event, ui){
488 var perc = ui.value/100;
489 if (perc==0) {
490 $j('#volume_control_'+embedObj.id + ' span').removeClass('ui-icon-volume-on').addClass('ui-icon-volume-off');
491 }else{
492 $j('#volume_control_'+embedObj.id + ' span').removeClass('ui-icon-volume-off').addClass('ui-icon-volume-on');
493 }
494 //only run the onChange event if done by a user slide:
495 if(embedObj.userSlide){
496 embedObj.userSlide=false;
497 embedObj.seeking=true;
498 var perc = ui.value/100;
499 embedObj.updateVolumen(perc);
500 }
501 }
502 });
503
504 },
505 getMvBufferHtml:function(){
506 return '<div class="ui-slider-range ui-slider-range-min ui-widget-header ' +
507 'ui-state-highlight ui-corner-all '+
508 'mv_buffer" style="width:0px;height:100%;z-index:1;top:0px" />';
509 },
510 components:{
511 'borders':{
512 'w':8,
513 'o':function(){
514 return '';
515 }
516 },
517 'mv_embedded_options':{
518 'w':0,
519 'o':function(){
520 var o= '<div id="mv_vid_options_'+ctrlBuilder.id+'" class="videoOptions">'+
521 '<div class="videoOptionsTop"></div>'+
522 '<div class="videoOptionsBox">'+
523 '<div class="block">'+
524 '<h6>Video Options</h6>'+
525 '</div>'+
526 '<div class="block">'+
527 '<p class="short_match vo_selection"><a href="#"><span>Stream Selection</span></a></p>'+
528 '<p class="short_match vo_download"><a href="#"><span>Download</span></a></p>'+
529 '<p class="short_match vo_showcode"><a href="#"><span>Share or Embed</span></a></p>';
530
531 //link to the stream page if we are not already there:
532 if( ctrlBuilder.embedObj.roe && typeof mv_stream_interface == 'undefined' )
533 o+='<p class="short_match"><a href="javascript:$j(\'#'+ctrlBuilder.id+'\').get(0).doLinkBack()"><span><strong>Source Page</strong></span></a></p>';
534
535 o+='</div>'+
536 '</div><!--videoOptionsInner-->' +
537 '<div class="videoOptionsBot"></div>' +
538 '</div><!--videoOptions-->';
539 return o;
540 }
541 },
542 'fullscreen':{
543 'w':20,
544 'o':function(){
545 return '<div title="' + gM('player_fullscreen') + '" id="fullscreen_'+ctrlBuilder.id+'" class="ui-state-default ui-corner-all ui-icon_link rButton"><span class="ui-icon ui-icon-arrow-4-diag"></span></div>'
546 }
547 },
548 'options':{
549 'w':26,
550 'o':function(){
551 return '<div title="'+ gM('player_options') + '" id="options_button_'+ctrlBuilder.id+'" class="ui-state-default ui-corner-all ui-icon_link rButton"><span class="ui-icon ui-icon-wrench"></span></div>';
552 }
553 },
554 'pause':{
555 'w':24,
556 'o':function(){
557 return '<div title="' + gM('play_clip') + '" id="mv_play_pause_button_' + ctrlBuilder.id + '" class="ui-state-default ui-corner-all ui-icon_link lButton"><span class="ui-icon ui-icon-play"/></div>';
558 }
559 },
560 'closed_captions':{
561 'w':23,
562 'o':function(){
563 return '<div title="' + gM('closed_captions') + '" id="timed_text_'+ctrlBuilder.id+'" class="ui-state-default ui-corner-all ui-icon_link rButton"><span class="ui-icon ui-icon-comment"></span></div>'
564 }
565 },
566 'volume_control':{
567 'w':23,
568 'o':function(){
569 return '<div title="' + gM('volume_control') + '" id="volume_control_'+ctrlBuilder.id+'" class="ui-state-default ui-corner-all ui-icon_link rButton">' +
570 '<span class="ui-icon ui-icon-volume-on"></span>' +
571 '<div style="position:absolute;display:none;" id="vol_container_'+ctrlBuilder.id+'" class="vol_container ui-corner-all">' +
572 '<div class="volume_bar" id="volume_bar_' + ctrlBuilder.id + '"></div>' +
573 '</div>'+
574 '</div>';
575 }
576 },
577 'time_display':{
578 'w':90,
579 'o':function(){
580 return '<div id="mv_time_'+ctrlBuilder.id+'" class="ui-widget time">' + ctrlBuilder.embedObj.getTimeReq() + '</div>';
581 }
582 },
583 'play_head':{
584 'w':0, //special case (takes up remaining space)
585 'o':function(){
586 return '<div class="play_head" id="mv_play_head_' + ctrlBuilder.id + '" style="width: ' + ( ctrlBuilder.available_width - 30 ) + 'px;"></div>';
587 }
588 }
589 }
590 }
591
592 /**
593 * mediaSource class represents a source for a media element.
594 * @param {String} type MIME type of the source.
595 * @param {String} uri URI of the source.
596 * @constructor
597 */
598 function mediaSource(element)
599 {
600 this.init(element);
601 }
602
603
604 mediaSource.prototype =
605 {
606 /** MIME type of the source. */
607 mime_type:null,
608 /** URI of the source. */
609 uri:null,
610 /** Title of the source. */
611 title:null,
612 /** True if the source has been marked as the default. */
613 marked_default:false,
614 /** True if the source supports url specification of offset and duration */
615 URLTimeEncoding:false,
616 /** Start offset of the requested segment */
617 start_offset:null,
618 /** Duration of the requested segment (0 if not known) */
619 duration:0,
620 is_playable:null,
621 upddate_interval:null,
622
623 id:null,
624 start_ntp:null,
625 end_ntp:null,
626
627 init : function(element)
628 {
629 //js_log('adding mediaSource: ' + element);
630 this.src = $j(element).attr('src');
631 this.marked_default = false;
632 if ( element.tagName.toLowerCase() == 'video')
633 this.marked_default = true;
634
635 //set default URLTimeEncoding if we have a time url:
636 //not ideal way to discover if content is on an oggz_chop server.
637 //should check some other way.
638 var pUrl = parseUri ( this.src );
639 if(typeof pUrl['queryKey']['t'] != 'undefined'){
640 this['URLTimeEncoding']=true;
641 }
642 for(var i=0; i < mv_default_source_attr.length; i++){ //array loop:
643 var attr = mv_default_source_attr[ i ];
644 if( $j(element).attr( attr ) ) {
645 this[ attr ] = $j(element).attr( attr );
646 }
647 }
648 //update duration from hit if present:
649 if(this.durationHint)
650 this.duration = this.durationHint;
651
652
653 if ( $j(element).attr('type'))
654 this.mime_type = $j(element).attr('type');
655 else if ($j(element).attr('content-type'))
656 this.mime_type = $j(element).attr('content-type');
657 else
658 this.mime_type = this.detectType(this.src);
659
660 //set the title if unset:
661 if( !this.title )
662 this.title = this.mime_type;
663
664 this.parseURLDuration();
665 },
666 updateSource:function(element){
667 //for now just update the title:
668 if ($j(element).attr("title"))
669 this.title = $j(element).attr("title");
670 },
671 /** updates the src time and start & end
672 * @param {String} start_time in NTP format
673 * @param {String} end_time in NTP format
674 */
675 updateSrcTime:function (start_ntp, end_ntp){
676 //js_log("f:updateSrcTime: "+ start_ntp+'/'+ end_ntp + ' from org: ' + this.start_ntp+ '/'+this.end_ntp);
677 //js_log("pre uri:" + this.src);
678 //if we have time we can use:
679 if( this.URLTimeEncoding ){
680 //make sure its a valid start time / end time (else set default)
681 if( !npt2seconds(start_ntp) )
682 start_ntp = this.start_ntp;
683
684 if( !npt2seconds(end_ntp) )
685 end_ntp = this.end_ntp;
686
687 this.src = getURLParamReplace(this.src, { 't': start_ntp +'/'+ end_ntp } );
688
689 //update the duration
690 this.parseURLDuration();
691 }
692 },
693 setDuration:function (duration)
694 {
695 this.duration = duration;
696 if(!this.end_ntp){
697 this.end_ntp = seconds2npt( this.start_offset + duration);
698 }
699 },
700 /** MIME type accessor function.
701 @return the MIME type of the source.
702 @type String
703 */
704 getMIMEType : function()
705 {
706 return this.mime_type;
707 },
708 /** URI accessor function.
709 * @param int seek_time_sec (used to adjust the URI for url based seeks)
710 @return the URI of the source.
711 @type String
712 */
713 getURI : function( seek_time_sec )
714 {
715 if( !seek_time_sec || !this.URLTimeEncoding ){
716 return this.src;
717 }
718 if(!this.end_ntp){
719 var endvar = '';
720 }else{
721 var endvar = '/'+ this.end_ntp;
722 }
723 return getURLParamReplace(this.src, { 't': seconds2npt( seek_time_sec )+endvar } ); ;
724 },
725 /** Title accessor function.
726 @return the title of the source.
727 @type String
728 */
729 getTitle : function()
730 {
731 return this.title;
732 },
733 /** Index accessor function.
734 @return the source's index within the enclosing mediaElement container.
735 @type Integer
736 */
737 getIndex : function()
738 {
739 return this.index;
740 },
741 /*
742 * function getDuration in milliseconds
743 * special case derive duration from request url
744 * supports media_url?t=ntp_start/ntp_end url request format
745 */
746 parseURLDuration : function(){
747 //check if we have a URLTimeEncoding:
748 if( this.URLTimeEncoding ){
749 var annoURL = parseUri( this.src );
750 if( annoURL.queryKey['t'] ){
751 var times = annoURL.queryKey['t'].split('/');
752 this.start_ntp = times[0];
753 this.end_ntp = times[1];
754 this.start_offset = npt2seconds( this.start_ntp );
755 this.duration = npt2seconds( this.end_ntp ) - this.start_offset;
756 }else{
757 //look for this info as attributes
758 if(this.startOffset){
759 this.start_offset = this.startOffset;
760 this.start_ntp = seconds2npt( this.startOffset);
761 }
762 if(this.duration){
763 this.end_ntp = seconds2npt( parseInt(this.duration) + parseInt(this.start_offset) );
764 }
765 }
766 }
767 //else nothing to parse just keep whatever info we already have
768
769 //js_log('f:parseURLDuration() for:' + this.src + ' d:' + this.duration);
770 },
771 /** Attempts to detect the type of a media file based on the URI.
772 @param {String} uri URI of the media file.
773 @returns The guessed MIME type of the file.
774 @type String
775 */
776 detectType:function(uri)
777 {
778 //@@todo if media is on the same server as the javascript or we have mv_proxy configured
779 //we can issue a HEAD request and read the mime type of the media...
780 // (this will detect media mime type independently of the url name)
781 //http://www.jibbering.com/2002/4/httprequest.html (this should be done by extending jquery's ajax objects)
782 var end_inx = (uri.indexOf('?')!=-1)? uri.indexOf('?') : uri.length;
783 var no_param_uri = uri.substr(0, end_inx);
784 switch( no_param_uri.substr(no_param_uri.lastIndexOf('.'),4).toLowerCase() ){
785 case '.flv':return 'video/x-flv';break;
786 case '.ogg': case '.ogv': return 'video/ogg';break;
787 case '.oga': return 'audio/ogg'; break;
788 case '.anx':return 'video/ogg';break;
789 }
790 }
791 };
792
793 /** A media element corresponding to a <video> element.
794 It is implemented as a collection of mediaSource objects. The media sources
795 will be initialized from the <video> element, its child <source> elements,
796 and/or the ROE file referenced by the <video> element.
797 @param {element} video_element <video> element used for initialization.
798 @constructor
799 */
800 function mediaElement(video_element)
801 {
802 this.init(video_element);
803 };
804
805 mediaElement.prototype =
806 {
807 /** The array of mediaSource elements. */
808 sources:null,
809 addedROEData:false,
810 /** Selected mediaSource element. */
811 selected_source:null,
812 thumbnail:null,
813 linkback:null,
814
815 /** @private */
816 init:function( video_element )
817 {
818 var _this = this;
819 js_log('Initializing mediaElement...' );
820 this.sources = new Array();
821 this.thumbnail = mv_default_thumb_url;
822 // Process the source element:
823 if($j(video_element).attr("src"))
824 this.tryAddSource(video_element);
825
826 if($j(video_element).attr('thumbnail'))
827 this.thumbnail=$j(video_element).attr('thumbnail');
828
829 if($j(video_element).attr('poster'))
830 this.thumbnail=$j(video_element).attr('poster');
831
832 // Process all inner <source> elements
833 //js_log("inner source count: " + video_element.getElementsByTagName('source').length );
834
835 $j(video_element).find('source,text').each(function(inx, inner_source){
836 _this.tryAddSource( inner_source );
837 });
838 },
839 /** Updates the time request for all sources that have a standard time request argument (ie &t=start_time/end_time)
840 */
841 updateSourceTimes:function(start_ntp, end_ntp){
842 var _this = this;
843 $j.each(this.sources, function(inx, mediaSource){
844 mediaSource.updateSrcTime(start_ntp, end_ntp);
845 });
846 },
847 /*timed Text check*/
848 timedTextSources:function(){
849 for(var i=0; i < this.sources.length; i++){
850 if( this.sources[i].mime_type == 'text/cmml' ||
851 this.sources[i].mime_type == 'text/x-srt')
852 return true;
853 };
854 return false;
855 },
856 /** Returns the array of mediaSources of this element.
857 \returns {Array} Array of mediaSource elements.
858 */
859 getSources:function( mime_filter )
860 {
861 if(!mime_filter)
862 return this.sources;
863 //apply mime filter:
864 var source_set = new Array();
865 for(var i=0; i < this.sources.length ; i++){
866 if( this.sources[i].mime_type.indexOf( mime_filter ) != -1 )
867 source_set.push( this.sources[i] );
868 }
869 return source_set;
870 },
871 getSourceById:function( source_id ){
872 for(var i=0; i < this.sources.length ; i++){
873 if( this.sources[i].id == source_id)
874 return this.sources[i];
875 }
876 return null;
877 },
878 /** Selects a particular source for playback.
879 */
880 selectSource:function(index)
881 {
882 js_log('f:selectSource:'+index);
883 var playable_sources = this.getPlayableSources();
884 for(var i=0; i < playable_sources.length; i++){
885 if( i==index ){
886 this.selected_source = playable_sources[i];
887 //update the user selected format:
888 embedTypes.players.userSelectFormat( playable_sources[i].mime_type );
889 break;
890 }
891 }
892 },
893 /** selects the default source via cookie preference, default marked, or by id order
894 * */
895 autoSelectSource:function(){
896 js_log('f:autoSelectSource:');
897 //@@todo read user preference for source
898 // Select the default source
899 var playable_sources = this.getPlayableSources();
900 var flash_flag=ogg_flag=false;
901 //debugger;
902 for(var source=0; source < playable_sources.length; source++){
903 var mime_type =playable_sources[source].mime_type;
904 if( playable_sources[source].marked_default ){
905 js_log('set via marked default: ' + playable_sources[source].marked_default);
906 this.selected_source = playable_sources[source];
907 return true;
908 }
909 //set via user-preference
910 if(embedTypes.players.preference['format_prefrence'] == mime_type){
911 js_log('set via preference: '+playable_sources[source].mime_type);
912 this.selected_source = playable_sources[source];
913 return true;
914 }
915 }
916 //set Ogg via player support
917 for(var source=0; source < playable_sources.length; source++){
918 js_log('f:autoSelectSource:' + playable_sources[source].mime_type);
919 var mime_type =playable_sources[source].mime_type;
920 //set source via player
921 if(mime_type=='video/ogg' || mime_type=='ogg/video' || mime_type=='video/annodex' || mime_type=='application/ogg'){
922 for(var i=0; i < embedTypes.players.players.length; i++){ //for in loop on object oky
923 var player = embedTypes.players.players[i];
924 if(player.library=='vlc' || player.library=='native'){
925 js_log('set via ogg via order');
926 this.selected_source = playable_sources[source];
927 return true;
928 }
929 }
930 }
931 }
932 //set basic flash
933 for(var source=0; source < playable_sources.length; source++){
934 var mime_type =playable_sources[source].mime_type;
935 if( mime_type=='video/x-flv' ){
936 js_log('set via by player preference normal flash')
937 this.selected_source = playable_sources[source];
938 return true;
939 }
940 }
941 //set h264 flash
942 for(var source=0; source < playable_sources.length; source++){
943 var mime_type =playable_sources[source].mime_type;
944 if( mime_type=='video/h264' ){
945 js_log('set via playable_sources preference h264 flash')
946 this.selected_source = playable_sources[source];
947 return true;
948 }
949 }
950 //select first source
951 if (!this.selected_source)
952 {
953 js_log('set via first source:' + playable_sources[0]);
954 this.selected_source = playable_sources[0];
955 return true;
956 }
957 },
958 /** Returns the thumbnail URL for the media element.
959 \returns {String} thumbnail URL
960 */
961 getThumbnailURL:function()
962 {
963 return this.thumbnail;
964 },
965 /** Checks whether there is a stream of a specified MIME type.
966 @param {String} mime_type MIME type to check.
967 @type {BooleanPrimitive}.
968 */
969 hasStreamOfMIMEType:function(mime_type)
970 {
971 for(source in this.sources)
972 {
973 if(this.sources[source].getMIMEType() == mime_type)
974 return true;
975 }
976 return false;
977 },
978 isPlayableType:function(mime_type)
979 {
980 if( embedTypes.players.defaultPlayer( mime_type ) ){
981 return true;
982 }else{
983 return false;
984 }
985 //if(this.selected_player){
986 //return mime_type=='video/ogg' || mime_type=='ogg/video' || mime_type=='video/annodex' || mime_type=='video/x-flv';
987 },
988 /** Adds a single mediaSource using the provided element if
989 the element has a 'src' attribute.
990 @param element {element} <video>, <source> or <mediaSource> element.
991 */
992 tryAddSource:function(element)
993 {
994 js_log('f:tryAddSource:'+ $j(element).attr("src"));
995 if (! $j(element).attr("src")){
996 //js_log("element has no src");
997 return false;
998 }
999 var new_src = $j(element).attr('src');
1000 //make sure an existing element with the same src does not already exist:
1001 for( var i=0; i < this.sources.length; i++ ){
1002 if(this.sources[i].src == new_src){
1003 //js_log('checking existing: '+this.sources[i].getURI() + ' != '+ new_src);
1004 //can't add it all but try to update any additional attr:
1005 this.sources[i].updateSource(element);
1006 return false;
1007 }
1008 }
1009 var source = new mediaSource( element );
1010 this.sources.push(source);
1011 //alert('pushed source to stack'+ source + 'sl:'+this.sources.length);
1012 },
1013 getPlayableSources: function(){
1014 var playable_sources= new Array();
1015 for(var i=0; i < this.sources.length; i++){
1016 if( this.isPlayableType( this.sources[i].mime_type ) ){
1017 playable_sources.push( this.sources[i] );
1018 }else{
1019 js_log("type "+ this.sources[i].mime_type + 'is not playable');
1020 }
1021 };
1022 return playable_sources;
1023 },
1024 /* Imports media sources from ROE data.
1025 * @param roe_data ROE data.
1026 */
1027 addROE:function(roe_data){
1028 js_log('f:addROE');
1029 this.addedROEData=true;
1030 var _this = this;
1031 if( typeof roe_data == 'string' )
1032 {
1033 var parser=new DOMParser();
1034 js_log('ROE data:' + roe_data);
1035 roe_data=parser.parseFromString(roe_data,"text/xml");
1036 }
1037 if( roe_data ){
1038 $j.each(roe_data.getElementsByTagName('mediaSource'), function(inx, source){
1039 _this.tryAddSource(source);
1040 });
1041 //set the thumbnail:
1042 $j.each(roe_data.getElementsByTagName('img'), function(inx, n){
1043 if($j(n).attr("id")=="stream_thumb"){
1044 js_log('roe:set thumb to '+$j(n).attr("src"));
1045 _this['thumbnail'] =$j(n).attr("src");
1046 }
1047 });
1048 //set the linkback:
1049 $j.each(roe_data.getElementsByTagName('link'), function(inx, n){
1050 if($j(n).attr('id')=='html_linkback'){
1051 js_log('roe:set linkback to '+$j(n).attr("href"));
1052 _this['linkback'] = $j(n).attr('href');
1053 }
1054 });
1055 }else{
1056 js_log('ROE data empty.');
1057 }
1058 }
1059 };
1060
1061
1062 /** base embedVideo object
1063 @param element <video> tag used for initialization.
1064 @constructor
1065 */
1066 var embedVideo = function(element) {
1067 return this.init(element);
1068 };
1069
1070 embedVideo.prototype = {
1071 /** The mediaElement object containing all mediaSource objects */
1072 media_element:null,
1073 preview_mode:false,
1074 ready_to_play:false, //should use html5 ready state
1075 load_error:false, //used to set error in case of error
1076 loading_external_data:false,
1077 thumbnail_updating:false,
1078 thumbnail_disp:true,
1079 init_with_sources_loadedDone:false,
1080 inDOM:false,
1081 //for onClip done stuff:
1082 anno_data_cache:null,
1083 seek_time_sec:0,
1084 base_seeker_slider_offset:null,
1085 onClipDone_disp:false,
1086 supports:{},
1087 //for seek thumb updates:
1088 cur_thumb_seek_time:0,
1089 thumb_seek_interval:null,
1090
1091 seeking:false,
1092 //set the buffered percent:
1093 bufferedPercent:0,
1094 //utility functions for property values:
1095 hx : function ( s ) {
1096 if ( typeof s != 'String' ) {
1097 s = s.toString();
1098 }
1099 return s.replace( /&/g, '&amp;' )
1100 . replace( /</g, '&lt;' )
1101 . replace( />/g, '&gt;' );
1102 },
1103 hq : function ( s ) {
1104 return '"' + this.hx( s ) + '"';
1105 },
1106 playerPixelWidth : function()
1107 {
1108 var player = $j('#mv_embedded_player_'+this.id).get(0);
1109 if(typeof player!='undefined' && player['offsetWidth'])
1110 return player.offsetWidth;
1111 else
1112 return parseInt(this.width);
1113 },
1114 playerPixelHeight : function()
1115 {
1116 var player = $j('#mv_embedded_player_'+this.id).get(0);
1117 if(typeof player!='undefined' && player['offsetHeight'])
1118 return player.offsetHeight;
1119 else
1120 return parseInt(this.height);
1121 },
1122 init: function(element){
1123 //this.element_pointer = element;
1124
1125 //inherit all the default video_attributes
1126 for(var attr in default_video_attributes){ //for in loop oky on user object
1127 if(element.getAttribute(attr)){
1128 this[attr]=element.getAttribute(attr);
1129 //js_log('attr:' + attr + ' val: ' + element.getAttribute(attr) +'(set by elm)');
1130 }else{
1131 this[attr]=default_video_attributes[attr];
1132 //js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+ 'elm_val:' + element.getAttribute(attr) + "\n (set by attr)");
1133 }
1134 }
1135 //make sure startOffset is cast as an int
1136 if( this.startOffset && this.startOffset.split(':').length >= 2)
1137 this.startOffset = npt2seconds(this.startOffset);
1138 //make sure offset is in float:
1139 this.startOffset = parseFloat(this.startOffset);
1140
1141 if( this.duration && this.duration.split(':').length >= 2)
1142 this.duration = npt2seconds( this.duration );
1143 //make sure duration is in float:
1144 this.duration = parseFloat(this.duration);
1145 js_log("duration is: " + this.duration);
1146 //if style is set override width and height
1147 var dwh = mv_default_video_size.split('x');
1148 this.width = element.style.width ? element.style.width : dwh[0];
1149 this.height = element.style.height ? element.style.height : dwh[1];
1150 //set the plugin id
1151 this.pid = 'pid_' + this.id;
1152
1153 //grab any innerHTML and set it to missing_plugin_html
1154 //@@todo we should strip source tags instead of checking and skipping
1155 if(element.innerHTML!='' && element.getElementsByTagName('source').length==0){
1156 js_log('innerHTML: ' + element.innerHTML);
1157 this.user_missing_plugin_html=element.innerHTML;
1158 }
1159 // load all of the specified sources
1160 this.media_element = new mediaElement(element);
1161 },
1162 on_dom_swap: function(){
1163 js_log('f:on_dom_swap');
1164 // Process the provided ROE file... if we don't yet have sources
1165 if(this.roe && this.media_element.sources.length==0 ){
1166 js_log('loading external data');
1167 this.loading_external_data=true;
1168 var _this = this;
1169 do_request(this.roe, function(data)
1170 {
1171 //continue
1172 _this.media_element.addROE( data );
1173 js_log('added_roe::' + _this.media_element.sources.length);
1174
1175 js_log('set loading_external_data=false');
1176 _this.loading_external_data=false;
1177
1178 _this.init_with_sources_loaded();
1179 });
1180 }
1181 },
1182 init_with_sources_loaded : function()
1183 {
1184 js_log('f:init_with_sources_loaded');
1185 //set flag that we have run this function:
1186 this.init_with_sources_loadedDone=true;
1187 //autoseletct the source
1188 this.media_element.autoSelectSource();
1189 //auto select player based on prefrence or default order
1190 if( !this.media_element.selected_source )
1191 {
1192 //check for parent clip:
1193 if( typeof this.pc != 'undefined' ){
1194 js_log('no sources, type:' +this.type + ' check for html');
1195 //debugger;
1196 //do load player if just displaying innerHTML:
1197 if( this.pc.type == 'text/html' ){
1198 this.selected_player = embedTypes.players.defaultPlayer( 'text/html' );
1199 js_log('set selected player:'+ this.selected_player.mime_type);
1200 }
1201 }
1202 }else{
1203 this.selected_player = embedTypes.players.defaultPlayer( this.media_element.selected_source.mime_type );
1204 }
1205 if( this.selected_player ){
1206 js_log('selected ' + this.selected_player.getName());
1207 js_log("PLAYBACK TYPE: "+this.selected_player.library);
1208 this.thumbnail_disp = true;
1209 this.inheritEmbedObj();
1210 }else{
1211 //no source's playable
1212 var missing_type ='';
1213 var or ='';
1214 for( var i=0; i < this.media_element.sources.length; i++){
1215 missing_type+=or + this.media_element.sources[i].mime_type;
1216 or=' or ';
1217 }
1218 if( this.pc )
1219 var missing_type = this.pc.type;
1220 js_log('no player found for given source type ' + missing_type);
1221 this.load_error= this.getPluginMissingHTML(missing_type);
1222 }
1223 },
1224 inheritEmbedObj:function(){
1225 js_log("inheritEmbedObj:duration is: " + this.duration);
1226 //@@note: tricky cuz direct overwrite is not so ideal.. since the extended object is already tied to the dom
1227 //clear out any non-base embedObj stuff:
1228 if(this.instanceOf){
1229 eval('tmpObj = '+this.instanceOf);
1230 for(var i in tmpObj){ //for in loop oky for object
1231 if(this['parent_'+i]){
1232 this[i]=this['parent_'+i];
1233 }else{
1234 this[i]=null;
1235 }
1236 }
1237 }
1238 //set up the new embedObj
1239 js_log('f: inheritEmbedObj: embedding with ' + this.selected_player.library);
1240 var _this = this;
1241 this.selected_player.load( function()
1242 {
1243 js_log("selected_player::load:duration is: " + _this.duration);
1244 //js_log('inheriting '+_this.selected_player.library +'Embed to ' + _this.id + ' ' + $j('#'+_this.id).length);
1245 //var _this = $j('#'+_this.id).get(0);
1246 //js_log( 'type of ' + _this.selected_player.library +'Embed + ' +
1247 // eval('typeof '+_this.selected_player.library +'Embed'));
1248 eval('embedObj = ' +_this.selected_player.library +'Embed;');
1249 for(var method in embedObj){ //for in loop oky for object
1250 //parent method preservation for local overwritten methods
1251 if(_this[method])
1252 _this['parent_' + method] = _this[method];
1253 _this[method]=embedObj[method];
1254 }
1255 js_log('TYPEOF_ppause: ' + typeof _this['parent_pause']);
1256
1257 if(_this.inheritEmbedOverride){
1258 _this.inheritEmbedOverride();
1259 }
1260 //update controls if possible
1261 if(!_this.loading_external_data)
1262 _this.refreshControlsHTML();
1263
1264 //js_log("READY TO PLAY:"+_this.id);
1265 _this.ready_to_play=true;
1266 _this.getDuration();
1267 _this.getHTML();
1268 });
1269 },
1270 selectPlayer:function(player)
1271 {
1272 var _this = this;
1273 if(this.selected_player.id != player.id){
1274 this.selected_player = player;
1275 this.inheritEmbedObj();
1276 }
1277 },
1278 doNativeWarningCheck:function(){
1279 if( $j.cookie('dismissNativeWarn') && $j.cookie('dismissNativeWarn')===true){
1280 return false;
1281 }else{
1282 //see if we have native support for ogg:
1283 var supporting_players = embedTypes.players.getMIMETypePlayers( 'video/ogg' );
1284 for(var i=0; i < supporting_players.length; i++){
1285 if(supporting_players[i].id == 'videoElement'){
1286 return false;
1287 }
1288 }
1289 //see if we are using mv_embed without a ogg source in which case no point in promoting firefox :P
1290 if(this.media_element && this.media_element.sources){
1291 var foundOgg = false;
1292 var playable_sources = this.media_element.getPlayableSources();
1293 for(var sInx=0; sInx < playable_sources.length; sInx++){
1294 var mime_type = playable_sources[sInx].mime_type;
1295 if( mime_type=='video/ogg' ){
1296 //they have flash / h.264 fallback no need to push firefox :(
1297 foundOgg = true;
1298 }
1299 }
1300 //no ogg no point in download firefox
1301 if(!foundOgg)
1302 return false;
1303
1304 }
1305 }
1306 return true;
1307 },
1308 getTimeReq:function(){
1309 //js_log('f:getTimeReq:'+ this.getDurationNTP());
1310 var default_time_req = '0:00:00/' + this.getDurationNTP() ;
1311 if(!this.media_element)
1312 return default_time_req;
1313 if(!this.media_element.selected_source)
1314 return default_time_req;
1315 if(!this.media_element.selected_source.end_ntp)
1316 return default_time_req;
1317 return this.media_element.selected_source.start_ntp+'/'+this.media_element.selected_source.end_ntp;
1318 },
1319 getDuration:function(){
1320 //update some local pointers for the selected source:
1321 if(this.media_element && this.media_element.selected_source && this.media_element.selected_source.duration){
1322 this.duration = this.media_element.selected_source.duration;
1323 this.start_offset = this.media_element.selected_source.start_offset;
1324 this.start_ntp = this.media_element.selected_source.start_ntp;
1325 this.end_ntp = this.media_element.selected_source.end_ntp;
1326 }
1327 //update start end_ntp if duration !=0 (set from plugin)
1328 if(!this.start_ntp)
1329 this.start_ntp = '0:0:0';
1330 if(!this.end_ntp && this.duration)
1331 this.end_ntp = seconds2npt( this.duration );
1332 //return the duration
1333 return this.duration;
1334 },
1335 /* get the duration in ntp format */
1336 getDurationNTP:function(){
1337 return seconds2npt(this.getDuration());
1338 },
1339 /*
1340 * wrapEmebedContainer
1341 * wraps the embed code into a container to better support playlist function
1342 * (where embed element is swapped for next clip
1343 * (where plugin method does not support playlsits)
1344 */
1345 wrapEmebedContainer:function(embed_code){
1346 //check if parent clip is set( ie we are in a playlist so name the embed container by playlistID)
1347 var id = (this.pc!=null)?this.pc.pp.id:this.id;
1348 return '<div id="mv_ebct_'+id+'" style="width:'+this.width+'px;height:'+this.height+'px;">' +
1349 embed_code +
1350 '</div>';
1351 },
1352 getEmbedHTML : function(){
1353 //return this.wrapEmebedContainer( this.getEmbedObj() );
1354 return 'function getEmbedHTML should be overitten by embedLib ';
1355 },
1356 //do seek function (should be overwritten by implementing embedLibs)
1357 // first check if seek can be done on locally downloaded content.
1358 doSeek : function( perc ){
1359 if( this.supportsURLTimeEncoding() ){
1360 //make sure this.seek_time_sec is up-to-date:
1361 this.seek_time_sec = npt2seconds( this.start_ntp ) + parseFloat( perc * this.getDuration() );
1362 js_log('updated seek_time_sec: ' + seconds2npt ( this.seek_time_sec) );
1363 this.stop();
1364 this.didSeekJump=true;
1365 //update the slider
1366 this.setSliderValue( perc );
1367 }
1368 //do play in 100ms (give things time to clear)
1369 setTimeout('$j(\'#' + this.id + '\').get(0).play()',100);
1370 },
1371 /*
1372 * seeks to the requested time and issues a callback when ready
1373 * (should be overwitten by client that supports frame serving)
1374 */
1375 setCurrentTime:function( time, callback){
1376 js_log('error: base embed setCurrentTime can not frame serve (overide via plugin)');
1377 },
1378 addPresTimeOffset:function(){
1379 //add in the offset:
1380 if(this.seek_time_sec && this.seek_time_sec!=0){
1381 this.currentTime+=this.seek_time_sec;
1382 }else if(this.start_offset && this.start_offset!=0){
1383 this.currentTime = parseFloat(this.currentTime) + parseFloat(this.start_offset);
1384 }
1385 },
1386 doEmbedHTML:function()
1387 {
1388 js_log('f:doEmbedHTML');
1389 js_log('thum disp:'+this.thumbnail_disp);
1390 var _this = this;
1391 this.closeDisplayedHTML();
1392
1393 // if(!this.selected_player){
1394 // return this.getPluginMissingHTML();
1395 //Set "loading" here
1396 $j('#mv_embedded_player_'+_this.id).html(''+
1397 '<div style="color:black;width:'+this.width+'px;height:'+this.height+'px;">' +
1398 gM('loading_plugin') +
1399 '</div>'
1400 );
1401 // schedule embedding
1402 this.selected_player.load(function()
1403 {
1404 js_log('performing embed for ' + _this.id);
1405 var embed_code = _this.getEmbedHTML();
1406 //js_log('shopuld embed:' + embed_code);
1407 $j('#mv_embedded_player_'+_this.id).html(embed_code);
1408 });
1409 },
1410 onClipDone:function(){
1411 js_log('base:onClipDone');
1412 //stop the clip (load the thumbnail etc)
1413 this.stop();
1414 this.seek_time_sec = 0;
1415 this.setSliderValue(0);
1416 var _this = this;
1417
1418 //if the clip resolution is < 320 don't do fancy onClipDone stuff
1419 if(this.width < 300){
1420 return ;
1421 }
1422 this.onClipDone_disp=true;
1423 this.thumbnail_disp=true;
1424 //make sure we are not in preview mode( no end clip actions in preview mode)
1425 if( this.preview_mode )
1426 return ;
1427
1428 $j('#img_thumb_'+this.id).css('zindex',1);
1429 $j('#big_play_link_'+this.id).hide();
1430 //add the liks_info_div black back
1431 $j('#dc_'+this.id).append('<div id="liks_info_'+this.id+'" ' +
1432 'style="width:' +parseInt(parseInt(this.width)/2)+'px;'+
1433 'height:'+ parseInt(parseInt(this.height)) +'px;'+
1434 'position:absolute;top:10px;overflow:auto'+
1435 'width: '+parseInt( ((parseInt(this.width)/2)-15) ) + 'px;'+
1436 'left:'+ parseInt( ((parseInt(this.width)/2)+15) ) +'px;">'+
1437 '</div>' +
1438 '<div id="black_back_'+this.id+'" ' +
1439 'style="z-index:-2;position:absolute;background:#000;' +
1440 'top:0px;left:0px;width:'+parseInt(this.width)+'px;' +
1441 'height:'+parseInt(this.height)+'px;">' +
1442 '</div>'
1443 );
1444
1445 //start animation (make thumb small in upper left add in div for "loading"
1446 $j('#img_thumb_'+this.id).animate({
1447 width:parseInt(parseInt(_this.width)/2),
1448 height:parseInt(parseInt(_this.height)/2),
1449 top:20,
1450 left:10
1451 },
1452 1000,
1453 function(){
1454 //animation done.. add "loading" to div if empty
1455 if($j('#liks_info_'+_this.id).html()==''){
1456 $j('#liks_info_'+_this.id).html(gM('loading_txt'));
1457 }
1458 }
1459 )
1460 //now load roe if run the showNextPrevLinks
1461 if(this.roe && this.media_element.addedROEData==false){
1462 do_request(this.roe, function(data)
1463 {
1464 _this.media_element.addROE(data);
1465 _this.getNextPrevLinks();
1466 });
1467 }else{
1468 this.getNextPrevLinks();
1469 }
1470 },
1471 //@@todo we should merge getNextPrevLinks with textInterface .. there is repeated code between them.
1472 getNextPrevLinks:function(){
1473 js_log('f:getNextPrevLinks');
1474 var anno_track_url = null;
1475 var _this = this;
1476 //check for annoative track
1477 $j.each(this.media_element.sources, function(inx, n){
1478 if(n.mime_type=='text/cmml'){
1479 if( n.id == 'Anno_en'){
1480 anno_track_url = n.src;
1481 }
1482 }
1483 });
1484 if( anno_track_url ){
1485 js_log('found annotative track:'+ anno_track_url);
1486 //zero out seconds (should improve cache hit rate and generally expands metadata search)
1487 //@@todo this could be repalced with a regExp
1488 var annoURL = parseUri(anno_track_url);
1489 var times = annoURL.queryKey['t'].split('/');
1490 var stime_parts = times[0].split(':');
1491 var etime_parts = times[1].split(':');
1492 //zero out the hour:
1493 var new_start = stime_parts[0]+':'+'0:0';
1494 //zero out the end sec
1495 var new_end = (etime_parts[0]== stime_parts[0])? (etime_parts[0]+1)+':0:0' :etime_parts[0]+':0:0';
1496
1497 var etime_parts = times[1].split(':');
1498
1499 var new_anno_track_url = annoURL.protocol +'://'+ annoURL.host + annoURL.path +'?';
1500 $j.each(annoURL.queryKey, function(i, val){
1501 new_anno_track_url +=(i=='t')?'t='+new_start+'/'+new_end +'&' :
1502 i+'='+ val+'&';
1503 });
1504 var request_key = new_start+'/'+new_end;
1505 //check the anno_data cache:
1506 //@@todo search cache see if current is in range.
1507 if(this.anno_data_cache){
1508 js_log('anno data found in cache: '+request_key);
1509 this.showNextPrevLinks();
1510 }else{
1511 do_request(new_anno_track_url, function(cmml_data){
1512 js_log('raw response: '+ cmml_data);
1513 if(typeof cmml_data == 'string')
1514 {
1515 var parser=new DOMParser();
1516 js_log('Parse CMML data:' + cmml_data);
1517 cmml_data=parser.parseFromString(cmml_data,"text/xml");
1518 }
1519 //init anno_data_cache
1520 if(!_this.anno_data_cache)
1521 _this.anno_data_cache={};
1522 //grab all metadata and put it into the anno_data_cache:
1523 $j.each(cmml_data.getElementsByTagName('clip'), function(inx, clip){
1524 _this.anno_data_cache[ $j(clip).attr("id") ]={
1525 'start_time_sec':npt2seconds($j(clip).attr("start").replace('npt:','')),
1526 'end_time_sec':npt2seconds($j(clip).attr("end").replace('npt:','')),
1527 'time_req':$j(clip).attr("start").replace('npt:','')+'/'+$j(clip).attr("end").replace('npt:','')
1528 };
1529 //grab all its meta
1530 _this.anno_data_cache[ $j(clip).attr("id") ]['meta']={};
1531 $j.each(clip.getElementsByTagName('meta'),function(imx, meta){
1532 //js_log('adding meta: '+ $j(meta).attr("name")+ ' = '+ $j(meta).attr("content"));
1533 _this.anno_data_cache[$j(clip).attr("id")]['meta'][$j(meta).attr("name")]=$j(meta).attr("content");
1534 });
1535 });
1536 _this.showNextPrevLinks();
1537 });
1538 }
1539 }else{
1540 js_log('no annotative track found');
1541 $j('#liks_info_'+this.id).html('no metadata found for related links');
1542 }
1543 //query current request time +|- 60s to get prev next speech links.
1544 },
1545 showNextPrevLinks:function(){
1546 //js_log('f:showNextPrevLinks');
1547 //int requested links:
1548 var link = {
1549 'prev':'',
1550 'current':'',
1551 'next':''
1552 }
1553 var curTime = this.getTimeReq().split('/');
1554
1555 var s_sec = npt2seconds(curTime[0]);
1556 var e_sec = npt2seconds(curTime[1]);
1557 js_log('showNextPrevLinks: req time: '+ s_sec + ' to ' + e_sec);
1558 //now we have all the data in anno_data_cache
1559 var current_done=false;
1560 for(var clip_id in this.anno_data_cache){ //for in loop oky for object
1561 var clip = this.anno_data_cache[clip_id];
1562 //js_log('on clip:'+ clip_id);
1563 //set prev_link (if cur_link is still empty)
1564 if( s_sec > clip.end_time_sec){
1565 link.prev = clip_id;
1566 js_log('showNextPrevLinks: ' + s_sec + ' < ' + clip.end_time_sec + ' set prev');
1567 }
1568
1569 if(e_sec==clip.end_time_sec && s_sec== clip.start_time_sec)
1570 current_done = true;
1571 //current clip is not done:
1572 if( e_sec < clip.end_time_sec && link.current=='' && !current_done){
1573 link.current = clip_id;
1574 js_log('showNextPrevLinks: ' + e_sec + ' < ' + clip.end_time_sec + ' set current');
1575 }
1576
1577 //set end clip (first clip where start time is > end_time of req
1578 if( e_sec < clip.start_time_sec && link.next==''){
1579 link.next = clip_id;
1580 js_log('showNextPrevLinks: '+ e_sec + ' < '+ clip.start_time_sec + ' && ' + link.next );
1581 }
1582 }
1583 var html='';
1584 if(link.prev=='' && link.current=='' && link.next==''){
1585 html='<p><a href="'+this.media_element.linkbackgetMsg+'">clip page</a>';
1586 }else{
1587 for(var link_type in link){
1588 var link_id = link[link_type];
1589 if(link_id!=''){
1590 var clip = this.anno_data_cache[link_id];
1591 var title_msg='';
1592 for(var j in clip['meta']){
1593 title_msg+=j.replace(/_/g,' ') +': ' +clip['meta'][j].replace(/_/g,' ') +" <br>";
1594 }
1595 var time_req = clip.time_req;
1596 if(link_type=='current') //if current start from end of current clip play to end of current meta:
1597 time_req = curTime[1]+ '/' + seconds2npt( clip.end_time_sec );
1598
1599 //do special linkbacks for metavid content:
1600 var regTimeCheck = new RegExp(/[0-9]+:[0-9]+:[0-9]+\/[0-9]+:[0-9]+:[0-9]+/);
1601 html+='<p><a ';
1602 if( regTimeCheck.test( this.media_element.linkback ) ){
1603 html+=' href="'+ this.media_element.linkback.replace(regTimeCheck,time_req) +'" ';
1604 }else{
1605 html+=' href="#" onClick="$j(\'#'+this.id+'\').get(0).playByTimeReq(\''+
1606 time_req + '\'); return false; "';
1607 }
1608 html+=' title="' + title_msg + '">' +
1609 gM(link_type+'_clip_msg') +
1610 '</a><br><span style="font-size:small">'+ title_msg +'<span></p>';
1611 }
1612 }
1613 }
1614 //js_og("should set html:"+ html);
1615 $j('#liks_info_'+this.id).html(html);
1616 },
1617 playByTimeReq: function(time_req){
1618 js_log('f:playByTimeReq: '+time_req );
1619 this.stop();
1620 this.updateVideoTimeReq(time_req);
1621 this.play();
1622 },
1623 doThumbnailHTML:function()
1624 {
1625 var _this = this;
1626 js_log('f:doThumbnailHTML'+ this.thumbnail_disp);
1627 this.closeDisplayedHTML();
1628 $j( '#mv_embedded_player_' + this.id ).html( this.getThumbnailHTML() );
1629 this.paused = true;
1630 this.thumbnail_disp = true;
1631 },
1632 refreshControlsHTML:function(){
1633 js_log('refreshing controls HTML');
1634 if($j('#mv_embedded_controls_'+this.id).length==0)
1635 {
1636 js_log('#mv_embedded_controls_'+this.id + ' not present, returning');
1637 return;
1638 }else{
1639 $j('#mv_embedded_controls_'+this.id).html( this.getControlsHTML() );
1640 ctrlBuilder.addControlHooks(this);
1641 }
1642 },
1643 getControlsHTML:function()
1644 {
1645 return ctrlBuilder.getControls( this );
1646 },
1647 getHTML : function (){
1648 //@@todo check if we have sources avaliable
1649 js_log('f:getHTML : ' + this.id );
1650 var _this = this;
1651 var html_code = '';
1652 html_code = '<div id="videoPlayer_'+this.id+'" style="width:'+this.width+'px;" class="videoPlayer">';
1653 html_code += '<div style="width:'+parseInt(this.width)+'px;height:'+parseInt(this.height)+'px;" id="mv_embedded_player_'+this.id+'">' +
1654 this.getThumbnailHTML() +
1655 '</div>';
1656 //js_log("mvEmbed:controls "+ typeof this.controls);
1657 if(this.controls)
1658 {
1659 js_log("f:getHTML:AddControls");
1660 html_code +='<div id="mv_embedded_controls_' + this.id + '" class="ui-widget ui-corner-bottom ui-state-default controls" >';
1661 html_code += this.getControlsHTML();
1662 html_code +='</div>';
1663 //block out some space by encapulating the top level div
1664 $j(this).wrap('<div style="width:'+parseInt(this.width)+'px;height:'
1665 +(parseInt(this.height)+ctrlBuilder.height)+'px"></div>');
1666 }
1667 html_code += '</div>'; //videoPlayer div close
1668 //js_log('should set: '+this.id);
1669 $j(this).html( html_code );
1670 //add hooks once Controls are in DOM
1671 ctrlBuilder.addControlHooks(this);
1672
1673 //js_log('set this to: ' + $j(this).html() );
1674 //alert('stop');
1675 //if auto play==true directly embed the plugin
1676 if(this.autoplay)
1677 {
1678 js_log('activating autoplay');
1679 this.play();
1680 }
1681 },
1682 /*
1683 * get missing plugin html (check for user included code)
1684 */
1685 getPluginMissingHTML : function(missing_type){
1686 //keep the box width hight:
1687 var out = '<div style="width:'+this.width+'px;height:'+this.height+'px">';
1688 if(this.user_missing_plugin_html){
1689 out+= this.user_missing_plugin_html;
1690 }else{
1691 if(!missing_type)
1692 missing_type='';
1693 out+= gM('mv_generic_missing_plugin', missing_type) + ' or <a title="'+gM('download_clip')+'" href="'+this.src +'">'+gM('download_clip')+'</a>';
1694 }
1695 return out + '</div>';
1696 },
1697 updateVideoTimeReq:function(time_req){
1698 js_log('f:updateVideoTimeReq');
1699 var time_parts =time_req.split('/');
1700 this.updateVideoTime(time_parts[0], time_parts[1]);
1701 },
1702 //update video time
1703 updateVideoTime:function(start_ntp, end_ntp){
1704 //update media
1705 this.media_element.updateSourceTimes( start_ntp, end_ntp );
1706 //update mv_time
1707 this.setStatus(start_ntp+'/'+end_ntp);
1708 //reset slider
1709 this.setSliderValue(0);
1710 //reset seek_offset:
1711 if(this.media_element.selected_source.URLTimeEncoding )
1712 this.seek_time_sec=0;
1713 else
1714 this.seek_time_sec=npt2seconds(start_ntp);
1715 },
1716 //@@todo overwite by embed library if we can render frames natavily
1717 renderTimelineThumbnail:function( options ){
1718 var my_thumb_src = this.media_element.getThumbnailURL();
1719 //check if our thumbnail has a time attribute:
1720 if( my_thumb_src.indexOf('t=') !== -1){
1721 var time_ntp = seconds2npt ( options.time + parseInt(this.start_offset) );
1722 my_thumb_src = getURLParamReplace( my_thumb_src, { 't':time_ntp, 'size': options.size } );
1723 }
1724 var thumb_class = (typeof options['thumb_class'] != 'undefined' ) ? options['thumb_class'] : '';
1725 return '<div class="ui-corner-all ' + thumb_class + '" src="' + my_thumb_src + '" '+
1726 'style="height:' + options.height + 'px;' +
1727 'width:' + options.width + 'px" >' +
1728 '<img src="' + my_thumb_src +'" '+
1729 'style="height:' + options.height + 'px;' +
1730 'width:' + options.width + 'px">' +
1731 '</div>';
1732 },
1733 updateThumbTimeNTP:function( time){
1734 this.updateThumbTime( npt2seconds(time) - parseInt(this.start_offset) );
1735 },
1736 updateThumbTime:function( float_sec ){
1737 //js_log('updateThumbTime:'+float_sec);
1738 var _this = this;
1739 if( typeof this.org_thum_src=='undefined' ){
1740 this.org_thum_src = this.media_element.getThumbnailURL();
1741 }
1742 if( this.org_thum_src.indexOf('t=') !== -1){
1743 this.last_thumb_url = getURLParamReplace(this.org_thum_src,
1744 { 't' : seconds2npt( float_sec + parseInt(this.start_offset)) } );
1745 if(!this.thumbnail_updating){
1746 this.updateThumbnail(this.last_thumb_url ,false);
1747 this.last_thumb_url =null;
1748 }
1749 }
1750 },
1751 //for now provide a src url .. but need to figure out how to copy frames from video for plug-in based thumbs
1752 updateThumbPerc:function( perc ){
1753 return this.updateThumbTime( (this.getDuration() * perc) );
1754 },
1755 //updates the thumbnail if the thumbnail is being displayed
1756 updateThumbnail : function(src, quick_switch){
1757 //make sure we don't go to the same url if we are not already updating:
1758 if( !this.thumbnail_updating && $j('#img_thumb_'+this.id).attr('src')== src )
1759 return false;
1760 //if we are already updating don't issue a new update:
1761 if( this.thumbnail_updating && $j('#new_img_thumb_'+this.id).attr('src')== src )
1762 return false;
1763
1764 js_log('update thumb: ' + src);
1765
1766 if(quick_switch){
1767 $j('#img_thumb_'+this.id).attr('src', src);
1768 }else{
1769 var _this = this;
1770 //if still animating remove new_img_thumb_
1771 if(this.thumbnail_updating==true)
1772 $j('#new_img_thumb_'+this.id).stop().remove();
1773
1774 if(this.thumbnail_disp){
1775 js_log('set to thumb:'+ src);
1776 this.thumbnail_updating=true;
1777 $j('#dc_'+this.id).append('<img src="'+src+'" ' +
1778 'style="display:none;position:absolute;zindex:2;top:0px;left:0px;" ' +
1779 'width="'+this.width+'" height="'+this.height+'" '+
1780 'id = "new_img_thumb_'+this.id+'" />');
1781 //js_log('appended: new_img_thumb_');
1782 $j('#new_img_thumb_'+this.id).fadeIn("slow", function(){
1783 //once faded in remove org and rename new:
1784 $j('#img_thumb_'+_this.id).remove();
1785 $j('#new_img_thumb_'+_this.id).attr('id', 'img_thumb_'+_this.id);
1786 $j('#img_thumb_'+_this.id).css('zindex','1');
1787 _this.thumbnail_updating=false;
1788 //js_log("done fadding in "+ $j('#img_thumb_'+_this.id).attr("src"));
1789
1790 //if we have a thumb queued update to that
1791 if(_this.last_thumb_url){
1792 var src_url =_this.last_thumb_url;
1793 _this.last_thumb_url=null;
1794 _this.updateThumbnail(src_url);
1795 }
1796 });
1797 }
1798 }
1799 },
1800 /** Returns the HTML code for the video when it is in thumbnail mode.
1801 This includes the specified thumbnail as well as buttons for
1802 playing, configuring the player, inline cmml display, HTML linkback,
1803 download, and embed code.
1804 */
1805 getThumbnailHTML : function ()
1806 {
1807 var thumb_html = '';
1808 var class_atr='';
1809 var style_atr='';
1810 //if(this.class)class_atr = ' class="'+this.class+'"';
1811 //if(this.style)style_atr = ' style="'+this.style+'"';
1812 // else style_atr = 'overflow:hidden;height:'+this.height+'px;width:'+this.width+'px;';
1813 this.thumbnail = this.media_element.getThumbnailURL();
1814
1815 //put it all in the div container dc_id
1816 thumb_html+= '<div id="dc_'+this.id+'" style="position:relative;'+
1817 ' overflow:hidden; top:0px; left:0px; width:'+this.playerPixelWidth()+'px; height:'+this.playerPixelHeight()+'px; z-index:0;">'+
1818 '<img width="'+this.playerPixelWidth()+'" height="'+this.playerPixelHeight()+'" style="position:relative;width:'+this.playerPixelWidth()+';height:'+this.playerPixelHeight()+'"' +
1819 ' id="img_thumb_'+this.id+'" src="' + this.thumbnail + '">';
1820
1821 if(this.play_button == true && this.controls == true)
1822 thumb_html+=this.getPlayButton();
1823
1824 thumb_html+='</div>';
1825 return thumb_html;
1826 },
1827 getEmbeddingHTML:function()
1828 {
1829 var thumbnail = this.media_element.getThumbnailURL();
1830
1831 var embed_thumb_html;
1832 if(thumbnail.substring(0,1)=='/'){
1833 eURL = parseUri(mv_embed_path);
1834 embed_thumb_html = eURL.protocol + '://' + eURL.host + thumbnail;
1835 //js_log('set from mv_embed_path:'+embed_thumb_html);
1836 }else{
1837 embed_thumb_html = (thumbnail.indexOf('http://')!=-1)?thumbnail:mv_embed_path + thumbnail;
1838 }
1839 var embed_code_html = '&lt;script type=&quot;text/javascript&quot; ' +
1840 'src=&quot;'+mv_embed_path+'mv_embed.js&quot;&gt;&lt;/script&gt' +
1841 '&lt;video ';
1842 if(this.roe){
1843 embed_code_html+='roe=&quot;'+this.roe+'&quot; &gt;';
1844 }else{
1845 embed_code_html+='src=&quot;'+this.src+'&quot; ' +
1846 'thumbnail=&quot;'+embed_thumb_html+'&quot;&gt;';
1847 }
1848 //close the video tag
1849 embed_code_html+='&lt;/video&gt;';
1850
1851 return embed_code_html;
1852 },
1853 doOptionsHTML:function()
1854 {
1855 var sel_id = (this.pc!=null)?this.pc.pp.id:this.id;
1856 var pos = $j('#options_button_'+sel_id).offset();
1857 pos['top']=pos['top']+24;
1858 pos['left']=pos['left']-124;
1859 //js_log('pos of options button: t:'+pos['top']+' l:'+ pos['left']);
1860 $j('#mv_vid_options_'+sel_id).css(pos).toggle();
1861 return;
1862 },
1863 getPlayButton:function(id){
1864 if(!id)id=this.id;
1865 return '<div title="' + gM('play_clip') + '" id="big_play_link_'+id+'" class="large_play_button" '+
1866 'style="left:'+((this.playerPixelWidth()-130)/2)+'px;'+
1867 'top:' + ((this.playerPixelHeight()-96)/2) + 'px;">'+
1868 '<img src="' + mv_skin_img_path + 'player_big_play_button.png">'+
1869 '</div>';
1870 },
1871 doLinkBack:function(){
1872 if(this.roe && this.media_element.addedROEData==false){
1873 var _this = this;
1874 this.displayHTML(gM('loading_txt'));
1875 do_request(this.roe, function(data)
1876 {
1877 _this.media_element.addROE(data);
1878 _this.doLinkBack();
1879 });
1880 }else{
1881 if(this.media_element.linkback){
1882 window.location = this.media_element.linkback;
1883 }else{
1884 this.displayHTML(gM('could_not_find_linkback'));
1885 }
1886 }
1887 },
1888 //display the code to remotely embed this video:
1889 showEmbedCode : function(embed_code){
1890 if(!embed_code)
1891 embed_code = this.getEmbeddingHTML();
1892 var o='';
1893 if(this.linkback){
1894 o+='<a class="email" href="'+this.linkback+'">Share Clip via Link</a> '+
1895 '<p>or</p> ';
1896 }
1897 o+='<div>' +
1898 '<span style="color:#FFF;font-size:14px;">Embed Clip in Blog or Site</span><br>'+
1899 '<span style="color:#FFF;font-size:12px;"><a style="color:red" href="http://metavid.org/wiki/Security_Notes_on_Remote_Embedding">'+
1900 'Read This</a> before embeding.</span>'+
1901 '<div class="embed_code"> '+
1902 '<textarea onClick="this.select();" id="embedding_user_html_'+this.id+'" name="embed">' +
1903 embed_code+
1904 '</textarea> '+
1905 '<button onClick="$j(\'#'+this.id+'\').get(0).copyText(); return false;" class="copy_to_clipboard">Copy to Clipboard</button> '+
1906 '</div> '+
1907 '</div>';
1908 this.displayHTML(o);
1909 },
1910 copyText:function(){
1911 $j('#embedding_user_html_'+this.id).focus().select();
1912 if(document.selection){
1913 CopiedTxt = document.selection.createRange();
1914 CopiedTxt.execCommand("Copy");
1915 }
1916 },
1917 showTextInterface:function(){
1918 var _this = this;
1919 //display the text container with loading text:
1920 //@@todo support position config
1921 var loc = $j(this).position();
1922 if($j('#metaBox_'+this.id).length==0){
1923 $j(this).after('<div class="ui-widget ui-widget-content ui-corner-all" style="position:absolute;z-index:10;'+
1924 'top:' + (loc.top) + 'px;' +
1925 'left:' + (parseInt( loc.left ) + parseInt(this.width) + 10 )+'px;' +
1926 'height:'+ parseInt( this.height )+'px;width:400px;' +
1927 'display:none;" ' +
1928 'id="metaBox_' + this.id + '">'+
1929 gM('loading_txt') +
1930 '</div>');
1931 }
1932 //fade in the text display
1933 $j('#metaBox_'+this.id).fadeIn("fast");
1934 //check if textObj present:
1935 if(typeof this.textInterface == 'undefined' ){
1936 //load the default text interface:
1937 mvJsLoader.doLoad([
1938 'mvTextInterface',
1939 '$j.fn.hoverIntent'
1940 ], function(){
1941 _this.textInterface = new mvTextInterface( _this );
1942 //show interface
1943 _this.textInterface.show();
1944 js_log("NEW TEXT INTERFACE");
1945 for(var i in _this.textInterface.availableTracks){
1946 js_log("tracks in new interface: "+_this.id+ ' tid:' + i);
1947 }
1948 }
1949 );
1950 }else{
1951 //show interface
1952 this.textInterface.show();
1953 }
1954 },
1955 closeTextInterface:function(){
1956 js_log('closeTextInterface '+ typeof this.textInterface);
1957 if(typeof this.textInterface !== 'undefined' ){
1958 this.textInterface.close();
1959 }
1960 },
1961 /** Generic function to display custom HTML inside the mv_embed element.
1962 The code should call the closeDisplayedHTML function to close the
1963 display of the custom HTML and restore the regular mv_embed display.
1964 @param {String} HTML code for the selection list.
1965 */
1966 displayHTML:function(html_code)
1967 {
1968 var sel_id = (this.pc!=null)?this.pc.pp.id:this.id;
1969
1970 if(!this.supports['overlays'])
1971 this.stop();
1972
1973 //put select list on-top
1974 //make sure the parent is relatively positioned:
1975 $j('#'+sel_id).css('position', 'relative');
1976 //set height width (check for playlist container)
1977 var width = (this.pc)?this.pc.pp.width:this.playerPixelWidth();
1978 var height = (this.pc)?this.pc.pp.height:this.playerPixelHeight();
1979
1980 if(this.pc)
1981 height+=(this.pc.pp.pl_layout.title_bar_height + this.pc.pp.pl_layout.control_height);
1982
1983 var fade_in = true;
1984 if($j('#blackbg_'+sel_id).length!=0)
1985 {
1986 fade_in = false;
1987 $j('#blackbg_'+sel_id).remove();
1988 }
1989 //fade in a black bg div ontop of everything
1990 var div_code = '<div id="blackbg_'+sel_id+'" class="videoComplete" ' +
1991 'style="height:'+parseInt(height)+'px;width:'+parseInt(width)+'px;">'+
1992 '<div class="videoOptionsComplete">'+
1993 //@@TODO: this style should go to .css
1994 '<span style="float:right;margin-right:10px">' +
1995 '<a href="#" style="color:white;" onClick="$j(\'#'+sel_id+'\').get(0).closeDisplayedHTML();return false;">close</a>' +
1996 '</span>'+
1997 '<div id="mv_disp_inner_'+sel_id+'" style="padding-top:10px;">'+
1998 html_code
1999 +'</div>'+
2000 '</div></div>';
2001 $j('#'+sel_id).prepend(div_code);
2002 if (fade_in)
2003 $j('#blackbg_'+sel_id).fadeIn("slow");
2004 else
2005 $j('#blackbg_'+sel_id).show();
2006 return false; //onclick action return false
2007 },
2008 /** Close the custom HTML displayed using displayHTML and restores the
2009 regular mv_embed display.
2010 */
2011 closeDisplayedHTML:function(){
2012 var sel_id = (this.pc!=null)?this.pc.pp.id:this.id;
2013 $j('#blackbg_'+sel_id).fadeOut("slow", function(){
2014 $j('#blackbg_'+sel_id).remove();
2015 });
2016 return false; //onclick action return false
2017 },
2018 selectPlaybackMethod:function(){
2019 //get id (in case where we have a parent container)
2020 var this_id = (this.pc!=null)?this.pc.pp.id:this.id;
2021
2022 var _this=this;
2023 var out= '<span style="color:#FFF;background-color:black;"><blockquote style="background-color:black;">';
2024 var _this=this;
2025 //js_log('selected src'+ _this.media_element.selected_source.url);
2026 $j.each( this.media_element.getPlayableSources(), function(source_id, source){
2027 var default_player = embedTypes.players.defaultPlayer( source.getMIMEType() );
2028
2029 var is_selected = (source == _this.media_element.selected_source);
2030 var image_src = mv_skin_img_path ;
2031
2032 //set the Playable source type:
2033 if( source.mime_type == 'video/x-flv' ){
2034 image_src += 'flash_icon_';
2035 }else if( source.mime_type == 'video/h264'){
2036 //for now all mp4 content is pulled from archive.org (so use archive.org icon)
2037 image_src += 'archive_org_';
2038 }else{
2039 image_src += 'fish_xiph_org_';
2040 }
2041 image_src += is_selected ? 'color':'bw';
2042 image_src += '.png';
2043
2044 if (default_player)
2045 {
2046 out += '<img src="'+image_src+'"/>';
2047 if( ! is_selected )
2048 out+='<a href="#" class="sel_source" id="sc_' + source_id + '_' + default_player.id +'">';
2049 out += source.getTitle()+ (is_selected?'</a>':'') + ' ';
2050
2051 //output the player select code:
2052 var supporting_players = embedTypes.players.getMIMETypePlayers( source.getMIMEType() );
2053 out+='<div id="player_select_list_' + source_id + '" class="player_select_list"><ul>';
2054 for(var i=0; i < supporting_players.length ; i++){
2055 if( _this.selected_player.id == supporting_players[i].id && is_selected ){
2056 out+='<li style="border-style:dashed;margin-left:20px;">'+
2057 '<img border="0" width="16" height="16" src="' + mv_skin_img_path + 'plugin.png">' +
2058 supporting_players[i].getName() +
2059 '</li>';
2060 }else{
2061 //else gray plugin and the plugin with link to select
2062 out+='<li style="margin-left:20px;">'+
2063 '<a href="#" class="sel_source" id="sc_' + source_id + '_' + supporting_players[i].id +'">'+
2064 '<img border="0" width="16" height="16" src="' + mv_skin_img_path + 'plugin_disabled.png">'+
2065 supporting_players[i].getName() +
2066 '</a>'+
2067 '</li>';
2068 }
2069 }
2070 out+='</ul></div>';
2071 }else
2072 out+= source.getTitle() + ' - no player available';
2073 });
2074 out+='</blockquote></span>';
2075 this.displayHTML(out);
2076
2077 //set up the click bindings:
2078 $j('.sel_source').each(function(){
2079 $j(this).click(function(){
2080 var iparts = $j(this).attr( 'id' ).replace(/sc_/,'').split('_');
2081 var source_id = iparts[0];
2082 var default_player_id = iparts[1];
2083 js_log('source id: ' + source_id + ' player id: ' + default_player_id);
2084
2085 $j('#' + this_id ).get(0).closeDisplayedHTML();
2086 $j('#' + _this.id ).get(0).media_element.selectSource( source_id );
2087
2088 embedTypes.players.userSelectPlayer( default_player_id,
2089 _this.media_element.sources[ source_id ].getMIMEType() );
2090
2091 //be sure to issue a stop
2092 $j('#' + this_id ).get(0).stop();
2093
2094 //don't follow the empty # link:
2095 return false;
2096 });
2097 });
2098 },
2099 showVideoDownload:function(){
2100 //load the roe if available (to populate out download options:
2101 //js_log('f:showVideoDownload '+ this.roe + ' ' + this.media_element.addedROEData);
2102 if(this.roe && this.media_element.addedROEData == false){
2103 var _this = this;
2104 this.displayHTML(gM('loading_txt'));
2105 do_request(this.roe, function(data)
2106 {
2107 _this.media_element.addROE(data);
2108 $j('#mv_disp_inner_'+_this.id).html( _this.getShowVideoDownload() );
2109 });
2110 }else{
2111 this.displayHTML( this.getShowVideoDownload() );
2112 }
2113 },
2114 getShowVideoDownload:function(){
2115 var out='<div style="color:white">' +
2116 '<b style="color:white;">'+gM('download_segment')+'</b><br>';
2117 out+='<blockquote style="background:#000">'+
2118 gM('download_right_click') + '</blockquote><br>';
2119 var dl_list='';
2120 var dl_txt_list='';
2121 $j.each(this.media_element.getSources(), function(index, source){
2122 var dl_line = '<li>' + '<a style="color:white" href="' + source.getURI() +'"> '
2123 + source.getTitle()+'</a> '+ '</li>'+"\n";
2124 if( source.getURI().indexOf('?t=')!==-1){
2125 out+=dl_line;
2126 }else if(this.getMIMEType()=="text/cmml" || this.getMIMEType()=="text/x-srt"){
2127 dl_txt_list+=dl_line;
2128 }else{
2129 dl_list+=dl_line;
2130 }
2131 });
2132
2133 if(dl_list!='')
2134 out+=gM('download_full') + '<blockquote style="background:#000">' + dl_list + '</blockquote>';
2135 if(dl_txt_list!='')
2136 out+=gM('download_text')+'<blockquote style="background:#000">' + dl_txt_list +'</blockquote>';
2137 out+='</div>';
2138 return out;
2139 },
2140 /*
2141 * base embed controls
2142 * the play button calls
2143 */
2144 play:function(){
2145 var this_id = (this.pc!=null)?this.pc.pp.id:this.id;
2146
2147 //js_log( "mv_embed play:" + this.id);
2148 //js_log('thum disp:'+this.thumbnail_disp);
2149 //check if thumbnail is being displayed and embed html
2150 if( this.thumbnail_disp ){
2151 if( !this.selected_player ){
2152 js_log('no selected_player');
2153 //this.innerHTML = this.getPluginMissingHTML();
2154 //$j(this).html(this.getPluginMissingHTML());
2155 $j('#'+this.id).html( this.getPluginMissingHTML() );
2156 }else{
2157 this.doEmbedHTML();
2158 this.onClipDone_disp=false;
2159 this.paused=false;
2160 this.thumbnail_disp=false;
2161 }
2162 }else{
2163 //the plugin is already being displayed
2164 this.paused=false; //make sure we are not "paused"
2165 this.seeking=false;
2166 }
2167
2168 $j("#mv_play_pause_button_" + this_id + ' span').removeClass('ui-icon-play').addClass('ui-icon-pause');
2169 $j("#mv_play_pause_button_" + this_id).unbind().btnBind().click(function(){
2170 $j('#' + this_id ).get(0).pause();
2171 }).attr('title', gM('pause_clip'));
2172
2173 },
2174 load:function(){
2175 //should be done by child (no base way to load assets)
2176 js_log('baseEmbed:load call');
2177 },
2178 getSrc:function(){
2179 return this.media_element.selected_source.getURI( this.seek_time_sec );
2180 },
2181 /*
2182 * base embed pause
2183 * there is no general way to pause the video
2184 * must be overwritten by embed object to support this functionality.
2185 */
2186 pause: function(){
2187 var this_id = (this.pc!=null)?this.pc.pp.id:this.id;
2188 //js_log('mv_embed:do pause');
2189 //(playing) do pause
2190 this.paused = true;
2191 //update the ctrl "paused state"
2192 $j("#mv_play_pause_button_" + this_id + ' span').removeClass('ui-icon-pause').addClass('ui-icon-play');
2193 $j("#mv_play_pause_button_" + this_id).unbind().btnBind().click(function(){
2194 $j('#'+this_id).get(0).play();
2195 }).attr('title', gM('play_clip'));
2196 },
2197 /*
2198 * base embed stop (can be overwritten by the plugin)
2199 */
2200 stop: function(){
2201 var _this = this;
2202 js_log('mvEmbed:stop:'+this.id);
2203
2204 //no longer seeking:
2205 this.didSeekJump=false;
2206
2207 //first issue pause to update interface (only call the parent)
2208 if(this['parent_pause']){
2209 this.parent_pause();
2210 }else{
2211 this.pause();
2212 }
2213
2214 //reset the currentTime:
2215 this.currentTime=0;
2216 //check if thumbnail is being displayed in which case do nothing
2217 if( this.thumbnail_disp ){
2218 //already in stooped state
2219 js_log('already in stopped state');
2220 }else{
2221 //rewrite the html to thumbnail disp
2222 this.doThumbnailHTML();
2223 this.bufferedPercent=0; //reset buffer state
2224 this.setSliderValue(0);
2225 this.setStatus( this.getTimeReq() );
2226 }
2227
2228 //make sure the big playbutton is has click action:
2229 $j('#big_play_link_' + _this.id).unbind('click').click(function(){
2230 $j('#' +_this.id).get(0).play();
2231 });
2232
2233 if(this.update_interval)
2234 {
2235 clearInterval(this.update_interval);
2236 this.update_interval = null;
2237 }
2238 },
2239 toggleMute:function(){
2240 var this_id = (this.pc!=null)?this.pc.pp.id:this.id;
2241 if(this.muted){
2242 this.muted=false;
2243 $j('#volume_control_'+this_id + ' span').removeClass('ui-icon-volume-off').addClass('ui-icon-volume-on');
2244 $j('#volume_bar_'+this_id).slider('value', 100);
2245 this.updateVolumen(1);
2246 }else{
2247 this.muted=true;
2248 $j('#volume_control_'+this_id + ' span').removeClass('ui-icon-volume-on').addClass('ui-icon-volume-off');
2249 $j('#volume_bar_'+this_id).slider('value', 0);
2250 this.updateVolumen(0);
2251 }
2252 js_log('f:toggleMute::' + this.muted);
2253 },
2254 updateVolumen:function(perc){
2255 js_log('update volume not supported with current playback type');
2256 },
2257 fullscreen:function(){
2258 js_log('fullscreen not supported with current playback type');
2259 },
2260 /* returns bool true if playing or paused, false if stooped
2261 */
2262 isPlaying : function(){
2263 if(this.thumbnail_disp){
2264 //in stoped state
2265 return false;
2266 }else if( this.paused ){
2267 //paused state
2268 return false;
2269 }else{
2270 return true;
2271 }
2272 },
2273 isPaused : function(){
2274 return this.isPlaying() && this.paused;
2275 },
2276 isStoped : function(){
2277 return this.thumbnail_disp;
2278 },
2279 playlistSupport:function(){
2280 //by default not supported (implemented in js)
2281 return false;
2282 },
2283 postEmbedJS:function(){
2284 return '';
2285 },
2286 //do common monitor code like update the playhead and play status
2287 //plugin objects are responsible for updating currentTime
2288 monitor:function(){
2289 if( this.currentTime && this.currentTime > 0 && this.duration){
2290 if( !this.userSlide ){
2291 if( this.start_offset ){
2292 //if start offset include that calculation
2293 this.setSliderValue( ( this.currentTime - this.start_offset ) / this.duration );
2294 this.setStatus( seconds2npt(this.currentTime) + '/'+ seconds2npt(parseFloat(this.start_offset)+parseFloat(this.duration) ));
2295 }else{
2296 this.setSliderValue( this.currentTime / this.duration );
2297 this.setStatus( seconds2npt(this.currentTime) + '/' + seconds2npt(this.duration ));
2298 }
2299 }
2300 }else{
2301 //js_log(' ct:' + this.currentTime + ' dur: ' + this.duration);
2302 if( this.isStoped() ){
2303 this.setStatus( this.getTimeReq() );
2304 }else if( this.isPaused() ){
2305 this.setStatus( "paused" );
2306 }else if( this.isPlaying() ){
2307 if( this.currentTime && ! this.duration )
2308 this.setStatus( seconds2npt( this.currentTime ) + ' /' );
2309 else
2310 this.setStatus(" - - - ");
2311 }else{
2312 this.setStatus( this.getTimeReq() );
2313 }
2314 }
2315 //update buffer information
2316 this.updateBufferStatus();
2317
2318 //update monitorTimerId to call child monitor
2319 if( ! this.monitorTimerId ){
2320 //make sure an instance of this.id exists:
2321 if( document.getElementById(this.id) ){
2322 this.monitorTimerId = setInterval('$j(\'#'+this.id+'\').get(0).monitor()', 250);
2323 }
2324 }
2325 },
2326 stopMonitor:function(){
2327 if( this.monitorTimerId != 0 )
2328 {
2329 clearInterval( this.monitorTimerId );
2330 this.monitorTimerId = 0;
2331 }
2332 },
2333 updateBufferStatus: function(){
2334
2335 //build the buffer targeet based for playlist vs clip
2336 var buffer_select = (this.pc) ?
2337 '#cl_status_' + this.id + ' .mv_buffer':
2338 '#mv_play_head_' + this.id + ' .mv_buffer';
2339
2340 //update the buffer progress bar (if available )
2341 if( this.bufferedPercent != 0 ){
2342 //js_log('bufferedPercent: ' + this.bufferedPercent);
2343 if(this.bufferedPercent > 1)
2344 this.bufferedPercent=1;
2345
2346 $j(buffer_select).css("width", (this.bufferedPercent*100) +'%' );
2347 }else{
2348 $j(buffer_select).css("width", '0px' );
2349 }
2350 },
2351 relativeCurrentTime: function(){
2352 if(!this.start_offset)
2353 this.start_offset =0;
2354 var rt = this.currentTime - this.start_offset;
2355 if( rt < 0 ) //should not happen but does.
2356 return 0;
2357 return rt;
2358 },
2359 getPluginEmbed : function(){
2360 if (window.document[this.pid]){
2361 return window.document[this.pid];
2362 }
2363 if ($j.browser.msie){
2364 return document.getElementById(this.pid );
2365 }else{
2366 if (document.embeds && document.embeds[this.pid])
2367 return document.embeds[this.pid];
2368 }
2369 return null;
2370 },
2371 //HELPER Functions for selected source
2372 /*
2373 * returns the selected source url for players to play
2374 */
2375 getURI : function( seek_time_sec ){
2376 return this.media_element.selected_source.getURI( this.seek_time_sec );
2377 },
2378 supportsURLTimeEncoding: function(){
2379 //do head request if on the same domain
2380 return this.media_element.selected_source.URLTimeEncoding;
2381 },
2382 setSliderValue: function(perc, hide_progress){
2383 if(this.controls){
2384 var this_id = (this.pc)?this.pc.pp.id:this.id;
2385 var val = parseInt( perc*1000 );
2386 $j('#mv_play_head_'+this_id).slider('value', val);
2387 }
2388 //js_log('set#mv_seeker_slider_'+this_id + ' perc in: ' + perc + ' * ' + $j('#mv_seeker_'+this_id).width() + ' = set to: '+ val + ' - '+ Math.round(this.mv_seeker_width*perc) );
2389 //js_log('op:' + offset_perc + ' *('+perc+' * ' + $j('#slider_'+id).width() + ')');
2390 },
2391 highlightPlaySection:function(options){
2392 js_log('highlightPlaySection');
2393 var this_id = (this.pc)?this.pc.pp.id:this.id;
2394 var dur = this.getDuration();
2395 var hide_progress = true;
2396 //set the left percet and update the slider:
2397 rel_start_sec = npt2seconds( options['start']);
2398 //remove the start_offset if relevent:
2399 if(this.start_offset)
2400 rel_start_sec = rel_start_sec - this.start_offset
2401
2402 var slider_perc=0;
2403 if( rel_start_sec <= 0 ){
2404 left_perc =0;
2405 options['start'] = seconds2npt( this.start_offset );
2406 rel_start_sec=0;
2407 this.setSliderValue( 0 , hide_progress);
2408 }else{
2409 left_perc = parseInt( (rel_start_sec / dur)*100 ) ;
2410 slider_perc = (left_perc / 100);
2411 }
2412 js_log("slider perc:" + slider_perc);
2413 if( ! this.isPlaying() ){
2414 this.setSliderValue( slider_perc , hide_progress);
2415 }
2416
2417 width_perc = parseInt( (( npt2seconds( options['end'] ) - npt2seconds( options['start'] ) ) / dur)*100 ) ;
2418 if( (width_perc + left_perc) > 100 ){
2419 width_perc = 100 - left_perc;
2420 }
2421 //js_log('should hl: '+rel_start_sec+ '/' + dur + ' re:' + rel_end_sec+' lp:' + left_perc + ' width: ' + width_perc);
2422 $j('#mv_seeker_' + this_id + ' .mv_highlight').css({
2423 'left':left_perc+'%',
2424 'width':width_perc+'%'
2425 }).show();
2426
2427 this.jump_time = options['start'];
2428 this.seek_time_sec = npt2seconds( options['start']);
2429 //trim output to
2430 this.setStatus( gM('seek_to')+' '+ seconds2npt( this.seek_time_sec ) );
2431 js_log('DO update: ' + this.jump_time);
2432 this.updateThumbTime( rel_start_sec );
2433 },
2434 hideHighlight:function(){
2435 var this_id = (this.pc)?this.pc.pp.id:this.id;
2436 $j('#mv_seeker_' + this_id + ' .mv_highlight').hide();
2437 this.setStatus( this.getTimeReq() );
2438 this.setSliderValue( 0 );
2439 },
2440 setStatus:function(value){
2441 var id = (this.pc)?this.pc.pp.id:this.id;
2442 //update status:
2443 $j('#mv_time_'+id).html(value);
2444 }
2445 }
2446
2447
2448
2449 /**
2450 * mediaPlayer represents a media player plugin.
2451 * @param {String} id id used for the plugin.
2452 * @param {Array<String>} supported_types n array of supported MIME types.
2453 * @param {String} library external script containing the plugin interface code. (mv_<library>Embed.js)
2454 * @constructor
2455 */
2456 function mediaPlayer(id, supported_types, library)
2457 {
2458 this.id=id;
2459 this.supported_types = supported_types;
2460 this.library = library;
2461 this.loaded = false;
2462 this.loading_callbacks = new Array();
2463 return this;
2464 }
2465 mediaPlayer.prototype =
2466 {
2467 id:null,
2468 supported_types:null,
2469 library:null,
2470 loaded:false,
2471 loading_callbacks:null,
2472 supportsMIMEType : function(type)
2473 {
2474 for (var i=0; i < this.supported_types.length; i++)
2475 if(this.supported_types[i] == type)
2476 return true;
2477 return false;
2478 },
2479 getName : function()
2480 {
2481 return gM('mv_ogg-player-' + this.id);
2482 },
2483 load : function(callback){
2484 var libName = this.library+'Embed';
2485 if( mvJsLoader.checkObjPath( libName ) ){
2486 js_log('plugin loaded, do callback:');
2487 callback();
2488 }else{
2489 var _this = this;
2490 //jQuery based get script does not work so well.
2491 mvJsLoader.doLoad([
2492 libName
2493 ],function(){
2494 callback();
2495 });
2496 }
2497 }
2498 }
2499 /* players and supported mime types
2500 @@todo ideally we query the plugin to get what mime types it supports in practice not always reliable/avaliable
2501 */
2502 var flowPlayer = new mediaPlayer('flowplayer',['video/x-flv', 'video/h264'],'flash');
2503
2504 var omtkPlayer = new mediaPlayer('omtkplayer',['audio/ogg'], 'omtk' );
2505
2506 var cortadoPlayer = new mediaPlayer('cortado',['video/ogg', 'audio/ogg'],'java');
2507 var videoElementPlayer = new mediaPlayer('videoElement',['video/ogg', 'audio/ogg'],'native');
2508
2509 var vlcMineList = ['video/ogg','audio/ogg', 'video/x-flv', 'video/mp4', 'video/h264'];
2510 var vlcMozillaPlayer = new mediaPlayer('vlc-mozilla',vlcMineList,'vlc');
2511 var vlcActiveXPlayer = new mediaPlayer('vlc-activex',vlcMineList,'vlc');
2512
2513 //add generic
2514 var oggPluginPlayer = new mediaPlayer('oggPlugin',['video/ogg'],'generic');
2515
2516 //depricate quicktime in favor of safari native
2517 //var quicktimeMozillaPlayer = new mediaPlayer('quicktime-mozilla',['video/ogg'],'quicktime');
2518 //var quicktimeActiveXPlayer = new mediaPlayer('quicktime-activex',['video/ogg'],'quicktime');
2519
2520 var htmlPlayer = new mediaPlayer('html',['text/html', 'image/jpeg', 'image/png', 'image/svg'], 'html');
2521
2522 /**
2523 * mediaPlayers is a collection of mediaPlayer objects supported by the client.
2524 * It could be merged with embedTypes, since there is one embedTypes per script
2525 * and one mediaPlayers per embedTypes.
2526 */
2527 function mediaPlayers()
2528 {
2529 this.init();
2530 }
2531
2532 mediaPlayers.prototype =
2533 {
2534 players : null,
2535 preference : null,
2536 default_players : {},
2537 init : function()
2538 {
2539 this.players = new Array();
2540 this.loadPreferences();
2541
2542 //set up default players order for each library type
2543 this.default_players['video/x-flv'] = ['flash','vlc'];
2544 this.default_players['video/h264'] = ['flash', 'vlc'];
2545
2546 this.default_players['video/ogg'] = ['native','vlc','java', 'generic'];
2547 this.default_players['application/ogg'] = ['native','vlc','java', 'generic'];
2548 this.default_players['audio/ogg'] = ['native','vlc', 'java', 'omtk' ];
2549 this.default_players['video/mp4'] = ['vlc'];
2550
2551 this.default_players['text/html'] = ['html'];
2552 this.default_players['image/jpeg'] = ['html'];
2553
2554 },
2555 addPlayer : function(player, mime_type)
2556 {
2557 //js_log('Adding ' + player.id + ' with mime_type ' + mime_type);
2558 for (var i =0; i < this.players.length; i++){
2559 if (this.players[i].id == player.id)
2560 {
2561 if(mime_type!=null)
2562 {
2563 //make sure the mime_type is not already there:
2564 var add_mime = true;
2565 for(var j=0; j < this.players[i].supported_types.length; j++ ){
2566 if( this.players[i].supported_types[j]== mime_type)
2567 add_mime=false;
2568 }
2569 if(add_mime)
2570 this.players[i].supported_types.push(mime_type);
2571 }
2572 return;
2573 }
2574 }
2575 //player not found:
2576 if(mime_type!=null)
2577 player.supported_types.push(mime_type);
2578
2579 this.players.push( player );
2580 },
2581 getMIMETypePlayers : function(mime_type)
2582 {
2583 var mime_players = new Array();
2584 var _this = this;
2585 var inx = 0;
2586 if( this.default_players[mime_type] ){
2587 $j.each( this.default_players[mime_type], function(d, lib){
2588 var library = _this.default_players[mime_type][d];
2589 for ( var i=0; i < _this.players.length; i++ ){
2590 if ( _this.players[i].library == library && _this.players[i].supportsMIMEType(mime_type) ){
2591 mime_players[ inx ] = _this.players[i];
2592 inx++;
2593 }
2594 }
2595 });
2596 }
2597 return mime_players;
2598 },
2599 defaultPlayer : function(mime_type)
2600 {
2601 js_log("f:defaultPlayer: " + mime_type);
2602 var mime_players = this.getMIMETypePlayers(mime_type);
2603 if( mime_players.length > 0)
2604 {
2605 // check for prior preference for this mime type
2606 for( var i=0; i < mime_players.length; i++ ){
2607 if( mime_players[i].id==this.preference[mime_type] )
2608 return mime_players[i];
2609 }
2610 // otherwise just return the first compatible player
2611 // (it will be chosen according to the default_players list
2612 return mime_players[0];
2613 }
2614 js_log( 'No default player found for ' + mime_type );
2615 return null;
2616 },
2617 userSelectFormat : function (mime_format){
2618 this.preference['format_prefrence'] = mime_format;
2619 this.savePreferences();
2620 },
2621 userSelectPlayer : function(player_id, mime_type)
2622 {
2623 var selected_player=null;
2624 for(var i=0; i < this.players.length; i++){
2625 if(this.players[i].id == player_id)
2626 {
2627 selected_player = this.players[i];
2628 js_log('choosing ' + player_id + ' for ' + mime_type);
2629 this.preference[mime_type]=player_id;
2630 this.savePreferences();
2631 break;
2632 }
2633 }
2634 if( selected_player )
2635 {
2636 for(var i=0; i < global_player_list.length; i++)
2637 {
2638 var embed = $j('#'+global_player_list[i]).get(0);
2639 if(embed.media_element.selected_source && (embed.media_element.selected_source.mime_type == mime_type))
2640 {
2641 embed.selectPlayer(selected_player);
2642 js_log('using ' + embed.selected_player.getName() + ' for ' + embed.media_element.selected_source.getTitle());
2643 }
2644 }
2645 }
2646 },
2647 loadPreferences : function()
2648 {
2649 this.preference = new Object();
2650 // see if we have a cookie set to a clientSupported type:
2651 var cookieVal = $j.cookie( 'ogg_player_exp' );
2652 if (cookieVal)
2653 {
2654 var pairs = cookieVal.split('&');
2655 for(var i=0; i < pairs.length; i++)
2656 {
2657 var name_value = pairs[i].split('=');
2658 this.preference[name_value[0]]=name_value[1];
2659 //js_log('load preference for ' + name_value[0] + ' is ' + name_value[1]);
2660 }
2661 }
2662 },
2663 savePreferences : function()
2664 {
2665 var cookieVal = '';
2666 for(var i in this.preference)
2667 cookieVal+= i + '='+ this.preference[i] + '&';
2668
2669 cookieVal=cookieVal.substr(0, cookieVal.length-1);
2670 var week = 7*86400*1000;
2671 $j.cookie( 'ogg_player_exp', cookieVal, { 'expires':week } );
2672 }
2673 };
2674
2675 /*
2676 * embedTypes object handles setting and getting of supported embed types:
2677 * closely mirrors OggHandler so that its easier to share efforts in this area:
2678 * http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/OggHandler/OggPlayer.js
2679 */
2680 var embedTypes = {
2681 // List of players
2682 players: null,
2683 detect_done:false,
2684 init: function(){
2685 //detect supported types
2686 this.detect();
2687 this.detect_done=true;
2688 },
2689 clientSupports: { 'thumbnail' : true },
2690 supportedMimeType: function(mimetype) {
2691 for (var i = navigator.plugins.length; i-- > 0; ) {
2692 var plugin = navigator.plugins[i];
2693 if (typeof plugin[mimetype] != "undefined")
2694 return true;
2695 }
2696 return false;
2697 },
2698
2699 detect: function() {
2700 js_log("running detect");
2701 this.players = new mediaPlayers();
2702 //every browser supports html rendering:
2703 this.players.addPlayer( htmlPlayer );
2704 // In Mozilla, navigator.javaEnabled() only tells us about preferences, we need to
2705 // search navigator.mimeTypes to see if it's installed
2706 var javaEnabled = navigator.javaEnabled();
2707 // In Opera, navigator.javaEnabled() is all there is
2708 var invisibleJava = $j.browser.opera;
2709 // Some browsers filter out duplicate mime types, hiding some plugins
2710 var uniqueMimesOnly = $j.browser.opera || $j.browser.safari;
2711 // Opera will switch off javaEnabled in preferences if java can't be found.
2712 // And it doesn't register an application/x-java-applet mime type like Mozilla does.
2713 if ( invisibleJava && javaEnabled )
2714 this.players.addPlayer( cortadoPlayer );
2715
2716 // ActiveX plugins
2717 if($j.browser.msie){
2718 // check for flash
2719 if ( this.testActiveX( 'ShockwaveFlash.ShockwaveFlash')){
2720 //try to get the flash version for omtk include:
2721 try {
2722 a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
2723 d = a.GetVariable("$version"); // Will crash fp6.0.21/23/29
2724 if (d) {
2725 d = d.split(" ")[1].split(",");
2726 //we need flash version 10 or greater:
2727 if(parseInt( d[0]) >=10){
2728 this.players.addPlayer( omtkPlayer );
2729 }
2730
2731 }
2732 }catch(e) {}
2733
2734 //flowplayer has pretty good compatiablity
2735 // (but if we wanted to be fancy we would check for version of flash and update the mp4/h.264 support
2736 this.players.addPlayer( flowPlayer );
2737 }
2738 // VLC
2739 if ( this.testActiveX( 'VideoLAN.VLCPlugin.2' ) )
2740 this.players.addPlayer(vlcActiveXPlayer);
2741 // Java
2742 if ( javaEnabled && this.testActiveX( 'JavaWebStart.isInstalled' ) )
2743 this.players.addPlayer(cortadoPlayer);
2744 // quicktime
2745 //if ( this.testActiveX( 'QuickTimeCheckObject.QuickTimeCheck.1' ) )
2746 // this.players.addPlayer(quicktimeActiveXPlayer);
2747 }
2748 // <video> element
2749 if ( typeof HTMLVideoElement == 'object' // Firefox, Safari
2750 || typeof HTMLVideoElement == 'function' ) // Opera
2751 {
2752 //do another test for safari:
2753 if( $j.browser.safari ){
2754 try{
2755 var dummyvid = document.createElement("video");
2756 if (dummyvid.canPlayType && dummyvid.canPlayType("video/ogg;codecs=\"theora,vorbis\"") == "probably")
2757 {
2758 this.players.addPlayer( videoElementPlayer );
2759 } else if(this.supportedMimeType( 'video/ogg' )) {
2760 /* older versions of safari do not support canPlayType,
2761 but xiph qt registers mimetype via quicktime plugin */
2762 this.players.addPlayer( videoElementPlayer );
2763 } else {
2764 //@@todo add some user nagging to install the xiph qt
2765 }
2766 }catch(e){
2767 js_log('could not run canPlayType in safari');
2768 }
2769 }else{
2770 this.players.addPlayer( videoElementPlayer );
2771 }
2772 }
2773
2774 // Mozilla plugins
2775 if( navigator.mimeTypes && navigator.mimeTypes.length > 0) {
2776 for ( var i = 0; i < navigator.mimeTypes.length; i++ ) {
2777 var type = navigator.mimeTypes[i].type;
2778 var semicolonPos = type.indexOf( ';' );
2779 if ( semicolonPos > -1 ) {
2780 type = type.substr( 0, semicolonPos );
2781 }
2782 //js_log('on type: '+type);
2783 var pluginName = navigator.mimeTypes[i].enabledPlugin ? navigator.mimeTypes[i].enabledPlugin.name : '';
2784 if ( !pluginName ) {
2785 // In case it is null or undefined
2786 pluginName = '';
2787 }
2788 if ( pluginName.toLowerCase() == 'vlc multimedia plugin' || pluginName.toLowerCase() == 'vlc multimedia plug-in' ) {
2789 this.players.addPlayer(vlcMozillaPlayer, type);
2790 continue;
2791 }
2792
2793 if ( javaEnabled && type == 'application/x-java-applet' ) {
2794 this.players.addPlayer(cortadoPlayer);
2795 continue;
2796 }
2797
2798 if ( type == 'application/ogg' ) {
2799 if ( pluginName.toLowerCase() == 'vlc multimedia plugin' ){
2800 this.players.addPlayer(vlcMozillaPlayer, type);
2801 //else if ( pluginName.indexOf( 'QuickTime' ) > -1 )
2802 // this.players.addPlayer(quicktimeMozillaPlayer);
2803 }else{
2804 this.players.addPlayer(oggPluginPlayer);
2805 }
2806 continue;
2807 } else if ( uniqueMimesOnly ) {
2808 if ( type == 'application/x-vlc-player' ) {
2809 this.players.addPlayer(vlcMozillaPlayer, type);
2810 continue;
2811 } else if ( type == 'video/quicktime' ) {
2812 //this.players.addPlayer(quicktimeMozillaPlayer);
2813 continue;
2814 }
2815 }
2816
2817 /*if ( type == 'video/quicktime' ) {
2818 this.players.addPlayer(vlcMozillaPlayer, type);
2819 continue;
2820 }*/
2821 if(type=='application/x-shockwave-flash'){
2822 this.players.addPlayer( flowPlayer );
2823
2824 //check version to add omtk:
2825 var flashDescription = navigator.plugins["Shockwave Flash"].description;
2826 var descArray = flashDescription.split(" ");
2827 var tempArrayMajor = descArray[2].split(".");
2828 var versionMajor = tempArrayMajor[0];
2829 //js_log("version of flash: " + versionMajor);
2830 if(versionMajor >= 10){
2831 this.players.addPlayer( omtkPlayer );
2832 }
2833 continue;
2834 }
2835 }
2836 }
2837 //@@The xiph quicktime component does not work well with annodex streams (temporarly disable)
2838 //this.clientSupports['quicktime-mozilla'] = false;
2839 //this.clientSupports['quicktime-activex'] = false;
2840 //js_log(this.clientSupports);
2841 },
2842 testActiveX : function ( name ) {
2843 var hasObj = true;
2844 try {
2845 // No IE, not a class called "name", it's a variable
2846 var obj = new ActiveXObject( '' + name );
2847 } catch ( e ) {
2848 hasObj = false;
2849 }
2850 return hasObj;
2851 }
2852 };