* (bug 20037) fixes script-loader loading of color picker (grouped scripts ~usually...
[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
232 // now that "embed_video" is stable, do more initialization (if we are ready)
233 if($j('#'+embed_video.id).get(0).loading_external_data==false &&
234 $j('#'+embed_video.id).get(0).init_with_sources_loadedDone==false){
235 //load and set ready state since source are available:
236 $j('#'+embed_video.id).get(0).init_with_sources_loaded();
237 }
238
239 js_log('done with child: ' + embed_video.id + ' len:' + global_player_list.length);
240 return true;
241 },
242 //this should not be needed.
243 checkClipsReady : function(){
244 //js_log('checkClipsReady');
245 var is_ready=true;
246 for(var i=0; i < global_player_list.length; i++){
247 if( $j('#'+global_player_list[i]).length !=0){
248 var cur_vid = $j('#'+global_player_list[i]).get(0);
249 is_ready = ( cur_vid.ready_to_play ) ? is_ready : false;
250 if( !is_ready && cur_vid.load_error ){
251 is_ready=true;
252 $j(cur_vid).html( cur_vid.load_error );
253 }
254 }
255 }
256 if( is_ready ){
257 mvEmbed.allClipsReady = true;
258 // run queued functions
259 //js_log('run queded functions:' + mvEmbed.flist[0]);
260 mvEmbed.runFlist();
261 }else{
262 setTimeout( 'mvEmbed.checkClipsReady()', 25 );
263 }
264 },
265 runFlist:function(){
266 while (this.flist.length){
267 this.flist.shift()();
268 }
269 }
270 }
271
272 /*
273 * controlsBuilder:
274 *
275 */
276 var ctrlBuilder = {
277 height:29,
278 supports:{
279 'options':true,
280 'borders':true
281 },
282 getControls:function( embedObj ){
283 js_log('f:controlsBuilder:: opt:' + this.options);
284 this.id = (embedObj.pc)?embedObj.pc.pp.id:embedObj.id;
285 this.available_width = embedObj.playerPixelWidth();
286 //make pointer to the embedObj
287 this.embedObj =embedObj;
288 var _this = this;
289 for(var i in embedObj.supports){
290 _this.supports[i] = embedObj.supports[i];
291 };
292
293 //special case vars:
294 if( ( embedObj.roe ||
295 (embedObj.media_element.timedTextSources &&
296 embedObj.media_element.timedTextSources() )
297 ) && embedObj.show_meta_link )
298 this.supports['closed_captions']=true;
299
300
301 //append options to body (if not already there)
302 if($j('#mv_vid_options_'+ctrlBuilder.id).length==0)
303 $j('body').append( this.components['mv_embedded_options'].o() );
304
305 var o='';
306 for( var i in this.components ){
307 if( this.supports[i] ){
308 if( this.available_width > this.components[i].w ){
309 //special case with playhead don't add unless we have 60px
310 if( i == 'play_head' && ctrlBuilder.available_width < 60 )
311 continue;
312 o+=this.components[i].o();
313 this.available_width -= this.components[i].w;
314 }else{
315 js_log('not enough space for control component:' + i);
316 }
317 }
318 }
319 return o;
320 },
321 /*
322 * addControlHooks
323 * to be run once controls are attached to the dom
324 */
325 addControlHooks:function(embedObj){
326 //add in drag/seek hooks:
327 if(!embedObj.base_seeker_slider_offset && $j('#mv_seeker_slider_'+embedObj.id).get(0))
328 embedObj.base_seeker_slider_offset = $j('#mv_seeker_slider_'+embedObj.id).get(0).offsetLeft;
329
330 //js_log('looking for: #mv_seeker_slider_'+embedObj.id + "\n " +
331 // 'start sec: '+embedObj.start_time_sec + ' base offset: '+embedObj.base_seeker_slider_offset);
332
333 //add play hook:
334 $j('#mv_play_pause_button_' + embedObj.id).unbind().btnBind().click(function(){
335 $j('#' + embedObj.id).get(0).play();
336 })
337
338 //big_play_link_ play binding:
339 $j('#big_play_link_' + embedObj.id).unbind().click(function(){
340 $j('#' + embedObj.id).get(0).play();
341 });
342
343 //add recomend firefox if non-native playback:
344 if( embedObj.doNativeWarningCheck() ){
345 $j('#dc_'+ embedObj.id).hover(
346 function(){
347 if($j('#gnp_' + embedObj.id).length==0){
348 $j(this).append('<div id="gnp_' + embedObj.id + '" class="ui-state-highlight ui-corner-all" ' +
349 'style="position:absolute;display:none;background:#FFF;top:10px;left:10px;right:10px;height:60px;">' +
350 gM('mv_for_best_experience') +
351 '<br><input id="ffwarn_'+embedObj.id+'" type=\"checkbox\">' +
352 gM('mv_do_not_warn_again') +
353 '</div>');
354 $j('#ffwarn_'+embedObj.id).click(function(){
355 if( $j(this).is(':checked') ){
356 //set up a cookie for 7 days:
357 $j.cookie('dismissNativeWarn', true, { expires: 5 });
358 //set the current instance
359 _global['dismissNativeWarn'] = true;
360 $j('#gnp_' + embedObj.id).fadeOut('slow');
361 }else{
362 _global['adismissNativeWarn'] = false;
363 $j.cookie('dismissNativeWarn', false);
364 }
365
366 });
367 }
368 if( ($j.cookie('dismissNativeWarn') !== true) &&
369 _global['dismissNativeWarn'] === false ){
370 $j('#gnp_' + embedObj.id).fadeIn('slow');
371 }
372 },
373 function(){
374 $j('#gnp_' + embedObj.id).fadeOut('slow');
375 }
376 );
377 }
378
379 if( $j.browser.msie && $j.browser.version <= 6){
380 $j('#big_play_link_' + embedObj.id).pngFix();
381 }
382
383
384 //captions binding:
385 $j('#timed_text_' + embedObj.id).unbind().btnBind().click(function(){
386 $j('#' + embedObj.id).get(0).showTextInterface();
387 });
388
389 //options binding:
390 $j('#options_button_' + embedObj.id).unbind().btnBind().click(function(){
391 $j('#' +embedObj.id).get(0).doOptionsHTML();
392 });
393
394 //fullscreen binding:
395 $j('#fullscreen_'+embedObj.id).unbind().btnBind().click(function(){
396 $j('#' +embedObj.id).get(0).fullscreen();
397 });
398
399 js_log(" should add slider binding: " + $j('#mv_play_head_'+embedObj.id).length) ;
400 $j('#mv_play_head_'+embedObj.id).slider({
401 range: "min",
402 value: 0,
403 min: 0,
404 max: 1000,
405 start: function(event, ui){
406 var id = (embedObj.pc!=null)?embedObj.pc.pp.id:embedObj.id;
407 embedObj.userSlide=true;
408 $j('#big_play_link_'+id).fadeOut('fast');
409 //if playlist always start at 0
410 embedObj.start_time_sec = (embedObj.instanceOf == 'mvPlayList')?0:
411 npt2seconds(embedObj.getTimeReq().split('/')[0]);
412 },
413 slide: function(event, ui) {
414 var perc = ui.value/1000;
415 embedObj.jump_time = seconds2npt( parseFloat( parseFloat(embedObj.getDuration()) * perc ) + embedObj.start_time_sec);
416 //js_log('perc:' + perc + ' * ' + embedObj.getDuration() + ' jt:'+ this.jump_time);
417 embedObj.setStatus( gM('seek_to')+' '+embedObj.jump_time );
418 //update the thumbnail / frame
419 if(embedObj.isPlaying==false){
420 embedObj.updateThumbPerc( perc );
421 }
422 },
423 change:function(event, ui){
424 //only run the onChange event if done by a user slide:
425 if(embedObj.userSlide){
426 embedObj.userSlide=false;
427 embedObj.seeking=true;
428 //stop the monitor timer (if we can)
429 if(embedObj.stopMonitor)
430 embedObj.stopMonitor();
431
432 var perc = ui.value/1000;
433 //set seek time (in case we have to do a url seek)
434 embedObj.seek_time_sec = npt2seconds( embedObj.jump_time, true );
435 js_log('do jump to: '+embedObj.jump_time + ' perc:' +perc + ' sts:' + embedObj.seek_time_sec);
436 embedObj.doSeek(perc);
437 }
438 }
439 });
440 //up the z-index of the default status indicator:
441 $j('#mv_play_head_'+embedObj.id + ' .ui-slider-handle').css('z-index', 4);
442 $j('#mv_play_head_'+embedObj.id + ' .ui-slider-range').addClass('ui-corner-all').css('z-index', 2);
443 //extended class list for jQuery ui themeing (we can probably refactor this with custom buffering highliter)
444 $j('#mv_play_head_'+embedObj.id).append( ctrlBuilder.getMvBufferHtml() );
445
446 //videoOptions:
447 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_selection').click(function(){
448 embedObj.selectPlaybackMethod();
449 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
450 return false;
451 });
452 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_download').click(function(){
453 embedObj.showVideoDownload();
454 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
455 return false;
456 })
457 $j('#mv_vid_options_'+ctrlBuilder.id+' .vo_showcode').click(function(){
458 embedObj.showEmbedCode();
459 $j('#mv_vid_options_'+ctrlBuilder.id).hide();
460 return false;
461 });
462
463 //volume binding:
464 var hoverOverDelay=false;
465 $j('#volume_control_'+embedObj.id).unbind().btnBind().click(function(){
466 $j('#' +embedObj.id).get(0).toggleMute();
467 }).hover(
468 function(){
469 $j('#vol_container_' + embedObj.id).addClass('vol_container_top');
470 //set to "below" if playing and embedType != native
471 if(embedObj && embedObj.isPlaying && embedObj.isPlaying() && !embedObj.supports['overlays']){
472 $j('#vol_container_' + embedObj.id).removeClass('vol_container_top').addClass('vol_container_below');
473 }
474
475 $j('#vol_container_' + embedObj.id).fadeIn('fast');
476 hoverOverDelay = true;
477 },
478 function(){
479 hoverOverDelay= false;
480 setTimeout(function doHideVolume(){
481 if(!hoverOverDelay){
482 $j('#vol_container_' + embedObj.id).fadeOut('fast');
483 }
484 }, 500);
485 }
486 );
487 //Volumen Slider
488 $j('#volume_bar_'+embedObj.id).slider({
489 orientation: "vertical",
490 range: "min",
491 value: 80,
492 min: 0,
493 max: 100,
494 slide: function(event, ui) {
495 var perc = ui.value/100;
496 //js_log('update volume:' + perc);
497 embedObj.updateVolumen(perc);
498 },
499 change:function(event, ui){
500 var perc = ui.value/100;
501 if (perc==0) {
502 $j('#volume_control_'+embedObj.id + ' span').removeClass('ui-icon-volume-on').addClass('ui-icon-volume-off');
503 }else{
504 $j('#volume_control_'+embedObj.id + ' span').removeClass('ui-icon-volume-off').addClass('ui-icon-volume-on');
505 }
506 //only run the onChange event if done by a user slide:
507 if(embedObj.userSlide){
508 embedObj.userSlide=false;
509 embedObj.seeking=true;
510 var perc = ui.value/100;
511 embedObj.updateVolumen(perc);
512 }
513 }
514 });
515
516 },
517 getMvBufferHtml:function(){
518 return '<div class="ui-slider-range ui-slider-range-min ui-widget-header ' +
519 'ui-state-highlight ui-corner-all '+
520 'mv_buffer" style="width:0px;height:100%;z-index:1;top:0px" />';
521 },
522 components:{
523 'borders':{
524 'w':8,
525 'o':function(){
526 return '';
527 }
528 },
529 'mv_embedded_options':{
530 'w':0,
531 'o':function(){
532 var o= '<div id="mv_vid_options_'+ctrlBuilder.id+'" class="videoOptions">'+
533 '<div class="videoOptionsTop"></div>'+
534 '<div class="videoOptionsBox">'+
535 '<div class="block">'+
536 '<h6>Video Options</h6>'+
537 '</div>'+
538 '<div class="block">'+
539 '<p class="short_match vo_selection"><a href="#"><span>Stream Selection</span></a></p>'+
540 '<p class="short_match vo_download"><a href="#"><span>Download</span></a></p>'+
541 '<p class="short_match vo_showcode"><a href="#"><span>Share or Embed</span></a></p>';
542
543 //link to the stream page if we are not already there:
544 if( ctrlBuilder.embedObj.roe && typeof mv_stream_interface == 'undefined' )
545 o+='<p class="short_match"><a href="javascript:$j(\'#'+ctrlBuilder.id+'\').get(0).doLinkBack()"><span><strong>Source Page</strong></span></a></p>';
546
547 o+='</div>'+
548 '</div><!--videoOptionsInner-->' +
549 '<div class="videoOptionsBot"></div>' +
550 '</div><!--videoOptions-->';
551 return o;
552 }
553 },
554 'fullscreen':{
555 'w':20,
556 'o':function(){
557 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>'
558 }
559 },
560 'options':{
561 'w':26,
562 'o':function(){
563 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>';
564 }
565 },
566 'pause':{
567 'w':24,
568 'o':function(){
569 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>';
570 }
571 },
572 'closed_captions':{
573 'w':23,
574 'o':function(){
575 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>'
576 }
577 },
578 'volume_control':{
579 'w':23,
580 'o':function(){
581 return '<div title="' + gM('volume_control') + '" id="volume_control_'+ctrlBuilder.id+'" class="ui-state-default ui-corner-all ui-icon_link rButton">' +
582 '<span class="ui-icon ui-icon-volume-on"></span>' +
583 '<div style="position:absolute;display:none;" id="vol_container_'+ctrlBuilder.id+'" class="vol_container ui-corner-all">' +
584 '<div class="volume_bar" id="volume_bar_' + ctrlBuilder.id + '"></div>' +
585 '</div>'+
586 '</div>';
587 }
588 },
589 'time_display':{
590 'w':90,
591 'o':function(){
592 return '<div id="mv_time_'+ctrlBuilder.id+'" class="ui-widget time">' + ctrlBuilder.embedObj.getTimeReq() + '</div>';
593 }
594 },
595 'play_head':{
596 'w':0, //special case (takes up remaining space)
597 'o':function(){
598 return '<div class="play_head" id="mv_play_head_' + ctrlBuilder.id + '" style="width: ' + ( ctrlBuilder.available_width - 30 ) + 'px;"></div>';
599 }
600 }
601 }
602 }
603
604 /**
605 * mediaSource class represents a source for a media element.
606 * @param {String} type MIME type of the source.
607 * @param {String} uri URI of the source.
608 * @constructor
609 */
610 function mediaSource(element)
611 {
612 this.init(element);
613 }
614
615
616 mediaSource.prototype =
617 {
618 /** MIME type of the source. */
619 mime_type:null,
620 /** URI of the source. */
621 uri:null,
622 /** Title of the source. */
623 title:null,
624 /** True if the source has been marked as the default. */
625 marked_default:false,
626 /** True if the source supports url specification of offset and duration */
627 URLTimeEncoding:false,
628 /** Start offset of the requested segment */
629 start_offset:null,
630 /** Duration of the requested segment (0 if not known) */
631 duration:0,
632 is_playable:null,
633 upddate_interval:null,
634
635 id:null,
636 start_ntp:null,
637 end_ntp:null,
638
639 init : function(element)
640 {
641 //js_log('adding mediaSource: ' + element);
642 this.src = $j(element).attr('src');
643 this.marked_default = false;
644 if ( element.tagName.toLowerCase() == 'video')
645 this.marked_default = true;
646
647 //set default URLTimeEncoding if we have a time url:
648 //not ideal way to discover if content is on an oggz_chop server.
649 //should check some other way.
650 var pUrl = parseUri ( this.src );
651 if(typeof pUrl['queryKey']['t'] != 'undefined'){
652 this['URLTimeEncoding']=true;
653 }
654 for(var i=0; i < mv_default_source_attr.length; i++){ //array loop:
655 var attr = mv_default_source_attr[ i ];
656 if( $j(element).attr( attr ) ) {
657 this[ attr ] = $j(element).attr( attr );
658 }
659 }
660 //update duration from hit if present:
661 if(this.durationHint)
662 this.duration = this.durationHint;
663
664
665 if ( $j(element).attr('type'))
666 this.mime_type = $j(element).attr('type');
667 else if ($j(element).attr('content-type'))
668 this.mime_type = $j(element).attr('content-type');
669 else
670 this.mime_type = this.detectType(this.src);
671
672 //set the title if unset:
673 if( !this.title )
674 this.title = this.mime_type;
675
676 this.parseURLDuration();
677 },
678 updateSource:function(element){
679 //for now just update the title:
680 if ($j(element).attr("title"))
681 this.title = $j(element).attr("title");
682 },
683 /** updates the src time and start & end
684 * @param {String} start_time in NTP format
685 * @param {String} end_time in NTP format
686 */
687 updateSrcTime:function (start_ntp, end_ntp){
688 //js_log("f:updateSrcTime: "+ start_ntp+'/'+ end_ntp + ' from org: ' + this.start_ntp+ '/'+this.end_ntp);
689 //js_log("pre uri:" + this.src);
690 //if we have time we can use:
691 if( this.URLTimeEncoding ){
692 //make sure its a valid start time / end time (else set default)
693 if( !npt2seconds(start_ntp) )
694 start_ntp = this.start_ntp;
695
696 if( !npt2seconds(end_ntp) )
697 end_ntp = this.end_ntp;
698
699 this.src = getURLParamReplace(this.src, { 't': start_ntp +'/'+ end_ntp } );
700
701 //update the duration
702 this.parseURLDuration();
703 }
704 },
705 setDuration:function (duration)
706 {
707 this.duration = duration;
708 if(!this.end_ntp){
709 this.end_ntp = seconds2npt( this.start_offset + duration);
710 }
711 },
712 /** MIME type accessor function.
713 @return the MIME type of the source.
714 @type String
715 */
716 getMIMEType : function()
717 {
718 return this.mime_type;
719 },
720 /** URI accessor function.
721 * @param int seek_time_sec (used to adjust the URI for url based seeks)
722 @return the URI of the source.
723 @type String
724 */
725 getURI : function( seek_time_sec )
726 {
727 if( !seek_time_sec || !this.URLTimeEncoding ){
728 return this.src;
729 }
730 if(!this.end_ntp){
731 var endvar = '';
732 }else{
733 var endvar = '/'+ this.end_ntp;
734 }
735 return getURLParamReplace(this.src, { 't': seconds2npt( seek_time_sec )+endvar } ); ;
736 },
737 /** Title accessor function.
738 @return the title of the source.
739 @type String
740 */
741 getTitle : function()
742 {
743 return this.title;
744 },
745 /** Index accessor function.
746 @return the source's index within the enclosing mediaElement container.
747 @type Integer
748 */
749 getIndex : function()
750 {
751 return this.index;
752 },
753 /*
754 * function getDuration in milliseconds
755 * special case derive duration from request url
756 * supports media_url?t=ntp_start/ntp_end url request format
757 */
758 parseURLDuration : function(){
759 //check if we have a URLTimeEncoding:
760 if( this.URLTimeEncoding ){
761 var annoURL = parseUri( this.src );
762 if( annoURL.queryKey['t'] ){
763 var times = annoURL.queryKey['t'].split('/');
764 this.start_ntp = times[0];
765 this.end_ntp = times[1];
766 this.start_offset = npt2seconds( this.start_ntp );
767 this.duration = npt2seconds( this.end_ntp ) - this.start_offset;
768 }else{
769 //look for this info as attributes
770 if(this.startOffset){
771 this.start_offset = this.startOffset;
772 this.start_ntp = seconds2npt( this.startOffset);
773 }
774 if(this.duration){
775 this.end_ntp = seconds2npt( parseInt(this.duration) + parseInt(this.start_offset) );
776 }
777 }
778 }
779 //else nothing to parse just keep whatever info we already have
780
781 //js_log('f:parseURLDuration() for:' + this.src + ' d:' + this.duration);
782 },
783 /** Attempts to detect the type of a media file based on the URI.
784 @param {String} uri URI of the media file.
785 @returns The guessed MIME type of the file.
786 @type String
787 */
788 detectType:function(uri)
789 {
790 //@@todo if media is on the same server as the javascript or we have mv_proxy configured
791 //we can issue a HEAD request and read the mime type of the media...
792 // (this will detect media mime type independently of the url name)
793 //http://www.jibbering.com/2002/4/httprequest.html (this should be done by extending jquery's ajax objects)
794 var end_inx = (uri.indexOf('?')!=-1)? uri.indexOf('?') : uri.length;
795 var no_param_uri = uri.substr(0, end_inx);
796 switch( no_param_uri.substr(no_param_uri.lastIndexOf('.'),4).toLowerCase() ){
797 case '.flv':return 'video/x-flv';break;
798 case '.ogg': case '.ogv': return 'video/ogg';break;
799 case '.oga': return 'audio/ogg'; break;
800 case '.anx':return 'video/ogg';break;
801 }
802 }
803 };
804
805 /** A media element corresponding to a <video> element.
806 It is implemented as a collection of mediaSource objects. The media sources
807 will be initialized from the <video> element, its child <source> elements,
808 and/or the ROE file referenced by the <video> element.
809 @param {element} video_element <video> element used for initialization.
810 @constructor
811 */
812 function mediaElement(video_element)
813 {
814 this.init(video_element);
815 };
816
817 mediaElement.prototype =
818 {
819 /** The array of mediaSource elements. */
820 sources:null,
821 addedROEData:false,
822 /** Selected mediaSource element. */
823 selected_source:null,
824 thumbnail:null,
825 linkback:null,
826
827 /** @private */
828 init:function( video_element )
829 {
830 var _this = this;
831 js_log('Initializing mediaElement...' );
832 this.sources = new Array();
833 this.thumbnail = mv_default_thumb_url;
834 // Process the source element:
835 if($j(video_element).attr("src"))
836 this.tryAddSource(video_element);
837
838 if($j(video_element).attr('thumbnail'))
839 this.thumbnail = $j(video_element).attr('thumbnail');
840
841 if($j(video_element).attr('poster'))
842 this.thumbnail = $j(video_element).attr('poster');
843
844 // Process all inner <source> elements
845 //js_log("inner source count: " + video_element.getElementsByTagName('source').length );
846
847 $j(video_element).find('source,text').each(function(inx, inner_source){
848 _this.tryAddSource( inner_source );
849 });
850 },
851 /** Updates the time request for all sources that have a standard time request argument (ie &t=start_time/end_time)
852 */
853 updateSourceTimes:function(start_ntp, end_ntp){
854 var _this = this;
855 $j.each(this.sources, function(inx, mediaSource){
856 mediaSource.updateSrcTime(start_ntp, end_ntp);
857 });
858 },
859 /*timed Text check*/
860 timedTextSources:function(){
861 for(var i=0; i < this.sources.length; i++){
862 if( this.sources[i].mime_type == 'text/cmml' ||
863 this.sources[i].mime_type == 'text/x-srt')
864 return true;
865 };
866 return false;
867 },
868 /** Returns the array of mediaSources of this element.
869 \returns {Array} Array of mediaSource elements.
870 */
871 getSources:function( mime_filter )
872 {
873 if(!mime_filter)
874 return this.sources;
875 //apply mime filter:
876 var source_set = new Array();
877 for(var i=0; i < this.sources.length ; i++){
878 if( this.sources[i].mime_type.indexOf( mime_filter ) != -1 )
879 source_set.push( this.sources[i] );
880 }
881 return source_set;
882 },
883 getSourceById:function( source_id ){
884 for(var i=0; i < this.sources.length ; i++){
885 if( this.sources[i].id == source_id)
886 return this.sources[i];
887 }
888 return null;
889 },
890 /** Selects a particular source for playback.
891 */
892 selectSource:function(index)
893 {
894 js_log('f:selectSource:'+index);
895 var playable_sources = this.getPlayableSources();
896 for(var i=0; i < playable_sources.length; i++){
897 if( i==index ){
898 this.selected_source = playable_sources[i];
899 //update the user selected format:
900 embedTypes.players.userSelectFormat( playable_sources[i].mime_type );
901 break;
902 }
903 }
904 },
905 /** selects the default source via cookie preference, default marked, or by id order
906 * */
907 autoSelectSource:function(){
908 js_log('f:autoSelectSource:');
909 //@@todo read user preference for source
910 // Select the default source
911 var playable_sources = this.getPlayableSources();
912 var flash_flag=ogg_flag=false;
913 //debugger;
914 for(var source=0; source < playable_sources.length; source++){
915 var mime_type =playable_sources[source].mime_type;
916 if( playable_sources[source].marked_default ){
917 js_log('set via marked default: ' + playable_sources[source].marked_default);
918 this.selected_source = playable_sources[source];
919 return true;
920 }
921 //set via user-preference
922 if(embedTypes.players.preference['format_prefrence'] == mime_type){
923 js_log('set via preference: '+playable_sources[source].mime_type);
924 this.selected_source = playable_sources[source];
925 return true;
926 }
927 }
928 //set Ogg via player support
929 for(var source=0; source < playable_sources.length; source++){
930 js_log('f:autoSelectSource:' + playable_sources[source].mime_type);
931 var mime_type =playable_sources[source].mime_type;
932 //set source via player
933 if(mime_type=='video/ogg' || mime_type=='ogg/video' || mime_type=='video/annodex' || mime_type=='application/ogg'){
934 for(var i=0; i < embedTypes.players.players.length; i++){ //for in loop on object oky
935 var player = embedTypes.players.players[i];
936 if(player.library=='vlc' || player.library=='native'){
937 js_log('set via ogg via order');
938 this.selected_source = playable_sources[source];
939 return true;
940 }
941 }
942 }
943 }
944 //set basic flash
945 for(var source=0; source < playable_sources.length; source++){
946 var mime_type =playable_sources[source].mime_type;
947 if( mime_type=='video/x-flv' ){
948 js_log('set via by player preference normal flash')
949 this.selected_source = playable_sources[source];
950 return true;
951 }
952 }
953 //set h264 flash
954 for(var source=0; source < playable_sources.length; source++){
955 var mime_type =playable_sources[source].mime_type;
956 if( mime_type=='video/h264' ){
957 js_log('set via playable_sources preference h264 flash')
958 this.selected_source = playable_sources[source];
959 return true;
960 }
961 }
962 //select first source
963 if (!this.selected_source)
964 {
965 js_log('set via first source:' + playable_sources[0]);
966 this.selected_source = playable_sources[0];
967 return true;
968 }
969 },
970 /** Returns the thumbnail URL for the media element.
971 \returns {String} thumbnail URL
972 */
973 getThumbnailURL:function()
974 {
975 return this.thumbnail;
976 },
977 /** Checks whether there is a stream of a specified MIME type.
978 @param {String} mime_type MIME type to check.
979 @type {BooleanPrimitive}.
980 */
981 hasStreamOfMIMEType:function(mime_type)
982 {
983 for(source in this.sources)
984 {
985 if(this.sources[source].getMIMEType() == mime_type)
986 return true;
987 }
988 return false;
989 },
990 isPlayableType:function(mime_type)
991 {
992 if( embedTypes.players.defaultPlayer( mime_type ) ){
993 return true;
994 }else{
995 return false;
996 }
997 //if(this.selected_player){
998 //return mime_type=='video/ogg' || mime_type=='ogg/video' || mime_type=='video/annodex' || mime_type=='video/x-flv';
999 },
1000 /** Adds a single mediaSource using the provided element if
1001 the element has a 'src' attribute.
1002 @param element {element} <video>, <source> or <mediaSource> element.
1003 */
1004 tryAddSource:function(element)
1005 {
1006 js_log('f:tryAddSource:'+ $j(element).attr("src"));
1007 if (! $j(element).attr("src")){
1008 //js_log("element has no src");
1009 return false;
1010 }
1011 var new_src = $j(element).attr('src');
1012 //make sure an existing element with the same src does not already exist:
1013 for( var i=0; i < this.sources.length; i++ ){
1014 if(this.sources[i].src == new_src){
1015 //js_log('checking existing: '+this.sources[i].getURI() + ' != '+ new_src);
1016 //can't add it all but try to update any additional attr:
1017 this.sources[i].updateSource(element);
1018 return false;
1019 }
1020 }
1021 var source = new mediaSource( element );
1022 this.sources.push(source);
1023 //alert('pushed source to stack'+ source + 'sl:'+this.sources.length);
1024 },
1025 getPlayableSources: function(){
1026 var playable_sources= new Array();
1027 for(var i=0; i < this.sources.length; i++){
1028 if( this.isPlayableType( this.sources[i].mime_type ) ){
1029 playable_sources.push( this.sources[i] );
1030 }else{
1031 js_log("type "+ this.sources[i].mime_type + 'is not playable');
1032 }
1033 };
1034 return playable_sources;
1035 },
1036 /* Imports media sources from ROE data.
1037 * @param roe_data ROE data.
1038 */
1039 addROE:function(roe_data){
1040 js_log('f:addROE');
1041 this.addedROEData=true;
1042 var _this = this;
1043 if( typeof roe_data == 'string' )
1044 {
1045 var parser=new DOMParser();
1046 js_log('ROE data:' + roe_data);
1047 roe_data=parser.parseFromString(roe_data,"text/xml");
1048 }
1049 if( roe_data ){
1050 $j.each(roe_data.getElementsByTagName('mediaSource'), function(inx, source){
1051 _this.tryAddSource(source);
1052 });
1053 //set the thumbnail:
1054 $j.each(roe_data.getElementsByTagName('img'), function(inx, n){
1055 if($j(n).attr("id")=="stream_thumb"){
1056 js_log('roe:set thumb to '+$j(n).attr("src"));
1057 _this['thumbnail'] =$j(n).attr("src");
1058 }
1059 });
1060 //set the linkback:
1061 $j.each(roe_data.getElementsByTagName('link'), function(inx, n){
1062 if($j(n).attr('id')=='html_linkback'){
1063 js_log('roe:set linkback to '+$j(n).attr("href"));
1064 _this['linkback'] = $j(n).attr('href');
1065 }
1066 });
1067 }else{
1068 js_log('ROE data empty.');
1069 }
1070 }
1071 };
1072
1073
1074 /** base embedVideo object
1075 @param element <video> tag used for initialization.
1076 @constructor
1077 */
1078 var embedVideo = function(element) {
1079 return this.init(element);
1080 };
1081
1082 embedVideo.prototype = {
1083 /** The mediaElement object containing all mediaSource objects */
1084 media_element:null,
1085 preview_mode:false,
1086 ready_to_play:false, //should use html5 ready state
1087 load_error:false, //used to set error in case of error
1088 loading_external_data:false,
1089 thumbnail_updating:false,
1090 thumbnail_disp:true,
1091 init_with_sources_loadedDone:false,
1092 inDOM:false,
1093 //for onClip done stuff:
1094 anno_data_cache:null,
1095 seek_time_sec:0,
1096 base_seeker_slider_offset:null,
1097 onClipDone_disp:false,
1098 supports:{},
1099 //for seek thumb updates:
1100 cur_thumb_seek_time:0,
1101 thumb_seek_interval:null,
1102
1103 seeking:false,
1104 //set the buffered percent:
1105 bufferedPercent:0,
1106 //utility functions for property values:
1107 hx : function ( s ) {
1108 if ( typeof s != 'String' ) {
1109 s = s.toString();
1110 }
1111 return s.replace( /&/g, '&amp;' )
1112 . replace( /</g, '&lt;' )
1113 . replace( />/g, '&gt;' );
1114 },
1115 hq : function ( s ) {
1116 return '"' + this.hx( s ) + '"';
1117 },
1118 playerPixelWidth : function()
1119 {
1120 var player = $j('#mv_embedded_player_'+this.id).get(0);
1121 if(typeof player!='undefined' && player['offsetWidth'])
1122 return player.offsetWidth;
1123 else
1124 return parseInt(this.width);
1125 },
1126 playerPixelHeight : function()
1127 {
1128 var player = $j('#mv_embedded_player_'+this.id).get(0);
1129 if(typeof player!='undefined' && player['offsetHeight'])
1130 return player.offsetHeight;
1131 else
1132 return parseInt(this.height);
1133 },
1134 init: function(element){
1135 //this.element_pointer = element;
1136
1137 //inherit all the default video_attributes
1138 for(var attr in default_video_attributes){ //for in loop oky on user object
1139 if(element.getAttribute(attr)){
1140 this[attr]=element.getAttribute(attr);
1141 //js_log('attr:' + attr + ' val: ' + element.getAttribute(attr) +'(set by elm)');
1142 }else{
1143 this[attr]=default_video_attributes[attr];
1144 //js_log('attr:' + attr + ' val: ' + video_attributes[attr] +" "+ 'elm_val:' + element.getAttribute(attr) + "\n (set by attr)");
1145 }
1146 }
1147 //make sure startOffset is cast as an int
1148 if( this.startOffset && this.startOffset.split(':').length >= 2)
1149 this.startOffset = npt2seconds(this.startOffset);
1150 //make sure offset is in float:
1151 this.startOffset = parseFloat(this.startOffset);
1152
1153 if( this.duration && this.duration.split(':').length >= 2)
1154 this.duration = npt2seconds( this.duration );
1155 //make sure duration is in float:
1156 this.duration = parseFloat(this.duration);
1157 js_log("duration is: " + this.duration);
1158 //if style is set override width and height
1159 var dwh = mv_default_video_size.split('x');
1160 this.width = element.style.width ? element.style.width : dwh[0];
1161 this.height = element.style.height ? element.style.height : dwh[1];
1162 //set the plugin id
1163 this.pid = 'pid_' + this.id;
1164
1165 //grab any innerHTML and set it to missing_plugin_html
1166 //@@todo we should strip source tags instead of checking and skipping
1167 if(element.innerHTML!='' && element.getElementsByTagName('source').length==0){
1168 js_log('innerHTML: ' + element.innerHTML);
1169 this.user_missing_plugin_html=element.innerHTML;
1170 }
1171 // load all of the specified sources
1172 this.media_element = new mediaElement(element);
1173 },
1174 on_dom_swap: function(){
1175 js_log('f:on_dom_swap');
1176 // Process the provided ROE file... if we don't yet have sources
1177 if(this.roe && this.media_element.sources.length==0 ){
1178 js_log('loading external data');
1179 this.loading_external_data=true;
1180 var _this = this;
1181 do_request(this.roe, function(data)
1182 {
1183 //continue
1184 _this.media_element.addROE( data );
1185 js_log('added_roe::' + _this.media_element.sources.length);
1186
1187 js_log('set loading_external_data=false');
1188 _this.loading_external_data=false;
1189
1190 _this.init_with_sources_loaded();
1191 });
1192 }
1193 },
1194 init_with_sources_loaded : function()
1195 {
1196 js_log('f:init_with_sources_loaded');
1197 //set flag that we have run this function:
1198 this.init_with_sources_loadedDone=true;
1199 //autoseletct the source
1200 this.media_element.autoSelectSource();
1201 //auto select player based on prefrence or default order
1202 if( !this.media_element.selected_source )
1203 {
1204 //check for parent clip:
1205 if( typeof this.pc != 'undefined' ){
1206 js_log('no sources, type:' +this.type + ' check for html');
1207 //debugger;
1208 //do load player if just displaying innerHTML:
1209 if( this.pc.type == 'text/html' ){
1210 this.selected_player = embedTypes.players.defaultPlayer( 'text/html' );
1211 js_log('set selected player:'+ this.selected_player.mime_type);
1212 }
1213 }
1214 }else{
1215 this.selected_player = embedTypes.players.defaultPlayer( this.media_element.selected_source.mime_type );
1216 }
1217 if( this.selected_player ){
1218 js_log('selected ' + this.selected_player.getName());
1219 js_log("PLAYBACK TYPE: "+this.selected_player.library);
1220 this.thumbnail_disp = true;
1221 this.inheritEmbedObj();
1222 }else{
1223 //no source's playable
1224 var missing_type ='';
1225 var or ='';
1226 for( var i=0; i < this.media_element.sources.length; i++){
1227 missing_type+= or + this.media_element.sources[i].mime_type;
1228 or=' or ';
1229 }
1230 if( this.pc )
1231 var missing_type = this.pc.type;
1232 js_log('no player found for given source type ' + missing_type);
1233 this.load_error= this.getPluginMissingHTML(missing_type);
1234 }
1235 },
1236 inheritEmbedObj:function(){
1237 js_log("inheritEmbedObj:duration is: " + this.duration);
1238 //@@note: tricky cuz direct overwrite is not so ideal.. since the extended object is already tied to the dom
1239 //clear out any non-base embedObj stuff:
1240 if(this.instanceOf){
1241 eval('tmpObj = '+this.instanceOf);
1242 for(var i in tmpObj){ //for in loop oky for object
1243 if(this['parent_'+i]){
1244 this[i]=this['parent_'+i];
1245 }else{
1246 this[i]=null;
1247 }
1248 }
1249 }
1250 //set up the new embedObj
1251 js_log('f: inheritEmbedObj: embedding with ' + this.selected_player.library);
1252 var _this = this;
1253 this.selected_player.load( function()
1254 {
1255 js_log("selected_player::load:duration is: " + _this.duration);
1256 //js_log('inheriting '+_this.selected_player.library +'Embed to ' + _this.id + ' ' + $j('#'+_this.id).length);
1257 //var _this = $j('#'+_this.id).get(0);
1258 //js_log( 'type of ' + _this.selected_player.library +'Embed + ' +
1259 // eval('typeof '+_this.selected_player.library +'Embed'));
1260 eval('embedObj = ' +_this.selected_player.library +'Embed;');
1261 for(var method in embedObj){ //for in loop oky for object
1262 //parent method preservation for local overwritten methods
1263 if(_this[method])
1264 _this['parent_' + method] = _this[method];
1265 _this[method]=embedObj[method];
1266 }
1267 js_log('TYPEOF_ppause: ' + typeof _this['parent_pause']);
1268
1269 if(_this.inheritEmbedOverride){
1270 _this.inheritEmbedOverride();
1271 }
1272 //update controls if possible
1273 if(!_this.loading_external_data)
1274 _this.refreshControlsHTML();
1275
1276 //js_log("READY TO PLAY:"+_this.id);
1277 _this.ready_to_play=true;
1278 _this.getDuration();
1279 _this.getHTML();
1280 });
1281 },
1282 selectPlayer:function(player)
1283 {
1284 var _this = this;
1285 if(this.selected_player.id != player.id){
1286 this.selected_player = player;
1287 this.inheritEmbedObj();
1288 }
1289 },
1290 doNativeWarningCheck:function(){
1291 if( $j.cookie('dismissNativeWarn') && $j.cookie('dismissNativeWarn')===true){
1292 return false;
1293 }else{
1294 //see if we have native support for ogg:
1295 var supporting_players = embedTypes.players.getMIMETypePlayers( 'video/ogg' );
1296 for(var i=0; i < supporting_players.length; i++){
1297 if(supporting_players[i].id == 'videoElement'){
1298 return false;
1299 }
1300 }
1301 //see if we are using mv_embed without a ogg source in which case no point in promoting firefox :P
1302 if(this.media_element && this.media_element.sources){
1303 var foundOgg = false;
1304 var playable_sources = this.media_element.getPlayableSources();
1305 for(var sInx=0; sInx < playable_sources.length; sInx++){
1306 var mime_type = playable_sources[sInx].mime_type;
1307 if( mime_type=='video/ogg' ){
1308 //they have flash / h.264 fallback no need to push firefox :(
1309 foundOgg = true;
1310 }
1311 }
1312 //no ogg no point in download firefox
1313 if(!foundOgg)
1314 return false;
1315
1316 }
1317 }
1318 return true;
1319 },
1320 getTimeReq:function(){
1321 var default_time_req = '0:00:00/' + seconds2npt(this.getDuration());
1322 if(!this.media_element)
1323 return default_time_req;
1324 if(!this.media_element.selected_source)
1325 return default_time_req;
1326 if(!this.media_element.selected_source.end_ntp)
1327 return default_time_req;
1328 return this.media_element.selected_source.start_ntp+'/'+this.media_element.selected_source.end_ntp;
1329 },
1330 getDuration:function(){
1331 //update some local pointers for the selected source:
1332 if(this.media_element && this.media_element.selected_source && this.media_element.selected_source.duration){
1333 this.duration = this.media_element.selected_source.duration;
1334 this.start_offset = this.media_element.selected_source.start_offset;
1335 this.start_ntp = this.media_element.selected_source.start_ntp;
1336 this.end_ntp = this.media_element.selected_source.end_ntp;
1337 }
1338 //update start end_ntp if duration !=0 (set from plugin)
1339 if(!this.start_ntp)
1340 this.start_ntp = '0:0:0';
1341 if(!this.end_ntp && this.duration)
1342 this.end_ntp = seconds2npt( this.duration );
1343 //return the duration
1344 return this.duration;
1345 },
1346 /*
1347 * wrapEmebedContainer
1348 * wraps the embed code into a container to better support playlist function
1349 * (where embed element is swapped for next clip
1350 * (where plugin method does not support playlsits)
1351 */
1352 wrapEmebedContainer:function(embed_code){
1353 //check if parent clip is set( ie we are in a playlist so name the embed container by playlistID)
1354 var id = (this.pc!=null)?this.pc.pp.id:this.id;
1355 return '<div id="mv_ebct_'+id+'" style="width:'+this.width+'px;height:'+this.height+'px;">' +
1356 embed_code +
1357 '</div>';
1358 },
1359 getEmbedHTML : function(){
1360 //return this.wrapEmebedContainer( this.getEmbedObj() );
1361 return 'function getEmbedHTML should be overitten by embedLib ';
1362 },
1363 //do seek function (should be overwritten by implementing embedLibs)
1364 // first check if seek can be done on locally downloaded content.
1365 doSeek : function( perc ){
1366 if( this.supportsURLTimeEncoding() ){
1367 //make sure this.seek_time_sec is up-to-date:
1368 this.seek_time_sec = npt2seconds( this.start_ntp ) + parseFloat( perc * this.getDuration() );
1369 js_log('updated seek_time_sec: ' + seconds2npt ( this.seek_time_sec) );
1370 this.stop();
1371 this.didSeekJump=true;
1372 //update the slider
1373 this.setSliderValue( perc );
1374 }
1375 //do play in 100ms (give things time to clear)
1376 setTimeout('$j(\'#' + this.id + '\').get(0).play()',100);
1377 },
1378 /*
1379 * seeks to the requested time and issues a callback when ready
1380 * (should be overwitten by client that supports frame serving)
1381 */
1382 setCurrentTime:function( time, callback){
1383 js_log('error: base embed setCurrentTime can not frame serve (overide via plugin)');
1384 },
1385 addPresTimeOffset:function(){
1386 //add in the offset:
1387 if(this.seek_time_sec && this.seek_time_sec!=0){
1388 this.currentTime+=this.seek_time_sec;
1389 }else if(this.start_offset && this.start_offset!=0){
1390 this.currentTime = parseFloat(this.currentTime) + parseFloat(this.start_offset);
1391 }
1392 },
1393 doEmbedHTML:function()
1394 {
1395 js_log('f:doEmbedHTML');
1396 js_log('thum disp:'+this.thumbnail_disp);
1397 var _this = this;
1398 this.closeDisplayedHTML();
1399
1400 // if(!this.selected_player){
1401 // return this.getPluginMissingHTML();
1402 //Set "loading" here
1403 $j('#mv_embedded_player_'+_this.id).html(''+
1404 '<div style="color:black;width:'+this.width+'px;height:'+this.height+'px;">' +
1405 gM('loading_plugin') +
1406 '</div>'
1407 );
1408 // schedule embedding
1409 this.selected_player.load(function()
1410 {
1411 js_log('performing embed for ' + _this.id);
1412 var embed_code = _this.getEmbedHTML();
1413 //js_log('shopuld embed:' + embed_code);
1414 $j('#mv_embedded_player_'+_this.id).html(embed_code);
1415 });
1416 },
1417 onClipDone:function(){
1418 js_log('base:onClipDone');
1419 //stop the clip (load the thumbnail etc)
1420 this.stop();
1421 this.seek_time_sec = 0;
1422 this.setSliderValue(0);
1423 var _this = this;
1424
1425 //if the clip resolution is < 320 don't do fancy onClipDone stuff
1426 if(this.width < 300){
1427 return ;
1428 }
1429 this.onClipDone_disp=true;
1430 this.thumbnail_disp=true;
1431 //make sure we are not in preview mode( no end clip actions in preview mode)
1432 if( this.preview_mode )
1433 return ;
1434
1435 $j('#img_thumb_'+this.id).css('zindex',1);
1436 $j('#big_play_link_'+this.id).hide();
1437 //add the liks_info_div black back
1438 $j('#dc_'+this.id).append('<div id="liks_info_'+this.id+'" ' +
1439 'style="width:' +parseInt(parseInt(this.width)/2)+'px;'+
1440 'height:'+ parseInt(parseInt(this.height)) +'px;'+
1441 'position:absolute;top:10px;overflow:auto'+
1442 'width: '+parseInt( ((parseInt(this.width)/2)-15) ) + 'px;'+
1443 'left:'+ parseInt( ((parseInt(this.width)/2)+15) ) +'px;">'+
1444 '</div>' +
1445 '<div id="black_back_'+this.id+'" ' +
1446 'style="z-index:-2;position:absolute;background:#000;' +
1447 'top:0px;left:0px;width:'+parseInt(this.width)+'px;' +
1448 'height:'+parseInt(this.height)+'px;">' +
1449 '</div>'
1450 );
1451
1452 //start animation (make thumb small in upper left add in div for "loading"
1453 $j('#img_thumb_'+this.id).animate({
1454 width:parseInt(parseInt(_this.width)/2),
1455 height:parseInt(parseInt(_this.height)/2),
1456 top:20,
1457 left:10
1458 },
1459 1000,
1460 function(){
1461 //animation done.. add "loading" to div if empty
1462 if($j('#liks_info_'+_this.id).html()==''){
1463 $j('#liks_info_'+_this.id).html(gM('loading_txt'));
1464 }
1465 }
1466 )
1467 //now load roe if run the showNextPrevLinks
1468 if(this.roe && this.media_element.addedROEData==false){
1469 do_request(this.roe, function(data)
1470 {
1471 _this.media_element.addROE(data);
1472 _this.getNextPrevLinks();
1473 });
1474 }else{
1475 this.getNextPrevLinks();
1476 }
1477 },
1478 //@@todo we should merge getNextPrevLinks with textInterface .. there is repeated code between them.
1479 getNextPrevLinks:function(){
1480 js_log('f:getNextPrevLinks');
1481 var anno_track_url = null;
1482 var _this = this;
1483 //check for annoative track
1484 $j.each(this.media_element.sources, function(inx, n){
1485 if(n.mime_type=='text/cmml'){
1486 if( n.id == 'Anno_en'){
1487 anno_track_url = n.src;
1488 }
1489 }
1490 });
1491 if( anno_track_url ){
1492 js_log('found annotative track:'+ anno_track_url);
1493 //zero out seconds (should improve cache hit rate and generally expands metadata search)
1494 //@@todo this could be repalced with a regExp
1495 var annoURL = parseUri(anno_track_url);
1496 var times = annoURL.queryKey['t'].split('/');
1497 var stime_parts = times[0].split(':');
1498 var etime_parts = times[1].split(':');
1499 //zero out the hour:
1500 var new_start = stime_parts[0]+':'+'0:0';
1501 //zero out the end sec
1502 var new_end = (etime_parts[0]== stime_parts[0])? (etime_parts[0]+1)+':0:0' :etime_parts[0]+':0:0';
1503
1504 var etime_parts = times[1].split(':');
1505
1506 var new_anno_track_url = annoURL.protocol +'://'+ annoURL.host + annoURL.path +'?';
1507 $j.each(annoURL.queryKey, function(i, val){
1508 new_anno_track_url +=(i=='t')?'t='+new_start+'/'+new_end +'&' :
1509 i+'='+ val+'&';
1510 });
1511 var request_key = new_start+'/'+new_end;
1512 //check the anno_data cache:
1513 //@@todo search cache see if current is in range.
1514 if(this.anno_data_cache){
1515 js_log('anno data found in cache: '+request_key);
1516 this.showNextPrevLinks();
1517 }else{
1518 do_request(new_anno_track_url, function(cmml_data){
1519 js_log('raw response: '+ cmml_data);
1520 if(typeof cmml_data == 'string')
1521 {
1522 var parser=new DOMParser();
1523 js_log('Parse CMML data:' + cmml_data);
1524 cmml_data=parser.parseFromString(cmml_data,"text/xml");
1525 }
1526 //init anno_data_cache
1527 if(!_this.anno_data_cache)
1528 _this.anno_data_cache={};
1529 //grab all metadata and put it into the anno_data_cache:
1530 $j.each(cmml_data.getElementsByTagName('clip'), function(inx, clip){
1531 _this.anno_data_cache[ $j(clip).attr("id") ]={
1532 'start_time_sec':npt2seconds($j(clip).attr("start").replace('npt:','')),
1533 'end_time_sec':npt2seconds($j(clip).attr("end").replace('npt:','')),
1534 'time_req':$j(clip).attr("start").replace('npt:','')+'/'+$j(clip).attr("end").replace('npt:','')
1535 };
1536 //grab all its meta
1537 _this.anno_data_cache[ $j(clip).attr("id") ]['meta']={};
1538 $j.each(clip.getElementsByTagName('meta'),function(imx, meta){
1539 //js_log('adding meta: '+ $j(meta).attr("name")+ ' = '+ $j(meta).attr("content"));
1540 _this.anno_data_cache[$j(clip).attr("id")]['meta'][$j(meta).attr("name")]=$j(meta).attr("content");
1541 });
1542 });
1543 _this.showNextPrevLinks();
1544 });
1545 }
1546 }else{
1547 js_log('no annotative track found');
1548 $j('#liks_info_'+this.id).html('no metadata found for related links');
1549 }
1550 //query current request time +|- 60s to get prev next speech links.
1551 },
1552 showNextPrevLinks:function(){
1553 //js_log('f:showNextPrevLinks');
1554 //int requested links:
1555 var link = {
1556 'prev':'',
1557 'current':'',
1558 'next':''
1559 }
1560 var curTime = this.getTimeReq().split('/');
1561
1562 var s_sec = npt2seconds(curTime[0]);
1563 var e_sec = npt2seconds(curTime[1]);
1564 js_log('showNextPrevLinks: req time: '+ s_sec + ' to ' + e_sec);
1565 //now we have all the data in anno_data_cache
1566 var current_done=false;
1567 for(var clip_id in this.anno_data_cache){ //for in loop oky for object
1568 var clip = this.anno_data_cache[clip_id];
1569 //js_log('on clip:'+ clip_id);
1570 //set prev_link (if cur_link is still empty)
1571 if( s_sec > clip.end_time_sec){
1572 link.prev = clip_id;
1573 js_log('showNextPrevLinks: ' + s_sec + ' < ' + clip.end_time_sec + ' set prev');
1574 }
1575
1576 if(e_sec==clip.end_time_sec && s_sec== clip.start_time_sec)
1577 current_done = true;
1578 //current clip is not done:
1579 if( e_sec < clip.end_time_sec && link.current=='' && !current_done){
1580 link.current = clip_id;
1581 js_log('showNextPrevLinks: ' + e_sec + ' < ' + clip.end_time_sec + ' set current');
1582 }
1583
1584 //set end clip (first clip where start time is > end_time of req
1585 if( e_sec < clip.start_time_sec && link.next==''){
1586 link.next = clip_id;
1587 js_log('showNextPrevLinks: '+ e_sec + ' < '+ clip.start_time_sec + ' && ' + link.next );
1588 }
1589 }
1590 var html='';
1591 if(link.prev=='' && link.current=='' && link.next==''){
1592 html='<p><a href="'+this.media_element.linkbackgetMsg+'">clip page</a>';
1593 }else{
1594 for(var link_type in link){
1595 var link_id = link[link_type];
1596 if(link_id!=''){
1597 var clip = this.anno_data_cache[link_id];
1598 var title_msg='';
1599 for(var j in clip['meta']){
1600 title_msg+=j.replace(/_/g,' ') +': ' +clip['meta'][j].replace(/_/g,' ') +" <br>";
1601 }
1602 var time_req = clip.time_req;
1603 if(link_type=='current') //if current start from end of current clip play to end of current meta:
1604 time_req = curTime[1]+ '/' + seconds2npt( clip.end_time_sec );
1605
1606 //do special linkbacks for metavid content:
1607 var regTimeCheck = new RegExp(/[0-9]+:[0-9]+:[0-9]+\/[0-9]+:[0-9]+:[0-9]+/);
1608 html+='<p><a ';
1609 if( regTimeCheck.test( this.media_element.linkback ) ){
1610 html+=' href="'+ this.media_element.linkback.replace(regTimeCheck,time_req) +'" ';
1611 }else{
1612 html+=' href="#" onClick="$j(\'#'+this.id+'\').get(0).playByTimeReq(\''+
1613 time_req + '\'); return false; "';
1614 }
1615 html+=' title="' + title_msg + '">' +
1616 gM(link_type+'_clip_msg') +
1617 '</a><br><span style="font-size:small">'+ title_msg +'<span></p>';
1618 }
1619 }
1620 }
1621 //js_og("should set html:"+ html);
1622 $j('#liks_info_'+this.id).html(html);
1623 },
1624 playByTimeReq: function(time_req){
1625 js_log('f:playByTimeReq: '+time_req );
1626 this.stop();
1627 this.updateVideoTimeReq(time_req);
1628 this.play();
1629 },
1630 doThumbnailHTML:function()
1631 {
1632 var _this = this;
1633 js_log('f:doThumbnailHTML'+ this.thumbnail_disp);
1634 this.closeDisplayedHTML();
1635 $j( '#mv_embedded_player_' + this.id ).html( this.getThumbnailHTML() );
1636 this.paused = true;
1637 this.thumbnail_disp = true;
1638 },
1639 refreshControlsHTML:function(){
1640 js_log('refreshing controls HTML');
1641 if($j('#mv_embedded_controls_'+this.id).length==0)
1642 {
1643 js_log('#mv_embedded_controls_'+this.id + ' not present, returning');
1644 return;
1645 }else{
1646 $j('#mv_embedded_controls_'+this.id).html( this.getControlsHTML() );
1647 ctrlBuilder.addControlHooks(this);
1648 }
1649 },
1650 getControlsHTML:function()
1651 {
1652 return ctrlBuilder.getControls( this );
1653 },
1654 getHTML : function (){
1655 //@@todo check if we have sources avaliable
1656 js_log('embedVideo:getHTML : ' + this.id + ' resource type: ' + this.type);
1657 var _this = this;
1658 var html_code = '';
1659 html_code = '<div id="videoPlayer_'+this.id+'" style="width:'+this.width+'px;position:relative;" class="videoPlayer">';
1660 html_code += '<div style="width:'+parseInt(this.width)+'px;height:'+parseInt(this.height)+'px;" id="mv_embedded_player_'+this.id+'">' +
1661 this.getThumbnailHTML() +
1662 '</div>';
1663 //js_log("mvEmbed:controls "+ typeof this.controls);
1664 if( this.controls )
1665 {
1666 js_log("f:getHTML:AddControls");
1667 html_code +='<div id="mv_embedded_controls_' + this.id + '" class="ui-widget ui-corner-bottom ui-state-default controls" >';
1668 html_code += this.getControlsHTML();
1669 html_code +='</div>';
1670 //block out some space by encapulating the top level div
1671 $j(this).wrap('<div style="width:'+parseInt(this.width)+'px;height:'
1672 +(parseInt(this.height)+ctrlBuilder.height)+'px"></div>');
1673 }
1674 html_code += '</div>'; //videoPlayer div close
1675 //js_log('should set: '+this.id);
1676 $j(this).html( html_code );
1677 //add hooks once Controls are in DOM
1678 ctrlBuilder.addControlHooks(this);
1679
1680 //js_log('set this to: ' + $j(this).html() );
1681 //alert('stop');
1682 //if auto play==true directly embed the plugin
1683 if(this.autoplay)
1684 {
1685 js_log('activating autoplay');
1686 this.play();
1687 }
1688 },
1689 /*
1690 * get missing plugin html (check for user included code)
1691 */
1692 getPluginMissingHTML : function(missing_type){
1693 //keep the box width hight:
1694 var out = '<div style="width:'+this.width+'px;height:'+this.height+'px">';
1695 if(this.user_missing_plugin_html){
1696 out+= this.user_missing_plugin_html;
1697 }else{
1698 if(!missing_type)
1699 missing_type='';
1700 out+= gM('mv_generic_missing_plugin', missing_type) + ' or <a title="'+gM('download_clip')+'" href="'+this.src +'">'+gM('download_clip')+'</a>';
1701 }
1702 return out + '</div>';
1703 },
1704 updateVideoTimeReq:function(time_req){
1705 js_log('f:updateVideoTimeReq');
1706 var time_parts =time_req.split('/');
1707 this.updateVideoTime(time_parts[0], time_parts[1]);
1708 },
1709 //update video time
1710 updateVideoTime:function(start_ntp, end_ntp){
1711 //update media
1712 this.media_element.updateSourceTimes( start_ntp, end_ntp );
1713 //update mv_time
1714 this.setStatus(start_ntp+'/'+end_ntp);
1715 //reset slider
1716 this.setSliderValue(0);
1717 //reset seek_offset:
1718 if(this.media_element.selected_source.URLTimeEncoding )
1719 this.seek_time_sec=0;
1720 else
1721 this.seek_time_sec=npt2seconds(start_ntp);
1722 },
1723 //@@todo overwite by embed library if we can render frames natavily
1724 renderTimelineThumbnail:function( options ){
1725 var my_thumb_src = this.media_element.getThumbnailURL();
1726 //check if our thumbnail has a time attribute:
1727 if( my_thumb_src.indexOf('t=') !== -1){
1728 var time_ntp = seconds2npt ( options.time + parseInt(this.start_offset) );
1729 my_thumb_src = getURLParamReplace( my_thumb_src, { 't':time_ntp, 'size': options.size } );
1730 }
1731 var thumb_class = (typeof options['thumb_class'] != 'undefined' ) ? options['thumb_class'] : '';
1732 return '<div class="ui-corner-all ' + thumb_class + '" src="' + my_thumb_src + '" '+
1733 'style="height:' + options.height + 'px;' +
1734 'width:' + options.width + 'px" >' +
1735 '<img src="' + my_thumb_src +'" '+
1736 'style="height:' + options.height + 'px;' +
1737 'width:' + options.width + 'px">' +
1738 '</div>';
1739 },
1740 updateThumbTimeNTP:function( time){
1741 this.updateThumbTime( npt2seconds(time) - parseInt(this.start_offset) );
1742 },
1743 updateThumbTime:function( float_sec ){
1744 //js_log('updateThumbTime:'+float_sec);
1745 var _this = this;
1746 if( typeof this.org_thum_src=='undefined' ){
1747 this.org_thum_src = this.media_element.getThumbnailURL();
1748 }
1749 if( this.org_thum_src.indexOf('t=') !== -1){
1750 this.last_thumb_url = getURLParamReplace(this.org_thum_src,
1751 { 't' : seconds2npt( float_sec + parseInt(this.start_offset)) } );
1752 if(!this.thumbnail_updating){
1753 this.updateThumbnail(this.last_thumb_url ,false);
1754 this.last_thumb_url =null;
1755 }
1756 }
1757 },
1758 //for now provide a src url .. but need to figure out how to copy frames from video for plug-in based thumbs
1759 updateThumbPerc:function( perc ){
1760 return this.updateThumbTime( (this.getDuration() * perc) );
1761 },
1762 //updates the thumbnail if the thumbnail is being displayed
1763 updateThumbnail : function(src, quick_switch){
1764 //make sure we don't go to the same url if we are not already updating:
1765 if( !this.thumbnail_updating && $j('#img_thumb_'+this.id).attr('src')== src )
1766 return false;
1767 //if we are already updating don't issue a new update:
1768 if( this.thumbnail_updating && $j('#new_img_thumb_'+this.id).attr('src')== src )
1769 return false;
1770
1771 js_log('update thumb: ' + src);
1772
1773 if(quick_switch){
1774 $j('#img_thumb_'+this.id).attr('src', src);
1775 }else{
1776 var _this = this;
1777 //if still animating remove new_img_thumb_
1778 if(this.thumbnail_updating==true)
1779 $j('#new_img_thumb_'+this.id).stop().remove();
1780
1781 if(this.thumbnail_disp){
1782 js_log('set to thumb:'+ src);
1783 this.thumbnail_updating=true;
1784 $j('#dc_'+this.id).append('<img src="'+src+'" ' +
1785 'style="display:none;position:absolute;zindex:2;top:0px;left:0px;" ' +
1786 'width="'+this.width+'" height="'+this.height+'" '+
1787 'id = "new_img_thumb_'+this.id+'" />');
1788 //js_log('appended: new_img_thumb_');
1789 $j('#new_img_thumb_'+this.id).fadeIn("slow", function(){
1790 //once faded in remove org and rename new:
1791 $j('#img_thumb_'+_this.id).remove();
1792 $j('#new_img_thumb_'+_this.id).attr('id', 'img_thumb_'+_this.id);
1793 $j('#img_thumb_'+_this.id).css('zindex','1');
1794 _this.thumbnail_updating=false;
1795 //js_log("done fadding in "+ $j('#img_thumb_'+_this.id).attr("src"));
1796
1797 //if we have a thumb queued update to that
1798 if(_this.last_thumb_url){
1799 var src_url =_this.last_thumb_url;
1800 _this.last_thumb_url=null;
1801 _this.updateThumbnail(src_url);
1802 }
1803 });
1804 }
1805 }
1806 },
1807 /** Returns the HTML code for the video when it is in thumbnail mode.
1808 This includes the specified thumbnail as well as buttons for
1809 playing, configuring the player, inline cmml display, HTML linkback,
1810 download, and embed code.
1811 */
1812 getThumbnailHTML : function ()
1813 {
1814 js_log('embedVideo:getThumbnailHTML::' + this.id);
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:absolute;'+
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 'poster=&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("embed video set: " + '#mv_play_head_'+this_id + ' to ' + val);
2397 }
2398 //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) );
2399 //js_log('op:' + offset_perc + ' *('+perc+' * ' + $j('#slider_'+id).width() + ')');
2400 },
2401 highlightPlaySection:function(options){
2402 js_log('highlightPlaySection');
2403 var this_id = (this.pc)?this.pc.pp.id:this.id;
2404 var dur = this.getDuration();
2405 var hide_progress = true;
2406 //set the left percet and update the slider:
2407 rel_start_sec = npt2seconds( options['start']);
2408 //remove the start_offset if relevent:
2409 if(this.start_offset)
2410 rel_start_sec = rel_start_sec - this.start_offset
2411
2412 var slider_perc=0;
2413 if( rel_start_sec <= 0 ){
2414 left_perc =0;
2415 options['start'] = seconds2npt( this.start_offset );
2416 rel_start_sec=0;
2417 this.setSliderValue( 0 , hide_progress);
2418 }else{
2419 left_perc = parseInt( (rel_start_sec / dur)*100 ) ;
2420 slider_perc = (left_perc / 100);
2421 }
2422 js_log("slider perc:" + slider_perc);
2423 if( ! this.isPlaying() ){
2424 this.setSliderValue( slider_perc , hide_progress);
2425 }
2426
2427 width_perc = parseInt( (( npt2seconds( options['end'] ) - npt2seconds( options['start'] ) ) / dur)*100 ) ;
2428 if( (width_perc + left_perc) > 100 ){
2429 width_perc = 100 - left_perc;
2430 }
2431 //js_log('should hl: '+rel_start_sec+ '/' + dur + ' re:' + rel_end_sec+' lp:' + left_perc + ' width: ' + width_perc);
2432 $j('#mv_seeker_' + this_id + ' .mv_highlight').css({
2433 'left':left_perc+'%',
2434 'width':width_perc+'%'
2435 }).show();
2436
2437 this.jump_time = options['start'];
2438 this.seek_time_sec = npt2seconds( options['start']);
2439 //trim output to
2440 this.setStatus( gM('seek_to')+' '+ seconds2npt( this.seek_time_sec ) );
2441 js_log('DO update: ' + this.jump_time);
2442 this.updateThumbTime( rel_start_sec );
2443 },
2444 hideHighlight:function(){
2445 var this_id = (this.pc)?this.pc.pp.id:this.id;
2446 $j('#mv_seeker_' + this_id + ' .mv_highlight').hide();
2447 this.setStatus( this.getTimeReq() );
2448 this.setSliderValue( 0 );
2449 },
2450 setStatus:function(value){
2451 var id = (this.pc)?this.pc.pp.id:this.id;
2452 //update status:
2453 $j('#mv_time_'+id).html(value);
2454 }
2455 }
2456
2457
2458
2459 /**
2460 * mediaPlayer represents a media player plugin.
2461 * @param {String} id id used for the plugin.
2462 * @param {Array<String>} supported_types n array of supported MIME types.
2463 * @param {String} library external script containing the plugin interface code. (mv_<library>Embed.js)
2464 * @constructor
2465 */
2466 function mediaPlayer(id, supported_types, library)
2467 {
2468 this.id=id;
2469 this.supported_types = supported_types;
2470 this.library = library;
2471 this.loaded = false;
2472 this.loading_callbacks = new Array();
2473 return this;
2474 }
2475 mediaPlayer.prototype =
2476 {
2477 id:null,
2478 supported_types:null,
2479 library:null,
2480 loaded:false,
2481 loading_callbacks:null,
2482 supportsMIMEType : function(type)
2483 {
2484 for (var i=0; i < this.supported_types.length; i++)
2485 if(this.supported_types[i] == type)
2486 return true;
2487 return false;
2488 },
2489 getName : function()
2490 {
2491 return gM('mv_ogg-player-' + this.id);
2492 },
2493 load : function(callback){
2494 var libName = this.library+'Embed';
2495 if( mvJsLoader.checkObjPath( libName ) ){
2496 js_log('plugin loaded, do callback:');
2497 callback();
2498 }else{
2499 var _this = this;
2500 //jQuery based get script does not work so well.
2501 mvJsLoader.doLoad([
2502 libName
2503 ],function(){
2504 callback();
2505 });
2506 }
2507 }
2508 }
2509 /* players and supported mime types
2510 @@todo ideally we query the plugin to get what mime types it supports in practice not always reliable/avaliable
2511 */
2512 var flowPlayer = new mediaPlayer('flowplayer',['video/x-flv', 'video/h264'],'flash');
2513
2514 var omtkPlayer = new mediaPlayer('omtkplayer',['audio/ogg'], 'omtk' );
2515
2516 var cortadoPlayer = new mediaPlayer('cortado',['video/ogg', 'audio/ogg'],'java');
2517 var videoElementPlayer = new mediaPlayer('videoElement',['video/ogg', 'audio/ogg'],'native');
2518
2519 var vlcMineList = ['video/ogg','audio/ogg', 'video/x-flv', 'video/mp4', 'video/h264'];
2520 var vlcMozillaPlayer = new mediaPlayer('vlc-mozilla',vlcMineList,'vlc');
2521 var vlcActiveXPlayer = new mediaPlayer('vlc-activex',vlcMineList,'vlc');
2522
2523 //add generic
2524 var oggPluginPlayer = new mediaPlayer('oggPlugin',['video/ogg'],'generic');
2525
2526 //depricate quicktime in favor of safari native
2527 //var quicktimeMozillaPlayer = new mediaPlayer('quicktime-mozilla',['video/ogg'],'quicktime');
2528 //var quicktimeActiveXPlayer = new mediaPlayer('quicktime-activex',['video/ogg'],'quicktime');
2529
2530 var htmlPlayer = new mediaPlayer('html',['text/html', 'image/jpeg', 'image/png', 'image/svg'], 'html');
2531
2532 /**
2533 * mediaPlayers is a collection of mediaPlayer objects supported by the client.
2534 * It could be merged with embedTypes, since there is one embedTypes per script
2535 * and one mediaPlayers per embedTypes.
2536 */
2537 function mediaPlayers()
2538 {
2539 this.init();
2540 }
2541
2542 mediaPlayers.prototype =
2543 {
2544 players : null,
2545 preference : null,
2546 default_players : {},
2547 init : function()
2548 {
2549 this.players = new Array();
2550 this.loadPreferences();
2551
2552 //set up default players order for each library type
2553 this.default_players['video/x-flv'] = ['flash','vlc'];
2554 this.default_players['video/h264'] = ['flash', 'vlc'];
2555
2556 this.default_players['video/ogg'] = ['native','vlc','java', 'generic'];
2557 this.default_players['application/ogg'] = ['native','vlc','java', 'generic'];
2558 this.default_players['audio/ogg'] = ['native','vlc', 'java', 'omtk' ];
2559 this.default_players['video/mp4'] = ['vlc'];
2560
2561 this.default_players['text/html'] = ['html'];
2562 this.default_players['image/jpeg'] = ['html'];
2563 this.default_players['image/png'] = ['html'];
2564 this.default_players['image/svg'] = ['html'];
2565
2566 },
2567 addPlayer : function(player, mime_type)
2568 {
2569 //js_log('Adding ' + player.id + ' with mime_type ' + mime_type);
2570 for (var i =0; i < this.players.length; i++){
2571 if (this.players[i].id == player.id)
2572 {
2573 if(mime_type!=null)
2574 {
2575 //make sure the mime_type is not already there:
2576 var add_mime = true;
2577 for(var j=0; j < this.players[i].supported_types.length; j++ ){
2578 if( this.players[i].supported_types[j]== mime_type)
2579 add_mime=false;
2580 }
2581 if(add_mime)
2582 this.players[i].supported_types.push(mime_type);
2583 }
2584 return;
2585 }
2586 }
2587 //player not found:
2588 if(mime_type!=null)
2589 player.supported_types.push(mime_type);
2590
2591 this.players.push( player );
2592 },
2593 getMIMETypePlayers : function(mime_type)
2594 {
2595 var mime_players = new Array();
2596 var _this = this;
2597 var inx = 0;
2598 if( this.default_players[mime_type] ){
2599 $j.each( this.default_players[mime_type], function(d, lib){
2600 var library = _this.default_players[mime_type][d];
2601 for ( var i=0; i < _this.players.length; i++ ){
2602 if ( _this.players[i].library == library && _this.players[i].supportsMIMEType(mime_type) ){
2603 mime_players[ inx ] = _this.players[i];
2604 inx++;
2605 }
2606 }
2607 });
2608 }
2609 return mime_players;
2610 },
2611 defaultPlayer : function(mime_type)
2612 {
2613 js_log("get defaultPlayer for " + mime_type);
2614 var mime_players = this.getMIMETypePlayers(mime_type);
2615 if( mime_players.length > 0)
2616 {
2617 // check for prior preference for this mime type
2618 for( var i=0; i < mime_players.length; i++ ){
2619 if( mime_players[i].id==this.preference[mime_type] )
2620 return mime_players[i];
2621 }
2622 // otherwise just return the first compatible player
2623 // (it will be chosen according to the default_players list
2624 return mime_players[0];
2625 }
2626 js_log( 'No default player found for ' + mime_type );
2627 return null;
2628 },
2629 userSelectFormat : function (mime_format){
2630 this.preference['format_prefrence'] = mime_format;
2631 this.savePreferences();
2632 },
2633 userSelectPlayer : function(player_id, mime_type)
2634 {
2635 var selected_player=null;
2636 for(var i=0; i < this.players.length; i++){
2637 if(this.players[i].id == player_id)
2638 {
2639 selected_player = this.players[i];
2640 js_log('choosing ' + player_id + ' for ' + mime_type);
2641 this.preference[mime_type]=player_id;
2642 this.savePreferences();
2643 break;
2644 }
2645 }
2646 if( selected_player )
2647 {
2648 for(var i=0; i < global_player_list.length; i++)
2649 {
2650 var embed = $j('#'+global_player_list[i]).get(0);
2651 if(embed.media_element.selected_source && (embed.media_element.selected_source.mime_type == mime_type))
2652 {
2653 embed.selectPlayer(selected_player);
2654 js_log('using ' + embed.selected_player.getName() + ' for ' + embed.media_element.selected_source.getTitle());
2655 }
2656 }
2657 }
2658 },
2659 loadPreferences : function()
2660 {
2661 this.preference = new Object();
2662 // see if we have a cookie set to a clientSupported type:
2663 var cookieVal = $j.cookie( 'ogg_player_exp' );
2664 if (cookieVal)
2665 {
2666 var pairs = cookieVal.split('&');
2667 for(var i=0; i < pairs.length; i++)
2668 {
2669 var name_value = pairs[i].split('=');
2670 this.preference[name_value[0]]=name_value[1];
2671 //js_log('load preference for ' + name_value[0] + ' is ' + name_value[1]);
2672 }
2673 }
2674 },
2675 savePreferences : function()
2676 {
2677 var cookieVal = '';
2678 for(var i in this.preference)
2679 cookieVal+= i + '='+ this.preference[i] + '&';
2680
2681 cookieVal=cookieVal.substr(0, cookieVal.length-1);
2682 var week = 7*86400*1000;
2683 $j.cookie( 'ogg_player_exp', cookieVal, { 'expires':week } );
2684 }
2685 };
2686
2687 /*
2688 * embedTypes object handles setting and getting of supported embed types:
2689 * closely mirrors OggHandler so that its easier to share efforts in this area:
2690 * http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/OggHandler/OggPlayer.js
2691 */
2692 var embedTypes = {
2693 // List of players
2694 players: null,
2695 detect_done:false,
2696 init: function(){
2697 //detect supported types
2698 this.detect();
2699 this.detect_done=true;
2700 },
2701 clientSupports: { 'thumbnail' : true },
2702 supportedMimeType: function(mimetype) {
2703 for (var i = navigator.plugins.length; i-- > 0; ) {
2704 var plugin = navigator.plugins[i];
2705 if (typeof plugin[mimetype] != "undefined")
2706 return true;
2707 }
2708 return false;
2709 },
2710
2711 detect: function() {
2712 js_log("running detect");
2713 this.players = new mediaPlayers();
2714 //every browser supports html rendering:
2715 this.players.addPlayer( htmlPlayer );
2716 // In Mozilla, navigator.javaEnabled() only tells us about preferences, we need to
2717 // search navigator.mimeTypes to see if it's installed
2718 var javaEnabled = navigator.javaEnabled();
2719 // In Opera, navigator.javaEnabled() is all there is
2720 var invisibleJava = $j.browser.opera;
2721 // Some browsers filter out duplicate mime types, hiding some plugins
2722 var uniqueMimesOnly = $j.browser.opera || $j.browser.safari;
2723 // Opera will switch off javaEnabled in preferences if java can't be found.
2724 // And it doesn't register an application/x-java-applet mime type like Mozilla does.
2725 if ( invisibleJava && javaEnabled )
2726 this.players.addPlayer( cortadoPlayer );
2727
2728 // ActiveX plugins
2729 if($j.browser.msie){
2730 // check for flash
2731 if ( this.testActiveX( 'ShockwaveFlash.ShockwaveFlash')){
2732 //try to get the flash version for omtk include:
2733 try {
2734 a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
2735 d = a.GetVariable("$version"); // Will crash fp6.0.21/23/29
2736 if (d) {
2737 d = d.split(" ")[1].split(",");
2738 //we need flash version 10 or greater:
2739 if(parseInt( d[0]) >=10){
2740 this.players.addPlayer( omtkPlayer );
2741 }
2742
2743 }
2744 }catch(e) {}
2745
2746 //flowplayer has pretty good compatiablity
2747 // (but if we wanted to be fancy we would check for version of flash and update the mp4/h.264 support
2748 this.players.addPlayer( flowPlayer );
2749 }
2750 // VLC
2751 if ( this.testActiveX( 'VideoLAN.VLCPlugin.2' ) )
2752 this.players.addPlayer(vlcActiveXPlayer);
2753 // Java
2754 if ( javaEnabled && this.testActiveX( 'JavaWebStart.isInstalled' ) )
2755 this.players.addPlayer(cortadoPlayer);
2756 // quicktime
2757 //if ( this.testActiveX( 'QuickTimeCheckObject.QuickTimeCheck.1' ) )
2758 // this.players.addPlayer(quicktimeActiveXPlayer);
2759 }
2760 // <video> element
2761 if ( typeof HTMLVideoElement == 'object' // Firefox, Safari
2762 || typeof HTMLVideoElement == 'function' ) // Opera
2763 {
2764 //do another test for safari:
2765 if( $j.browser.safari ){
2766 try{
2767 var dummyvid = document.createElement("video");
2768 if (dummyvid.canPlayType && dummyvid.canPlayType("video/ogg;codecs=\"theora,vorbis\"") == "probably")
2769 {
2770 this.players.addPlayer( videoElementPlayer );
2771 } else if(this.supportedMimeType( 'video/ogg' )) {
2772 /* older versions of safari do not support canPlayType,
2773 but xiph qt registers mimetype via quicktime plugin */
2774 this.players.addPlayer( videoElementPlayer );
2775 } else {
2776 //@@todo add some user nagging to install the xiph qt
2777 }
2778 }catch(e){
2779 js_log('could not run canPlayType in safari');
2780 }
2781 }else{
2782 this.players.addPlayer( videoElementPlayer );
2783 }
2784 }
2785
2786 // Mozilla plugins
2787 if( navigator.mimeTypes && navigator.mimeTypes.length > 0) {
2788 for ( var i = 0; i < navigator.mimeTypes.length; i++ ) {
2789 var type = navigator.mimeTypes[i].type;
2790 var semicolonPos = type.indexOf( ';' );
2791 if ( semicolonPos > -1 ) {
2792 type = type.substr( 0, semicolonPos );
2793 }
2794 //js_log('on type: '+type);
2795 var pluginName = navigator.mimeTypes[i].enabledPlugin ? navigator.mimeTypes[i].enabledPlugin.name : '';
2796 if ( !pluginName ) {
2797 // In case it is null or undefined
2798 pluginName = '';
2799 }
2800 if ( pluginName.toLowerCase() == 'vlc multimedia plugin' || pluginName.toLowerCase() == 'vlc multimedia plug-in' ) {
2801 this.players.addPlayer(vlcMozillaPlayer, type);
2802 continue;
2803 }
2804
2805 if ( javaEnabled && type == 'application/x-java-applet' ) {
2806 this.players.addPlayer(cortadoPlayer);
2807 continue;
2808 }
2809
2810 if ( type == 'application/ogg' ) {
2811 if ( pluginName.toLowerCase() == 'vlc multimedia plugin' ){
2812 this.players.addPlayer(vlcMozillaPlayer, type);
2813 //else if ( pluginName.indexOf( 'QuickTime' ) > -1 )
2814 // this.players.addPlayer(quicktimeMozillaPlayer);
2815 }else{
2816 this.players.addPlayer(oggPluginPlayer);
2817 }
2818 continue;
2819 } else if ( uniqueMimesOnly ) {
2820 if ( type == 'application/x-vlc-player' ) {
2821 this.players.addPlayer(vlcMozillaPlayer, type);
2822 continue;
2823 } else if ( type == 'video/quicktime' ) {
2824 //this.players.addPlayer(quicktimeMozillaPlayer);
2825 continue;
2826 }
2827 }
2828
2829 /*if ( type == 'video/quicktime' ) {
2830 this.players.addPlayer(vlcMozillaPlayer, type);
2831 continue;
2832 }*/
2833 if(type=='application/x-shockwave-flash'){
2834 this.players.addPlayer( flowPlayer );
2835
2836 //check version to add omtk:
2837 var flashDescription = navigator.plugins["Shockwave Flash"].description;
2838 var descArray = flashDescription.split(" ");
2839 var tempArrayMajor = descArray[2].split(".");
2840 var versionMajor = tempArrayMajor[0];
2841 //js_log("version of flash: " + versionMajor);
2842 if(versionMajor >= 10){
2843 this.players.addPlayer( omtkPlayer );
2844 }
2845 continue;
2846 }
2847 }
2848 }
2849 //@@The xiph quicktime component does not work well with annodex streams (temporarly disable)
2850 //this.clientSupports['quicktime-mozilla'] = false;
2851 //this.clientSupports['quicktime-activex'] = false;
2852 //js_log(this.clientSupports);
2853 },
2854 testActiveX : function ( name ) {
2855 var hasObj = true;
2856 try {
2857 // No IE, not a class called "name", it's a variable
2858 var obj = new ActiveXObject( '' + name );
2859 } catch ( e ) {
2860 hasObj = false;
2861 }
2862 return hasObj;
2863 }
2864 };