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