1 /* the base video control JSON object with default attributes
2 * for supported attribute details see README
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"
62 var default_video_attributes = {
78 "wikiTitleKey" : null,
80 // roe url (for xml based metadata)
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)
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)
94 "startOffset" : null, // if serving an ogg_chop segment use this to offset the presentation time
96 // custom attributes for mv_embed:
101 "download_link" : true,
102 "type" :null // the content type of the media
105 * the base source attribute checks
107 var mv_default_source_attr = new Array(
113 'URLTimeEncoding', // boolean if we support temporal url requests on the source media
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 ) {
130 mvEmbed.init( swap_done_callback, force_id );
133 // flist stores the set of functions to run after the video has been swapped in.
135 init:function( swap_callback, force_id ) {
139 mvEmbed.flist.push( swap_callback );
140 // Get mv_embed location if it has not been set
141 js_log( 'mvEmbed:init: ' + mw.version );
143 var loadPlaylistLib = false;
145 // Setup the selector (should be a configuration option)
146 if ( force_id == null && force_id != '' ) {
147 var j_selector = 'video,audio,playlist';
149 var j_selector = '#' + force_id;
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 ) );
160 $j( document.getElementsByTagName( jtags[i] ) ).each( function() {
161 _this.doElementSwap( this );
166 $j( j_selector ).each( function() {
167 _this.doElementSwap( this );
170 // Check clips ready to call swap_callback now in ( mvEmbed.flist )
171 this.checkClipsReady();
173 doElementSwap: function( element ) {
174 if ( $j( element ).attr( "id" ) == '' ) {
175 $j( element ).attr( "id", 'v' + mw.player_list.length );
177 js_log( "mvEmbed::rewrite:: " + $j( element ).attr( "id" ) + ' tag: ' + element.tagName.toLowerCase() );
179 // Store a global reference to the id
180 mw.player_list.push( $j( element ).attr( "id" ) );
182 switch( element.tagName.toLowerCase() ) {
185 var videoInterface = new embedVideo( element );
186 mvEmbed.swapEmbedVideoElement( element, videoInterface );
189 // Make sure we have the necessary play-list libs loaded:
192 '$j.ui', // Include dialog for pop-ing up things
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>' );
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
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'] );
229 embed_video.setAttribute( "class", videoInterface['class'] );
232 embed_video[method] = videoInterface[method];
235 // string -> boolean:
236 if ( embed_video[method] == "false" )
237 embed_video[method] = false;
239 if ( embed_video[method] == "true" )
240 embed_video[method] = true;
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();
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();
253 js_log( 'done with child: ' + embed_video.id + ' len:' + mw.player_list.length );
257 * Check if Clips in the player_list are ready and runs
258 * any associated queued functions
260 checkClipsReady : function() {
261 // js_log('checkClipsReady');
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 );
267 is_ready = ( cur_vid.ready_to_play ) ? is_ready : false;
269 if ( !is_ready && cur_vid.load_error ) {
271 // Update the video with its load error:
272 $j( cur_vid ).html( cur_vid.load_error );
277 mvEmbed.allClipsReady = true;
278 // run queued functions
279 // js_log('run queded functions:' + mvEmbed.flist[0]);
282 setTimeout( 'mvEmbed.checkClipsReady()', 25 );
285 runFlist:function() {
286 while ( this.flist.length ) {
287 this.flist.shift()();
293 * mediaSource class represents a source for a media element.
294 * @param {Element} element: MIME type of the source.
297 function mediaSource( element ) {
298 this.init( element );
301 mediaSource.prototype = {
302 /** MIME type of the source. */
304 /** URI of the source. */
306 /** Title of the source. */
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 */
314 /** Duration of the requested segment (0 if not known) */
317 upddate_interval:null,
323 * MediaSource constructor:
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;
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;
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 );
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' );
352 this.mime_type = this.detectType( this.src );
354 this.parseURLDuration();
357 * Update Source title via Element
360 updateSource:function( element ) {
361 // for now just update the title:
362 if ( $j( element ).attr( "title" ) )
363 this.title = $j( element ).attr( "title" );
366 * Updates the src time and start & end
367 * @param {String} start_time: in NTP format
368 * @param {String} end_time: in NTP format
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;
379 if ( !npt2seconds( end_ntp ) )
380 end_ntp = this.end_ntp;
382 this.src = getURLParamReplace( this.src, {
383 't': start_ntp + '/' + end_ntp
386 // update the duration
387 this.parseURLDuration();
391 * Sets the duration and sets the end time if unset
392 * @param {Float} duration: in seconds
394 setDuration:function ( duration ) {
395 this.duration = duration;
396 if ( !this.end_ntp ) {
397 this.end_ntp = seconds2npt( this.start_offset + duration );
401 * MIME type accessor function.
402 * @return {String} the MIME type of the source.
404 getMIMEType : function() {
405 return this.mime_type;
408 * @param {Number} seek_time_sec Int: Used to adjust the URI for url based seeks)
409 * @return {String} the URI of the source.
411 getURI : function( seek_time_sec ) {
412 if ( !seek_time_sec || !this.URLTimeEncoding ) {
415 if ( !this.end_ntp ) {
418 var endvar = '/' + this.end_ntp;
420 return getURLParamReplace( this.src,
422 't': seconds2npt( seek_time_sec ) + endvar
426 /** Title accessor function.
427 @return the title of the source.
430 getTitle : function() {
434 // Return a title based on mime type:
435 switch( this.mime_type ){
437 return gM( 'mwe-video-h264' );
440 return gM( 'mwe-video-flv' );
443 return gM( 'mwe-video-ogg' );
446 return gM( 'mwe-video-audio' );
449 // Return the mime type string if not known type.
450 return this.mime_type;
452 /** Index accessor function.
453 @return the source's index within the enclosing mediaElement container.
456 getIndex : function() {
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
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;
476 // look for this info as attributes
477 if ( this.startOffset ) {
478 this.start_offset = this.startOffset;
479 this.start_ntp = seconds2npt( this.startOffset );
481 if ( this.duration ) {
482 this.end_ntp = seconds2npt( parseInt( this.duration ) + parseInt( this.start_offset ) );
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.
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() ) {
504 return 'video/x-flv';
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.
529 function mediaElement( element )
531 this.init( element );
534 mediaElement.prototype = {
535 /** The array of mediaSource elements. */
538 /** Selected mediaSource element. */
539 selected_source:null,
544 init:function( video_element )
547 js_log( 'Initializing mediaElement...' );
548 this.sources = new Array();
549 this.thumbnail = mv_default_thumb_url;
551 if ( $j( video_element ).attr( 'thumbnail' ) )
552 this.thumbnail = $j( video_element ).attr( 'thumbnail' );
554 if ( $j( video_element ).attr( 'poster' ) )
555 this.thumbnail = $j( video_element ).attr( 'poster' );
557 if ( $j( video_element ).attr( 'wikiTitleKey' ) )
558 this.wikiTitleKey = $j( video_element ).attr( 'wikiTitleKey' );
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 );
566 // Process the video_element as a source element:
567 if ( $j( video_element ).attr( "src" ) )
568 this.tryAddSource( video_element );
570 // Process all inner <source> elements
571 $j( video_element ).find( 'source,text,itext' ).each( function( inx, inner_source ) {
572 _this.tryAddSource( inner_source );
576 * Updates the time request for all sources that have a standard time request argument (ie &t=start_time/end_time)
578 updateSourceTimes:function( start_ntp, end_ntp ) {
580 $j.each( this.sources, function( inx, mediaSource ) {
581 mediaSource.updateSrcTime( start_ntp, end_ntp );
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' )
593 /** Returns the array of mediaSources of this element.
594 * @return {Array} of mediaSource elements.
596 getSources:function( mime_filter )
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] );
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];
615 /** Selects a particular source for playback.
617 selectSource:function( index )
619 js_log( 'f:selectSource:' + index );
620 var playable_sources = this.getPlayableSources();
621 for ( var i = 0; i < playable_sources.length; i++ ) {
623 this.selected_source = playable_sources[i];
624 // Update the user selected format:
625 embedTypes.players.userSelectFormat( playable_sources[i].mime_type );
631 * Selects the default source via cookie preference, default marked, or by id order
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;
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];
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];
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];
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];
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];
687 // Select first source
688 if ( !this.selected_source )
690 js_log( 'set via first source:' + playable_sources[0] );
691 this.selected_source = playable_sources[0];
696 * Returns the thumbnail URL for the media element.
697 * @returns {String} thumbnail URL
699 getThumbnailURL:function()
701 return this.thumbnail;
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.
708 hasStreamOfMIMEType:function( mime_type )
710 for ( source in this.sources )
712 if ( this.sources[source].getMIMEType() == mime_type )
717 isPlayableType:function( mime_type )
719 if ( embedTypes.players.defaultPlayer( mime_type ) ) {
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.
730 tryAddSource:function( element )
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 );
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;
749 if ( !source.startOffset && this.startOffset )
750 source.startOffset = this.startOffset;
753 this.sources.push( source );
754 js_log( 'pushed source to stack' + source + 'sl:' + this.sources.length );
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] );
762 js_log( "type " + this.sources[i].mime_type + 'is not playable' );
765 return playable_sources;
767 /* Imports media sources from ROE data.
768 * @param roe_data ROE data.
770 addROE:function( roe_data ) {
771 js_log( 'f:addROE' );
772 this.addedROEData = true;
774 if ( typeof roe_data == 'string' )
776 var parser = new DOMParser();
777 js_log( 'ROE data:' + roe_data );
778 roe_data = parser.parseFromString( roe_data, "text/xml" );
781 $j.each( roe_data.getElementsByTagName( 'mediaSource' ), function( inx, source ) {
782 _this.tryAddSource( source );
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" );
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' );
799 js_log( 'ROE data empty.' );
805 /** base embedVideo object
806 @param element <video> tag used for initialization.
809 var embedVideo = function( element ) {
810 return this.init( element );
813 embedVideo.prototype = {
814 /** The mediaElement object containing all mediaSource objects */
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,
822 init_with_sources_loadedDone:false,
824 // for onClip done stuff:
825 anno_data_cache:null,
827 base_seeker_slider_offset:null,
828 onClipDone_disp:false,
830 // for seek thumb updates:
831 cur_thumb_seek_time:0,
832 thumb_seek_interval:null,
833 // set the default tag type to video:
835 // set the buffered percent:
837 // utility functions for property values:
838 hx : function ( s ) {
839 if ( typeof s != 'String' ) {
842 return s.replace( /&/g, '&' )
843 . replace( /</g, '<' )
844 . replace( />/g, '>' );
846 hq : function ( s ) {
847 return '"' + this.hx( s ) + '"';
849 playerPixelWidth : function()
851 var player = $j( '#mv_embedded_player_' + this.id ).get( 0 );
852 if ( typeof player != 'undefined' && player['offsetWidth'] )
853 return player.offsetWidth;
855 return parseInt( this.width );
857 playerPixelHeight : function()
859 var player = $j( '#mv_embedded_player_' + this.id ).get( 0 );
860 if ( typeof player != 'undefined' && player['offsetHeight'] )
861 return player.offsetHeight;
863 return parseInt( this.height );
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 );
871 this[attr] = default_video_attributes[attr];
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 ];
884 // Set the default if unset:
885 if ( !this.skin_name )
886 this.skin_name = mw.conf.skin_name;
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 );
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 );
901 this.setDimSize( element, 'width' );
902 this.setDimSize( element, 'height' );
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;
913 // Load all of the specified sources
914 this.media_element = new mediaElement( element );
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
924 loadExternalCss( mv_embed_path + 'skins/' + this.skin_name + '/playerSkin.css' );
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;
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];
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;
947 do_request( this.roe, function( data ){
949 _this.media_element.addROE( data );
950 js_log( 'added_roe::' + _this.media_element.sources.length );
952 js_log( 'set loading_external_data=false' );
953 _this.loading_external_data = false;
955 _this.init_with_sources_loaded();
959 init_with_sources_loaded : function()
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 )
969 // check for parent clip:
970 if ( typeof this.pc != 'undefined' ) {
971 js_log( 'no sources, type:' + this.type + ' check for html' );
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 );
980 this.selected_player = embedTypes.players.defaultPlayer( this.media_element.selected_source.mime_type );
982 if ( this.selected_player ) {
983 js_log( "Playback type: " + this.selected_player.library );
984 this.thumbnail_disp = true;
985 this.inheritEmbedObj();
987 // No source's playable
988 var missing_type = '';
990 for ( var i = 0; i < this.media_element.sources.length; i++ ) {
991 missing_type += or + this.media_element.sources[i].mime_type;
994 // Get from parent playlist if set:
996 var missing_type = this.pc.type;
998 js_log( 'No player found for given source type ' + missing_type );
999 this.load_error = this.getPluginMissingHTML( missing_type );
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];
1016 // set up the new embedObj
1017 js_log( 'f: inheritEmbedObj: embedding with ' + this.selected_player.library );
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];
1028 if ( _this.inheritEmbedOverride ) {
1029 _this.inheritEmbedOverride();
1031 // Update controls if possible
1032 if ( !_this.loading_external_data )
1033 _this.refreshControlsHTML();
1035 _this.ready_to_play = true;
1036 _this.getDuration();
1040 selectPlayer:function( player )
1043 if ( this.selected_player.id != player.id ) {
1044 this.selected_player = player;
1045 this.inheritEmbedObj();
1048 doNativeWarningCheck:function() {
1049 if ( $j.cookie( 'dismissNativeWarn' ) && $j.cookie( 'dismissNativeWarn' ) === true ) {
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' ) {
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' ) {
1070 // no ogg src... no point in download firefox link
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;
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;
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;
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)
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;">' +
1118 getEmbedHTML : function() {
1119 // Return this.wrapEmebedContainer( this.getEmbedObj() );
1120 return 'Error: function getEmbedHTML should be overwritten by embedLib ';
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 ) {
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 ) );
1131 this.didSeekJump = true;
1132 // update the slider
1133 this.setSliderValue( perc );
1135 // do play in 100ms (give things time to clear)
1136 setTimeout( function(){
1141 * seeks to the requested time and issues a callback when ready
1142 * (should be overwritten by client that supports frame serving)
1144 setCurrentTime:function( time, callback ) {
1145 js_log( 'Error: base embed setCurrentTime can not frame serve (override via plugin)' );
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 );
1155 doEmbedHTML:function() {
1156 js_log( 'f:doEmbedHTML' );
1157 js_log( 'thum disp:' + this.thumbnail_disp );
1159 this.closeDisplayedHTML();
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' ) +
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 );
1175 relatedTitleKeySearch:function() {
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'
1183 var req_categories = new Array();
1185 'url' : mw.commons_api_url,
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 );
1194 _this.getRelatedFromCat( req_categories );
1196 _this.doThumbnailHTML();
1200 getRelatedFromCat:function( catAry ) {
1201 js_log( 'getRelatedFromCat' );
1203 for ( var i = 0 ; i <= catAry.length ; i++ ) {
1208 'generator' : 'categorymembers' ,
1209 'gcmtitle' : catAry[i],
1210 'prop' : 'imageinfo',
1216 'url': mw.commons_api_url
1217 }, function( data ) {
1218 // empty the videos:
1219 $j( '#dc_' + _this.id + ' .related_vids ul' ).html( ' ' );
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;
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 + '">' +
1238 ' <a title="' + title_str + '" target="_blank" ' +
1239 'href="' + descriptionurl + '">' + title_str + '</a>' +
1241 $j( '#dc_' + _this.id + ' .related_vids ul' ).append( liout ) ;
1245 } ); // end do_api_req
1248 onClipDone:function() {
1249 js_log( 'base:onClipDone' );
1250 // stop the clip (load the thumbnail etc)
1252 this.seek_time_sec = 0;
1253 this.setSliderValue( 0 );
1256 if ( this.width < 300 ) {
1259 this.onClipDone_disp = true;
1260 this.thumbnail_disp = true;
1262 // make sure we are not in preview mode( no end clip actions in preview mode)
1263 if ( this.preview_mode )
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;">' +
1276 if ( this.wikiTitleKey ) {
1277 $j( '#dc_' + this.id ).append(
1278 '<div class="related_vids" >' +
1279 '<h1>' + gM( 'mwe-related_videos' ) + '</h1>' +
1283 $j( '#img_thumb_' + this.id ).fadeOut( "fast" );
1284 $j( '#dc_' + _this.id + ' .related_vids ul' ).html( gM( 'mwe-loading_txt' ) );
1285 this.relatedTitleKeySearch();
1287 this.onClipDoneDisp();
1290 onClipDoneDisp:function() {
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;">' +
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 ),
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' ) );
1316 // now load roe if run the showNextPrevLinks
1317 if ( this.roe && this.media_element.addedROEData == false ) {
1318 do_request( this.roe, function( data )
1320 _this.media_element.addROE( data );
1321 _this.getNextPrevLinks();
1324 this.getNextPrevLinks();
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;
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;
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();
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';
1360 var etime_parts = times[1].split( ':' );
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 + '&';
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();
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" );
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:', '' )
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" );
1398 _this.showNextPrevLinks();
1401 // query current request time +|- 60s to get prev next speech links.
1403 showNextPrevLinks:function() {
1404 // js_log('f:showNextPrevLinks');
1405 // int requested links:
1411 var curTime = this.getTimeReq().split( '/' );
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' );
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' );
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 );
1442 if ( link.prev == '' && link.current == '' && link.next == '' ) {
1443 html = '<p><a href="' + this.media_element.linkbackgetMsg + '">clip page</a>';
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];
1450 for ( var j in clip['meta'] ) {
1451 title_msg += j.replace( /_/g, ' ' ) + ': ' + clip['meta'][j].replace( /_/g, ' ' ) + " <br>";
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 );
1457 // do special linkbacks for metavid content:
1458 var regTimeCheck = new RegExp( /[0-9]+:[0-9]+:[0-9]+\/[0-9]+:[0-9]+:[0-9]+/ );
1460 if ( regTimeCheck.test( this.media_element.linkback ) ) {
1461 html += ' href="' + this.media_element.linkback.replace( regTimeCheck, time_req ) + '" ';
1463 html += ' href="#" onClick="$j(\'#' + this.id + '\').get(0).playByTimeReq(\'' +
1464 time_req + '\'); return false; "';
1466 html += ' title="' + title_msg + '">' +
1467 gM( 'mwe-' + link_type + '_clip_msg' ) +
1468 '</a><br><span style="font-size:small">' + title_msg + '<span></p>';
1472 // js_og("should set html:"+ html);
1473 $j( '#liks_info_' + this.id ).html( html );
1475 playByTimeReq: function( time_req ) {
1476 js_log( 'f:playByTimeReq: ' + time_req );
1478 this.updateVideoTimeReq( time_req );
1481 doThumbnailHTML:function() {
1483 js_log( 'f:doThumbnailHTML' + this.thumbnail_disp );
1484 this.closeDisplayedHTML();
1485 $j( '#mv_embedded_player_' + this.id ).html( this.getThumbnailHTML() );
1487 this.thumbnail_disp = true;
1488 // make sure the ctrlBuilder remain active:
1489 this.ctrlBuilder.addControlHooks();
1491 refreshControlsHTML:function() {
1492 if ( $j( '#' + this.id + ' .control-bar' ).length == 0 ) {
1493 js_log( 'refreshControlsHTML::control-bar not present, no refresh' );
1496 // Do update controls:
1497 $j( '#' + this.id + ' .control-bar' ).html( this.getControlsHTML() );
1498 this.ctrlBuilder.addControlHooks();
1501 getControlsHTML:function() {
1502 return this.ctrlBuilder.getControls( this );
1504 getHTML : function () {
1505 // @@todo check if we have sources available
1506 js_log( 'embedVideo:getHTML : ' + this.id + ' resource type: ' + this.type );
1508 // set-up the local ctrlBuilder instance:
1509 this.ctrlBuilder = new ctrlBuilder( this );
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() +
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>' );
1530 html_code += '</div>'; // videoPlayer div close
1532 // js_log('should set: '+this.id);
1533 $j( this ).html( html_code );
1535 // Add hooks once Controls are in DOM
1536 this.ctrlBuilder.addControlHooks();
1539 if ( this.autoplay ) {
1540 js_log( 'getHTML::activating autoplay' );
1545 * get missing plugin html (check for user included code)
1546 * @param {String} misssing_type missing type mime
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;
1554 if ( !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>';
1558 return out + '</div>';
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] );
1565 // update video time
1566 updateVideoTime:function( start_ntp, end_ntp ) {
1568 this.media_element.updateSourceTimes( start_ntp, end_ntp );
1570 this.setStatus( start_ntp + '/' + end_ntp );
1572 this.setSliderValue( 0 );
1573 // reset seek_offset:
1574 if ( this.media_element.selected_source.URLTimeEncoding )
1575 this.seek_time_sec = 0;
1577 this.seek_time_sec = npt2seconds( start_ntp );
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, {
1587 'size': options.size
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">' +
1599 updateThumbTimeNTP:function( time ) {
1600 this.updateThumbTime( npt2seconds( time ) - parseInt( this.start_offset ) );
1602 updateThumbTime:function( float_sec ) {
1603 // js_log('updateThumbTime:'+float_sec);
1605 if ( typeof this.org_thum_src == 'undefined' ) {
1606 this.org_thum_src = this.media_element.getThumbnailURL();
1608 if ( this.org_thum_src.indexOf( 't=' ) !== -1 ) {
1609 this.last_thumb_url = getURLParamReplace( this.org_thum_src,
1611 't' : seconds2npt( float_sec + parseInt( this.start_offset ) )
1614 if ( !this.thumbnail_updating ) {
1615 this.updateThumbnail( this.last_thumb_url , false );
1616 this.last_thumb_url = null;
1620 updateThumbPerc:function( perc ) {
1621 return this.updateThumbTime( ( this.getDuration() * perc ) );
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 )
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 )
1632 js_log( 'update thumb: ' + src );
1634 if ( quick_switch ) {
1635 $j( '#img_thumb_' + this.id ).attr( 'src', src );
1638 // if still animating remove new_img_thumb_
1639 if ( this.thumbnail_updating == true )
1640 $j( '#new_img_thumb_' + this.id ).stop().remove();
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 );
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.
1674 getThumbnailHTML : function () {
1675 js_log( 'embedVideo:getThumbnailHTML::' + this.id );
1676 var thumb_html = '';
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 + '">';
1690 if ( this.play_button == true && this.controls == true )
1691 thumb_html += this.ctrlBuilder.getComponent( 'play-btn-large' );
1693 thumb_html += '</div>';
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);
1705 embed_thumb_url = ( thumbnail.indexOf( 'http://' ) != -1 ) ? thumbnail:mv_embed_path + thumbnail;
1707 var embed_code_html = '<script type="text/javascript" ' +
1708 'src="' + mv_embed_path + 'mv_embed.js"></script>' +
1711 embed_code_html += 'roe="' + escape( this.roe ) + '" ';
1713 embed_code_html += 'src="' + this.src + '" ' +
1714 'poster="' + escape( embed_thumb_url ) + '" ';
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="' + escape( this.wikiTitleKey ) + '"';
1724 // close the video tag
1725 embed_code_html += '></video>';
1727 return embed_code_html;
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();
1738 doLinkBack:function() {
1739 if ( this.roe && this.media_element.addedROEData == false ) {
1741 this.displayHTML( gM( 'mwe-loading_txt' ) );
1742 do_request( this.roe, function( data ) {
1743 _this.media_element.addROE( data );
1747 if ( this.linkback ) {
1748 window.location = this.linkback;
1749 } else if ( this.media_element.linkback ) {
1750 window.location = this.media_element.linkback;
1752 this.displayHTML( gM( 'mwe-could_not_find_linkback' ) );
1756 showShare:function( $target ) {
1757 var embed_code = this.getEmbeddingHTML();
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.
1763 o += '<h2>' + gM( 'mwe-share_this_video' ) + '</h2>' +
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>';
1769 '<div class="source_wrap">'+
1770 '<textarea>' + embed_code + '</textarea>'+
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' ) +
1777 $cpBtn = $j( '#' + this.id + ' .copycode' );
1778 $cpTxt = $j( '#' + this.id + ' .source_wrap textarea' );
1780 $cpTxt.click( function() {
1781 $j( this ).get( 0 ).select();
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" );
1793 showTextInterface:function() {
1795 if( $j( '#metaBox_' + this.id ).is( ':visible' ) ) {
1796 $j( '#metaBox_' + this.id ).fadeOut("fast");
1798 $j( '#metaBox_' + this.id ).fadeIn( "fast" );
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;' +
1810 'id="metaBox_' + this.id + '">' +
1811 mv_get_loading_img() +
1814 // check if textObj present:
1815 if ( typeof this.textInterface == 'undefined' ) {
1816 // load the default text interface:
1817 mvJsLoader.doLoad( [
1821 _this.textInterface = new mvTextInterface( _this );
1823 _this.textInterface.show();
1828 this.textInterface.show();
1831 closeTextInterface:function() {
1832 js_log( 'closeTextInterface ' + typeof this.textInterface );
1833 if ( typeof this.textInterface !== 'undefined' ) {
1834 this.textInterface.close();
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.
1843 displayHTML:function( html_code )
1845 var sel_id = ( this.pc != null ) ? this.pc.pp.id:this.id;
1847 if ( !this.supports['overlays'] )
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();
1858 height += ( this.pc.pp.pl_layout.title_bar_height + this.pc.pp.pl_layout.control_height );
1861 if ( $j( '#blackbg_' + sel_id ).length != 0 )
1864 $j( '#blackbg_' + sel_id ).remove();
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>' +
1872 '<div class="videoOptionsComplete">' +
1875 $j( '#' + sel_id ).prepend( div_code );
1877 $j( '#blackbg_' + sel_id ).fadeIn( "slow" );
1879 $j( '#blackbg_' + sel_id ).show();
1880 return false; // onclick action return false
1883 * Close the custom HTML displayed using displayHTML and restores the
1884 * regular mv_embed display.
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();
1891 return false; // onclick action return false
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;
1898 o += '<h2>' + gM( 'mwe-chose_player' ) + '</h2>';
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 ;
1906 o += '<h2>' + source.getTitle() + '</h2>';
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 ) {
1916 '<a href="#" class="active" rel="sel_source" id="sc_' + source_id + '_' + supporting_players[i].id + '">' +
1917 supporting_players[i].getName() +
1921 '<a href="#" rel="sel_source" id="sc_' + source_id + '_' + supporting_players[i].id + '">' +
1922 supporting_players[i].getName() +
1929 o += source.getTitle() + ' - no player available';
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() );
1949 $j( '#' + this_id ).get( 0 ).stop();
1951 // Don't follow the empty # link:
1956 showDownload:function( $target ) {
1958 // Load the roe if available (to populate out download options:
1959 function getShowVideoDownload() {
1960 var out = '<div style="color:white">';
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 ) {
1968 } else if ( this.getMIMEType() == "text/cmml" || this.getMIMEType() == "text/x-srt" ) {
1969 dl_txt_list += dl_line;
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>';
1982 // js_log('f:showDownload '+ this.roe + ' ' + this.media_element.addedROEData);
1983 if ( this.roe && this.media_element.addedROEData == false ) {
1985 $target.html( gM( 'loading_txt' ) );
1986 do_request( this.roe, function( data ) {
1987 _this.media_element.addROE( data );
1988 $target.html( getShowVideoDownload() );
1991 $target.html( getShowVideoDownload() );
1995 * Base embed controls
1999 var eid = ( this.pc != null ) ? this.pc.pp.id:this.id;
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() );
2009 this.onClipDone_disp = false;
2010 this.paused = false;
2011 this.thumbnail_disp = false;
2014 // the plugin is already being displayed
2015 this.paused = false; // make sure we are not "paused"
2016 this.seeking = false;
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' ) );
2026 // should be done by child (no base way to pre-buffer video)
2027 js_log( 'baseEmbed:load call' );
2030 return this.media_element.selected_source.getURI( this.seek_time_sec );
2034 * there is no general way to pause the video
2035 * must be overwritten by embed object to support this functionality.
2039 var eid = ( this.pc != null ) ? this.pc.pp.id:this.id;
2040 // js_log('mv_embed:do pause');
2041 // (playing) do pause
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() {
2048 } ).attr( 'title', gM( 'mwe-play_clip' ) );
2051 * Base embed stop (can be overwritten by the plugin)
2055 js_log( 'mvEmbed:stop:' + this.id );
2057 // no longer seeking:
2058 this.didSeekJump = false;
2060 // first issue pause to update interface (only call the parent)
2061 if ( this['parent_pause'] ) {
2062 this.parent_pause();
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' );
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() );
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();
2086 if ( this.update_interval )
2088 clearInterval( this.update_interval );
2089 this.update_interval = null;
2092 toggleMute:function() {
2093 var eid = ( this.pc != null ) ? this.pc.pp.id:this.id;
2096 $j( '#' + eid + ' .volume-slider' ).slider( 'value', 100 );
2097 this.updateVolumen( 1 );
2100 $j( '#' + eid + ' .volume-slider' ).slider( 'value', 0 );
2101 this.updateVolumen( 0 );
2103 js_log( 'f:toggleMute::' + this.muted );
2105 updateVolumen:function( perc ) {
2106 js_log( 'update volume not supported with current playback type' );
2108 fullscreen:function() {
2109 js_log( 'fullscreen not supported with current playback type' );
2112 * returns bool true if playing or paused, false if stooped
2114 isPlaying : function() {
2115 if ( this.thumbnail_disp ) {
2118 } else if ( this.paused ) {
2125 isPaused : function() {
2126 return this.isPlaying() && this.paused;
2128 isStoped : function() {
2129 return this.thumbnail_disp;
2131 playlistSupport:function() {
2132 // by default not supported (implemented in js)
2135 postEmbedJS:function() {
2139 * Monitor playback and update interface components.
2140 * underling plugin objects are responsible for updating currentTime
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 );
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 );
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 );
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 ) + ' /' );
2174 this.setStatus( " - - - " );
2176 this.setStatus( this.getTimeReq() );
2179 // Could check if time > duration here and stop playback
2181 // Update buffer information
2182 this.updateBufferStatus();
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();
2196 stopMonitor:function() {
2197 if ( this.monitorTimerId != 0 )
2199 clearInterval( this.monitorTimerId );
2200 this.monitorTimerId = 0;
2203 updateBufferStatus: function() {
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';
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;
2216 $j( buffer_select ).css( "width", ( this.bufferedPercent * 100 ) + '%' );
2218 $j( buffer_select ).css( "width", '0px' );
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.
2229 getPluginEmbed : function() {
2230 if ( window.document[this.pid] ) {
2231 return window.document[this.pid];
2233 if ( $j.browser.msie ) {
2234 return document.getElementById( this.pid );
2236 if ( document.embeds && document.embeds[this.pid] )
2237 return document.embeds[this.pid];
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 );
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() + ')');
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
2261 var slider_perc = 0;
2262 if ( rel_start_sec <= 0 ) {
2264 options['start'] = seconds2npt( this.start_offset );
2266 this.setSliderValue( 0 , hide_progress );
2268 left_perc = parseInt( ( rel_start_sec / dur ) * 100 ) ;
2269 slider_perc = ( left_perc / 100 );
2272 js_log( "slider perc:" + slider_perc );
2273 if ( ! this.isPlaying() ) {
2274 this.setSliderValue( slider_perc , hide_progress );
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;
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 + '%'
2287 this.jump_time = options['start'];
2288 this.seek_time_sec = npt2seconds( options['start'] );
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 );
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 );
2300 setStatus:function( value ) {
2301 var eid = ( this.pc ) ? this.pc.pp.id:this.id;
2303 $j( '#' + eid + ' .time-disp' ).html( value );
2306 * Helper Functions for selected source
2309 * returns the selected source url for players to play
2311 getURI : function( seek_time_sec ) {
2312 return this.media_element.selected_source.getURI( this.seek_time_sec );
2314 supportsURLTimeEncoding: function() {
2315 // do head request if on the same domain
2316 return this.media_element.selected_source.URLTimeEncoding;
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)
2329 function mediaPlayer( id, supported_types, library )
2332 this.supported_types = supported_types;
2333 this.library = library;
2334 this.loaded = false;
2335 this.loading_callbacks = new Array();
2338 mediaPlayer.prototype =
2341 supported_types:null,
2344 loading_callbacks:null,
2345 supportsMIMEType : function( type )
2347 for ( var i = 0; i < this.supported_types.length; i++ )
2348 if ( this.supported_types[i] == type )
2352 getName : function()
2354 return gM( 'mwe-ogg-player-' + this.id );
2356 load : function( callback ) {
2357 mvJsLoader.doLoad( [
2358 this.library + 'Embed'
2360 js_log("wtf: " + typeof( vlcEmbed ) );
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' );
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.
2393 function mediaPlayers()
2398 mediaPlayers.prototype =
2402 default_players : { },
2404 this.players = new Array();
2405 this.loadPreferences();
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'];
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'];
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'];
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 )
2433 this.players[i].supported_types.push( mime_type );
2438 // Player not found:
2439 if ( mime_type != null )
2440 player.supported_types.push( mime_type );
2442 js_log( 'Adding ' + player.id + ' with mime_type ' + mime_type );
2443 this.players.push( player );
2445 getMIMETypePlayers : function( mime_type ) {
2446 var mime_players = new Array();
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];
2460 return mime_players;
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 )
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];
2472 // Otherwise just return the first compatible player
2473 // (it will be chosen according to the default_players list
2474 return mime_players[0];
2476 js_log( 'No default player found for ' + mime_type );
2479 userSelectFormat : function ( mime_format ) {
2480 this.preference['format_prefrence'] = mime_format;
2481 this.savePreferences();
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();
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 ) )
2499 embed.selectPlayer( selected_player );
2500 js_log( 'using ' + embed.selected_player.getName() + ' for ' + embed.media_element.selected_source.getTitle() );
2505 loadPreferences : function()
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' );
2511 var pairs = cookieVal.split( '&' );
2512 for ( var i = 0; i < pairs.length; i++ )
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]);
2520 savePreferences : function() {
2522 for ( var i in this.preference )
2523 cookieVal += i + '=' + this.preference[i] + '&';
2525 cookieVal = cookieVal.substr( 0, cookieVal.length -1 );
2526 var week = 7 * 86400 * 1000;
2527 $j.cookie( 'ogg_player_exp', cookieVal, { 'expires':week } );
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
2541 // detect supported types
2543 this.detect_done = true;
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" )
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.
2568 this.players.addPlayer( cortadoPlayer );
2571 if ( $j.browser.msie ) {
2573 if ( this.testActiveX( 'ShockwaveFlash.ShockwaveFlash' ) ) {
2574 // try to get the flash version for omtk include:
2576 a = new ActiveXObject( SHOCKWAVE_FLASH_AX + ".7" );
2577 d = a.GetVariable( "$version" ); // Will crash fp6.0.21/23/29
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 );
2586 // failed to check for flash
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 );
2595 if ( this.testActiveX( 'VideoLAN.VLCPlugin.2' ) )
2596 this.players.addPlayer( vlcPlayer );
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);
2606 if ( typeof HTMLVideoElement == 'object' // Firefox, Safari
2607 || typeof HTMLVideoElement == 'function' ) // Opera
2609 // do another test for safari:
2610 if ( $j.browser.safari ) {
2612 var dummyvid = document.createElement( "video" );
2613 if ( dummyvid.canPlayType && dummyvid.canPlayType( "video/ogg;codecs=\"theora,vorbis\"" ) == "probably" )
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 );
2621 // @@todo add some user nagging to install the xiph qt
2624 js_log( 'could not run canPlayType in safari' );
2627 this.players.addPlayer( videoElementPlayer );
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 );
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
2645 if ( pluginName.toLowerCase() == 'vlc multimedia plugin' || pluginName.toLowerCase() == 'vlc multimedia plug-in' ) {
2646 this.players.addPlayer( vlcPlayer, type );
2650 if ( type == 'application/x-java-applet' ) {
2651 this.players.addPlayer( cortadoPlayer );
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);
2661 this.players.addPlayer( oggPluginPlayer );
2664 } else if ( uniqueMimesOnly ) {
2665 if ( type == 'application/x-vlc-player' ) {
2666 this.players.addPlayer( vlcMozillaPlayer, type );
2668 } else if ( type == 'video/quicktime' ) {
2669 // this.players.addPlayer(quicktimeMozillaPlayer);
2674 /*if ( type == 'video/quicktime' ) {
2675 this.players.addPlayer(vlcMozillaPlayer, type);
2678 if ( type == 'application/x-shockwave-flash' ) {
2680 this.players.addPlayer( kplayer );
2681 //this.players.addPlayer( flowPlayer );
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 );
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);
2701 testActiveX : function ( name ) {
2704 // No IE, not a class called "name", it's a variable
2705 var obj = new ActiveXObject( '' + name );