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