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