Index: trunk/extensions/MwEmbedSupport/MwEmbedModules/MediaWikiSupport/resources/MediaWikiPlayerSupport.js |
— | — | @@ -204,6 +204,12 @@ |
205 | 205 | callback(); |
206 | 206 | } |
207 | 207 | }); |
| 208 | + |
| 209 | + $( embedPlayer ).bind('GetShareIframeCode', function(event, callback){ |
| 210 | + if( data-mwprovider ) |
| 211 | + // Check the embedPlayer title key: |
| 212 | + iframeSrc = $( embedPlayer).attr( 'data-mwtitle') |
| 213 | + }); |
208 | 214 | }; |
209 | 215 | |
210 | 216 | } )( window.mediaWiki, window.jQuery ); |
\ No newline at end of file |
Index: trunk/extensions/MwEmbedSupport/MwEmbedModules/MwEmbedSupport/jquery.menu/jquery.menu.js |
— | — | @@ -44,7 +44,7 @@ |
45 | 45 | ); |
46 | 46 | } |
47 | 47 | $li.find( 'a' ).append( $('<span>').text( string ) ); |
48 | | - //mw.log(' li html: ' + $('<div>').append( $li ).html() ); |
| 48 | +// mw.log(string + "\n" + ' li html: ' + $('<div>').append( $li ).html() ); |
49 | 49 | return $li; |
50 | 50 | }; |
51 | 51 | |
— | — | @@ -127,6 +127,7 @@ |
128 | 128 | }, |
129 | 129 | showSpeed: 200, // show/hide speed in milliseconds |
130 | 130 | createMenuCallback: null, |
| 131 | + closeMenuCallback: null, |
131 | 132 | callerOnState: 'ui-state-active', // class to change the appearance of the link/button when the menu is showing |
132 | 133 | loadingState: 'ui-state-loading', // class added to the link/button while the menu is created |
133 | 134 | linkHover: 'ui-state-hover', // class for menu option hover state |
— | — | @@ -173,6 +174,10 @@ |
174 | 175 | container.hide(); |
175 | 176 | } |
176 | 177 | menu.menuOpen = false; |
| 178 | + if( typeof options.closeMenuCallback == 'function'){ |
| 179 | + options.closeMenuCallback(); |
| 180 | + } |
| 181 | + |
177 | 182 | $(document).unbind('click', killAllMenus); |
178 | 183 | $(document).unbind('keydown'); |
179 | 184 | }; |
— | — | @@ -382,7 +387,6 @@ |
383 | 388 | var allSubLists = $(this).find('ul'); |
384 | 389 | |
385 | 390 | allSubLists.css({ left: linkWidth, width: linkWidth }).hide(); |
386 | | - |
387 | 391 | $(this).find('a:eq(0)').addClass('fg-menu-indicator').html( |
388 | 392 | '<span>' + $(this).find('a:eq(0)').html() + |
389 | 393 | '</span><span class="ui-icon '+options.nextMenuLink+'"></span>') |
— | — | @@ -479,11 +483,18 @@ |
480 | 484 | checkMenuHeight(topList); |
481 | 485 | |
482 | 486 | topList.find('a').each(function() { |
483 | | - // if the link opens a child menu: |
| 487 | + |
484 | 488 | if ($(this).next().is('ul')) { |
485 | 489 | $(this) |
486 | 490 | .addClass('fg-menu-indicator') |
487 | | - .each(function() { $(this).html('<span>' + $(this).html() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>'); }) |
| 491 | + .each(function() { |
| 492 | + // if the link opens a child menu: |
| 493 | + if( !$(this).hasClass('fg-menu-link') ){ |
| 494 | + $(this) |
| 495 | + .addClass('fg-menu-link') |
| 496 | + .html( nextMenuLink = '<span>' + $(this).html() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>' ) |
| 497 | + } |
| 498 | + }) |
488 | 499 | .click(function() { // ----- show the next menu |
489 | 500 | var nextList = $(this).next(); |
490 | 501 | var parentUl = $(this).parents('ul:eq(0)'); |
Index: trunk/extensions/MwEmbedSupport/MwEmbedModules/MwEmbedSupport/MwEmbedSupport.php |
— | — | @@ -32,5 +32,10 @@ |
33 | 33 | 'styles' => 'jquery.menu/jquery.menu.css' |
34 | 34 | ), |
35 | 35 | "jquery.triggerQueueCallback" => array( 'scripts'=> "jquery/jquery.triggerQueueCallback.js" ), |
36 | | - "jquery.mwEmbedUtil" => array( 'scripts' => "jquery/jquery.mwEmbedUtil.js" ), |
| 36 | + "jquery.mwEmbedUtil" => array( |
| 37 | + 'scripts' => "jquery/jquery.mwEmbedUtil.js", |
| 38 | + 'dependencies' => array( |
| 39 | + 'jquery.ui.dialog' |
| 40 | + ) |
| 41 | + ), |
37 | 42 | ); |
Index: trunk/extensions/MwEmbedSupport/MwEmbedModules/MwEmbedSupport/mwEmbedSupport.js |
— | — | @@ -158,7 +158,8 @@ |
159 | 159 | } |
160 | 160 | }; |
161 | 161 | // Else use normal mediaWiki string based gM |
162 | | - return mediaWiki.msg( key, paramaters); |
| 162 | + |
| 163 | + return mediaWiki.msg.apply( this, $.makeArray( arguments ) ); |
163 | 164 | }; |
164 | 165 | |
165 | 166 | /** |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php |
— | — | @@ -100,6 +100,16 @@ |
101 | 101 | return $metadata['playtime_seconds']; |
102 | 102 | } |
103 | 103 | } |
| 104 | + function getFramerate( $file ){ |
| 105 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 106 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 107 | + return 0; |
| 108 | + } else { |
| 109 | + print_r($metadata); |
| 110 | + die(); |
| 111 | + return $metadata['framerate']; |
| 112 | + } |
| 113 | + } |
104 | 114 | |
105 | 115 | function getShortDesc( $file ) { |
106 | 116 | global $wgLang, $wgMediaAudioTypes, $wgMediaVideoTypes; |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/OggHandler.php |
— | — | @@ -109,7 +109,20 @@ |
110 | 110 | return $metadata['length']; |
111 | 111 | } |
112 | 112 | } |
113 | | - |
| 113 | + function getFramerate( $file ){ |
| 114 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 115 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 116 | + return 0; |
| 117 | + } else { |
| 118 | + // Return the first found theora stream framerate: |
| 119 | + foreach ( $metadata['streams'] as $stream ) { |
| 120 | + if( $stream['type'] == 'Theora' ){ |
| 121 | + return $stream['header']['FRN'] / $stream['header']['FRD']; |
| 122 | + } |
| 123 | + } |
| 124 | + return 0; |
| 125 | + } |
| 126 | + } |
114 | 127 | function getShortDesc( $file ) { |
115 | 128 | global $wgLang, $wgMediaAudioTypes, $wgMediaVideoTypes; |
116 | 129 | |
Index: trunk/extensions/TimedMediaHandler/TimedMediaTransformOutput.php |
— | — | @@ -52,7 +52,7 @@ |
53 | 53 | return $this->getXmlTagOutput( |
54 | 54 | $this->getMediaAttr(), |
55 | 55 | $this->getMediaSources(), |
56 | | - $this->getTextSources() |
| 56 | + $this->getLocalTextSources() |
57 | 57 | ); |
58 | 58 | } |
59 | 59 | // XXX migrate this to the mediawiki XML class as 'tagSet' helper function |
— | — | @@ -66,6 +66,7 @@ |
67 | 67 | } |
68 | 68 | return $s; |
69 | 69 | } |
| 70 | + |
70 | 71 | /** |
71 | 72 | * Call mediaWiki xml helper class to build media tag output from |
72 | 73 | * supplied arrays |
— | — | @@ -124,7 +125,7 @@ |
125 | 126 | ); |
126 | 127 | |
127 | 128 | // Add api provider: |
128 | | - if( $this->file->getRepoName() != 'local' ){ |
| 129 | + if( !$this->file->isLocal() ){ |
129 | 130 | // Set the api provider name to "commons" for shared ( instant commons convention ) |
130 | 131 | // ( provider names should have identified the provider |
131 | 132 | // instead of the provider type "shared" ) |
— | — | @@ -147,8 +148,15 @@ |
148 | 149 | return $this->sources; |
149 | 150 | } |
150 | 151 | |
151 | | - function getTextSources(){ |
| 152 | + function getLocalTextSources(){ |
152 | 153 | global $wgServer, $wgScriptPath; |
| 154 | + |
| 155 | + // Don't do lookup if non-local path: |
| 156 | + // TODO integrate with repo api and do remote lookup |
| 157 | + if( !$this->file->isLocal() ){ |
| 158 | + return array(); |
| 159 | + } |
| 160 | + |
153 | 161 | // Check local cache: |
154 | 162 | if( $this->textTracks ){ |
155 | 163 | return $this->textTracks; |
— | — | @@ -183,7 +191,7 @@ |
184 | 192 | if( !isset( $langNames[ $languageKey ] ) ){ |
185 | 193 | continue; |
186 | 194 | } |
187 | | - $this->textTracks[] = array( |
| 195 | + $this->textTracks[] = array( |
188 | 196 | 'kind' => 'subtitles', |
189 | 197 | 'data-mwtitle' => $subTitle->getNsText() . ':' . $subTitle->getDBkey(), |
190 | 198 | 'type' => 'text/x-srt', |
— | — | @@ -191,7 +199,7 @@ |
192 | 200 | // http://www.whatwg.org/specs/web-apps/current-work/webvtt.html |
193 | 201 | 'src' => $subTitle->getFullURL( array( |
194 | 202 | 'action' => 'raw', |
195 | | - 'ctype' => 'text/plain' |
| 203 | + 'ctype' => 'text/x-srt' |
196 | 204 | )), |
197 | 205 | 'srclang' => $languageKey, |
198 | 206 | 'label' => wfMsg('timedmedia-subtitle-language', |
Index: trunk/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php |
— | — | @@ -54,7 +54,7 @@ |
55 | 55 | 'videoBitrate' => '160', |
56 | 56 | 'audioBitrate' => '32', |
57 | 57 | 'samplerate' => '22050', |
58 | | - //'framerate' => '15', |
| 58 | + 'framerate' => '18', |
59 | 59 | 'channels' => '1', |
60 | 60 | 'noUpscaling' => 'true', |
61 | 61 | 'twopass' => 'true', |
— | — | @@ -221,21 +221,33 @@ |
222 | 222 | $thumbName = $file->thumbName( array() ); |
223 | 223 | $thumbUrl = $file->getThumbUrl( $thumbName ); |
224 | 224 | $thumbUrlDir = dirname( $thumbUrl ); |
225 | | - |
| 225 | + |
226 | 226 | // if the source size is < $transcodeKey assume source size: |
227 | 227 | if( is_file( $derivativeFile ) ){ |
| 228 | + // Estimate bandwith: |
| 229 | + $bandwith = intval( filesize( $derivativeFile ) / $file->getLength() ) * 8; |
| 230 | + |
| 231 | + list( $width, $height ) = WebVideoTranscode::getMaxSizeTransform( |
| 232 | + $file, |
| 233 | + self::$derivativeSettings[$transcodeKey]['maxSize'] |
| 234 | + ); |
| 235 | + |
| 236 | + $framerate = ( isset( self::$derivativeSettings[$transcodeKey]['framerate'] ) )? |
| 237 | + self::$derivativeSettings[$transcodeKey]['framerate'] : |
| 238 | + $file->getHandler()->getFramerate( $file ); |
228 | 239 | $sources[] = array( |
229 | 240 | 'src' => $thumbUrlDir . '/' .$file->getName() . '.' . $transcodeKey, |
230 | 241 | 'title' => wfMsg('timedmedia-derivative-desc-' . $transcodeKey ), |
231 | 242 | 'data-shorttitle' => wfMsg('timedmedia-derivative-' . $transcodeKey), |
232 | | - 'data-size' => implode( 'x', |
233 | | - WebVideoTranscode::getMaxSizeTransform( |
234 | | - $file, |
235 | | - self::$derivativeSettings[$transcodeKey]['maxSize'] |
236 | | - ) |
237 | | - ) |
| 243 | + |
| 244 | + // Add data attributes per emerging DASH / webTV adaptive streaming attributes |
| 245 | + // eventually we will define a manifest xml entry point. |
| 246 | + 'data-width' => $width, |
| 247 | + 'data-height' => $height, |
| 248 | + 'data-bandwith' => $bandwith, |
| 249 | + 'data-framerate' => $framerate, |
238 | 250 | ); |
239 | | - } else { |
| 251 | + } else { |
240 | 252 | self::updateJobQueue($file, $transcodeKey); |
241 | 253 | } |
242 | 254 | } |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/TimedText/resources/mw.TimedText.js |
— | — | @@ -361,7 +361,13 @@ |
362 | 362 | 'autoShow': autoShow, |
363 | 363 | 'targetMenuContainer' : _this.menuTarget, |
364 | 364 | 'positionOpts' : positionOpts, |
365 | | - 'backLinkText' : gM( 'mwe-timedtext-back-btn' ) |
| 365 | + 'backLinkText' : gM( 'mwe-timedtext-back-btn' ), |
| 366 | + 'createMenuCallback' : function(){ |
| 367 | + _this.embedPlayer.controlBuilder.showControlBar( true ); |
| 368 | + }, |
| 369 | + 'closeMenuCallback' : function(){ |
| 370 | + _this.embedPlayer.controlBuilder.hideControlBar( true ); |
| 371 | + } |
366 | 372 | } ); |
367 | 373 | }); |
368 | 374 | }, |
— | — | @@ -394,6 +400,7 @@ |
395 | 401 | this.textSources = [ ]; |
396 | 402 | // Get local reference to all timed text sources: ( text/xml, text/x-srt etc ) |
397 | 403 | var inlineSources = this.embedPlayer.mediaElement.getSources( 'text' ); |
| 404 | + |
398 | 405 | // Add all the sources to textSources |
399 | 406 | for( var i = 0 ; i < inlineSources.length ; i++ ) { |
400 | 407 | // Make a new textSource: |
— | — | @@ -401,9 +408,9 @@ |
402 | 409 | this.textSources.push( source ); |
403 | 410 | } |
404 | 411 | |
405 | | - // If there are no inline sources check & apiTitleKey |
406 | | - if( this.textSources.length == 0 || !this.embedPlayer.apiTitleKey ) { |
407 | | - //no other sources just issue the callback: |
| 412 | + // If there are inline sources or no apiTitleKey we are done loading |
| 413 | + if( this.textSources.length != 0 || !this.embedPlayer.apiTitleKey ) { |
| 414 | + // No other sources just issue the callback: |
408 | 415 | callback(); |
409 | 416 | return ; |
410 | 417 | } |
— | — | @@ -416,6 +423,7 @@ |
417 | 424 | mw.log("Error: loading source without apiProvider or apiTitleKey"); |
418 | 425 | return ; |
419 | 426 | } |
| 427 | + |
420 | 428 | //For now only support mediaWikTrack provider library |
421 | 429 | this.textProvider = new mw.MediaWikTrackProvider( { |
422 | 430 | 'provider_id' : provider_id, |
— | — | @@ -444,7 +452,7 @@ |
445 | 453 | |
446 | 454 | // Add a title |
447 | 455 | $( textElm ).attr('title', |
448 | | - gM('mwe-timedtext-key-language', [textSource.srclang, mw.Language.names[ textSource.srclang ] ] ) |
| 456 | + gM('mwe-timedtext-key-language', textSource.srclang, mw.Language.names[ textSource.srclang ] ) |
449 | 457 | ); |
450 | 458 | |
451 | 459 | // Add the sources to the parent embedPlayer |
— | — | @@ -794,13 +802,12 @@ |
795 | 803 | return $j.getLineItem( source.title, source_icon, function() { |
796 | 804 | _this.selectTextSource( source ); |
797 | 805 | }); |
798 | | - } |
799 | | - |
| 806 | + } |
800 | 807 | if( source.srclang ) { |
801 | 808 | var langKey = source.srclang.toLowerCase(); |
802 | | - _this.getLanguageName ( langKey ); |
| 809 | + var cat = gM('mwe-timedtext-key-language', langKey, _this.getLanguageName ( langKey ) ); |
803 | 810 | return $j.getLineItem( |
804 | | - gM('mwe-timedtext-key-language', [langKey, mw.Language.names[ source.srclang ] ] ), |
| 811 | + gM('mwe-timedtext-key-language', langKey, _this.getLanguageName ( langKey ) ), |
805 | 812 | source_icon, |
806 | 813 | function() { |
807 | 814 | _this.selectTextSource( source ); |
— | — | @@ -862,9 +869,6 @@ |
863 | 870 | // Update the config and redraw layout |
864 | 871 | _this.config.layout = layoutMode; |
865 | 872 | |
866 | | - // Update the user config: |
867 | | - mw.setUserConfig( 'timedTextConfig', _this.config); |
868 | | - |
869 | 873 | // Update the display: |
870 | 874 | _this.updateLayout(); |
871 | 875 | } |
— | — | @@ -1074,7 +1078,7 @@ |
1075 | 1079 | // Setup the display text div: |
1076 | 1080 | var layoutMode = this.getLayoutMode(); |
1077 | 1081 | if( layoutMode == 'ontop' ) { |
1078 | | - this.embedPlayer.controlBuilder.displayOptionsMenuFlag = false; |
| 1082 | + this.embedPlayer.controlBuilder.keepControlBarOnScreen = false; |
1079 | 1083 | var $track = $('<div>') |
1080 | 1084 | .addClass( 'track' + ' ' + 'track_' + category ) |
1081 | 1085 | .css( { |
— | — | @@ -1100,7 +1104,7 @@ |
1101 | 1105 | $playerTarget.append( $track ); |
1102 | 1106 | |
1103 | 1107 | } else if ( layoutMode == 'below') { |
1104 | | - this.embedPlayer.controlBuilder.displayOptionsMenuFlag = true; |
| 1108 | + this.embedPlayer.controlBuilder.keepControlBarOnScreen = true; |
1105 | 1109 | // Set the belowBar size to 60 pixels: |
1106 | 1110 | var belowBarHeight = 60; |
1107 | 1111 | // Append before controls: |
— | — | @@ -1191,7 +1195,7 @@ |
1192 | 1196 | } |
1193 | 1197 | }; |
1194 | 1198 | _this.loaded = true; |
1195 | | - |
| 1199 | + |
1196 | 1200 | // Set parser handler: |
1197 | 1201 | switch( this.getMIMEType() ) { |
1198 | 1202 | //Special mediaWiki srt format ( support wiki-text in srt's ) |
— | — | @@ -1212,6 +1216,7 @@ |
1213 | 1217 | mw.log("Error: no handler for type: " + this.getMIMEType() ); |
1214 | 1218 | return ; |
1215 | 1219 | } |
| 1220 | + |
1216 | 1221 | // Try to load src via textProvider: |
1217 | 1222 | if( this.textProvider && this.titleKey ) { |
1218 | 1223 | this.textProvider.loadTitleKey( this.titleKey, function( data ) { |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/TimedText/TimedText.php |
— | — | @@ -6,8 +6,9 @@ |
7 | 7 | 'scripts' => "resources/mw.TimedText.js", |
8 | 8 | 'styles' => "resources/mw.style.TimedText.css", |
9 | 9 | 'dependencies' => array( |
10 | | - 'mw.EmbedPlayer', |
11 | | - 'mw.Language.names' |
| 10 | + 'mw.EmbedPlayer', |
| 11 | + 'mw.Language.names', |
| 12 | + 'jquery.ui.dialog', |
12 | 13 | ), |
13 | 14 | 'messageFile' => 'TimedText.i18n.php', |
14 | 15 | ), |
— | — | @@ -15,8 +16,7 @@ |
16 | 17 | 'scripts' => "resources/mw.TimedTextEdit.js", |
17 | 18 | 'styles' => "resources/mw.style.TimedTextEdit.css", |
18 | 19 | 'dependencies' => array( |
19 | | - 'mw.TimedText', |
20 | | - 'jquery.ui.dialog', |
| 20 | + 'mw.TimedText', |
21 | 21 | 'jquery.ui.tabs' |
22 | 22 | ) |
23 | 23 | ), |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.php |
— | — | @@ -2,15 +2,33 @@ |
3 | 3 | |
4 | 4 | // Register all the EmbedPlayer modules |
5 | 5 | return array( |
| 6 | + "MediaElement" => array( 'scripts' => 'resources/MediaElement.js' ), |
| 7 | + "MediaPlayer" => array( 'scripts' => 'resources/MediaPlayer.js' ), |
| 8 | + "MediaPlayers" => array( |
| 9 | + 'scripts' => 'resources/MediaPlayers.js', |
| 10 | + 'dependencies' => 'MediaPlayer' |
| 11 | + ), |
| 12 | + "MediaSource" => array( 'scripts' => 'resources/MediaSource.js' ), |
| 13 | + "mw.EmbedTypes" => array( |
| 14 | + 'scripts' => 'resources/mw.EmbedTypes.js', |
| 15 | + 'dependencies' => 'MediaPlayers' |
| 16 | + ), |
| 17 | + |
6 | 18 | "mw.EmbedPlayer" => array( |
7 | 19 | 'scripts' => array( |
8 | | - "players/mw.EmbedPlayer.js", |
9 | | - "skins/mw.PlayerControlBuilder.js", |
| 20 | + "resources/mw.EmbedPlayer.js", |
| 21 | + "resources/skins/mw.PlayerControlBuilder.js", |
10 | 22 | ), |
11 | 23 | 'dependencies' => array( |
12 | | - // mwEmbed support: |
| 24 | + // mwEmbed support module |
13 | 25 | 'mwEmbedSupport', |
14 | 26 | |
| 27 | + // Sub classes: |
| 28 | + 'MediaElement', |
| 29 | + 'MediaPlayers', |
| 30 | + 'MediaSource', |
| 31 | + 'mw.EmbedTypes', |
| 32 | + |
15 | 33 | // jQuery dependencies: |
16 | 34 | 'jquery.hoverIntent', |
17 | 35 | 'jquery.cookie', |
— | — | @@ -18,24 +36,24 @@ |
19 | 37 | 'jquery.menu', |
20 | 38 | 'jquery.ui.slider' |
21 | 39 | ), |
22 | | - 'styles' => "skins/mw.style.EmbedPlayer.css", |
| 40 | + 'styles' => "resources/skins/mw.style.EmbedPlayer.css", |
23 | 41 | 'messageFile' => 'EmbedPlayer.i18n.php', |
24 | 42 | ), |
25 | 43 | |
26 | | - "mw.EmbedPlayerKplayer" => array( 'scripts'=> "players/mw.EmbedPlayerKplayer.js" ), |
27 | | - "mw.EmbedPlayerGeneric" => array( 'scripts'=> "players/mw.EmbedPlayerGeneric.js" ), |
28 | | - "mw.EmbedPlayerJava" => array( 'scripts'=> "players/mw.EmbedPlayerJava.js"), |
29 | | - "mw.EmbedPlayerNative" => array( 'scripts'=> "players/mw.EmbedPlayerNative.js" ), |
| 44 | + "mw.EmbedPlayerKplayer" => array( 'scripts'=> "resources/mw.EmbedPlayerKplayer.js" ), |
| 45 | + "mw.EmbedPlayerGeneric" => array( 'scripts'=> "resources/mw.EmbedPlayerGeneric.js" ), |
| 46 | + "mw.EmbedPlayerJava" => array( 'scripts'=> "resources/mw.EmbedPlayerJava.js"), |
| 47 | + "mw.EmbedPlayerNative" => array( 'scripts'=> "resources/mw.EmbedPlayerNative.js" ), |
30 | 48 | |
31 | | - "mw.EmbedPlayerVlc" => array( 'scripts'=> "players/mw.EmbedPlayerVlc.js" ), |
| 49 | + "mw.EmbedPlayerVlc" => array( 'scripts'=> "resources/mw.EmbedPlayerVlc.js" ), |
32 | 50 | |
33 | | - "mw.IFramePlayerApiServer" => array( 'scripts' => "iframeApi/mw.IFramePlayerApiServer.js" ), |
34 | | - "mw.IFramePlayerApiClient" => array( 'scripts' => "iframeApi/mw.IFramePlayerApiClient.js" ), |
| 51 | + "mw.IFramePlayerApiServer" => array( 'scripts' => "resources/iframeApi/mw.IFramePlayerApiServer.js" ), |
| 52 | + "mw.IFramePlayerApiClient" => array( 'scripts' => "resources/iframeApi/mw.IFramePlayerApiClient.js" ), |
35 | 53 | |
36 | | - "mw.PlayerSkinKskin" => array( 'scripts' => "skins/kskin/mw.PlayerSkinKskin.js", |
37 | | - 'styles' => "skins/kskin/mw.style.PlayerSkinKskin.css"), |
| 54 | + "mw.PlayerSkinKskin" => array( 'scripts' => "resources/skins/kskin/mw.PlayerSkinKskin.js", |
| 55 | + 'styles' => "resources/skins/kskin/mw.style.PlayerSkinKskin.css"), |
38 | 56 | |
39 | | - "mw.PlayerSkinMvpcf" => array( 'scripts'=> "skins/mvpcf/mw.PlayerSkinMvpcf.js", |
40 | | - 'styles'=> "skins/mvpcf/mw.style.PlayerSkinMvpcf.css"), |
| 57 | + "mw.PlayerSkinMvpcf" => array( 'scripts'=> "resources/skins/mvpcf/mw.PlayerSkinMvpcf.js", |
| 58 | + 'styles'=> "resources/skins/mvpcf/mw.style.PlayerSkinMvpcf.css"), |
41 | 59 | ); |
42 | 60 | ?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.config.php |
— | — | @@ -53,6 +53,9 @@ |
54 | 54 | |
55 | 55 | // If fullscreen is global enabled. |
56 | 56 | "EmbedPlayer.EnableFullscreen" => true, |
| 57 | + |
| 58 | + // If the options control bar menu item should be enabled: |
| 59 | + 'EmbedPlayer.EnableOptionsMenu' => true, |
57 | 60 | |
58 | 61 | // If mwEmbed should use the Native player controls |
59 | 62 | // this will prevent video tag rewriting and skinning |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.loader.js |
— | — | @@ -113,9 +113,9 @@ |
114 | 114 | mediaWiki.loader.using( dependencySet, function(){ |
115 | 115 | setTimeout(function(){ |
116 | 116 | mw.processEmbedPlayers( playerSelect, readyCallback ); |
117 | | - },500); |
118 | | - }, function(){ |
119 | | - throw new Error( 'Error loading EmbedPlayer dependency set' ); |
| 117 | + },1000); |
| 118 | + }, function( e ){ |
| 119 | + //throw new Error( 'Error loading EmbedPlayer dependency set' ); |
120 | 120 | }); |
121 | 121 | }; |
122 | 122 | |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerGeneric.js |
— | — | @@ -0,0 +1,33 @@ |
| 2 | +/* |
| 3 | +* Simple embed object for unknown application/ogg plugin |
| 4 | +*/ |
| 5 | +( function( mw, $ ) { |
| 6 | + mw.EmbedPlayerGeneric = { |
| 7 | + // List of supported features of the generic plugin |
| 8 | + supports: { |
| 9 | + 'playHead':false, |
| 10 | + 'pause':false, |
| 11 | + 'stop':true, |
| 12 | + 'fullscreen':false, |
| 13 | + 'timeDisplay':false, |
| 14 | + 'volumeControl':false |
| 15 | + }, |
| 16 | + |
| 17 | + // Instance name: |
| 18 | + instanceOf:'Generic', |
| 19 | + |
| 20 | + /* |
| 21 | + * Generic embed html |
| 22 | + * |
| 23 | + * @return {String} |
| 24 | + * embed code for generic ogg plugin |
| 25 | + */ |
| 26 | + doEmbedHTML: function() { |
| 27 | + $( this ).html( |
| 28 | + '<object type="application/ogg" ' + |
| 29 | + 'width="' + this.getWidth() + '" height="' + this.getHeight() + '" ' + |
| 30 | + 'data="' + this.getSrc( this.seek_time_sec ) + '"></object>' |
| 31 | + ); |
| 32 | + } |
| 33 | + }; |
| 34 | +} )( window.mediaWiki, window.jQuery ); |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerGeneric.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 35 | + text/plain |
Added: svn:eol-style |
2 | 36 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaElement.js |
— | — | @@ -0,0 +1,344 @@ |
| 2 | +/** |
| 3 | + * A media element corresponding to a <video> element. |
| 4 | + * |
| 5 | + * It is implemented as a collection of mediaSource objects. The media sources |
| 6 | + * will be initialized from the <video> element, its child <source> elements, |
| 7 | + * and/or the ROE file referenced by the <video> element. |
| 8 | + * |
| 9 | + * @param {element} |
| 10 | + * videoElement <video> element used for initialization. |
| 11 | + * @constructor |
| 12 | + */ |
| 13 | +function mediaElement( element ) { |
| 14 | + this.init( element ); |
| 15 | +} |
| 16 | + |
| 17 | +mediaElement.prototype = { |
| 18 | + |
| 19 | + // The array of mediaSource elements. |
| 20 | + sources: null, |
| 21 | + |
| 22 | + // flag for ROE data being added. |
| 23 | + addedROEData: false, |
| 24 | + |
| 25 | + // Selected mediaSource element. |
| 26 | + selectedSource: null, |
| 27 | + |
| 28 | + /** |
| 29 | + * Media Element constructor |
| 30 | + * |
| 31 | + * Sets up a mediaElement from a provided top level "video" element adds any |
| 32 | + * child sources that are found |
| 33 | + * |
| 34 | + * @param {Element} |
| 35 | + * videoElement Element that has src attribute or has children |
| 36 | + * source elements |
| 37 | + */ |
| 38 | + init: function( videoElement ) { |
| 39 | + var _this = this; |
| 40 | + mw.log( "EmbedPlayer::mediaElement:init:" + videoElement.id ); |
| 41 | + this.sources = new Array(); |
| 42 | + |
| 43 | + // Process the videoElement as a source element: |
| 44 | + if ( $( videoElement ).attr( "src" ) ) { |
| 45 | + _this.tryAddSource( videoElement ); |
| 46 | + } |
| 47 | + |
| 48 | + // Process elements source children |
| 49 | + $( videoElement ).find( 'source,track' ).each( function( ) { |
| 50 | + _this.tryAddSource( this ); |
| 51 | + } ); |
| 52 | + }, |
| 53 | + |
| 54 | + /** |
| 55 | + * Updates the time request for all sources that have a standard time |
| 56 | + * request argument (ie &t=start_time/end_time) |
| 57 | + * |
| 58 | + * @param {String} |
| 59 | + * start_npt Start time in npt format |
| 60 | + * @param {String} |
| 61 | + * end_npt End time in npt format |
| 62 | + */ |
| 63 | + updateSourceTimes: function( start_npt, end_npt ) { |
| 64 | + var _this = this; |
| 65 | + $j.each( this.sources, function( inx, mediaSource ) { |
| 66 | + mediaSource.updateSrcTime( start_npt, end_npt ); |
| 67 | + } ); |
| 68 | + }, |
| 69 | + |
| 70 | + /** |
| 71 | + * Check for Timed Text tracks |
| 72 | + * |
| 73 | + * @return {Boolean} True if text tracks exist, false if no text tracks are |
| 74 | + * found |
| 75 | + */ |
| 76 | + textSourceExists: function() { |
| 77 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 78 | + if ( this.sources[i].mimeType == 'text/cmml' || |
| 79 | + this.sources[i].mimeType == 'text/x-srt' ) |
| 80 | + { |
| 81 | + return true; |
| 82 | + } |
| 83 | + }; |
| 84 | + return false; |
| 85 | + }, |
| 86 | + |
| 87 | + /** |
| 88 | + * Returns the array of mediaSources of this element. |
| 89 | + * |
| 90 | + * @param {String} |
| 91 | + * [mimeFilter] Filter criteria for set of mediaSources to return |
| 92 | + * @return {Array} mediaSource elements. |
| 93 | + */ |
| 94 | + getSources: function( mimeFilter ) { |
| 95 | + if ( !mimeFilter ) { |
| 96 | + return this.sources; |
| 97 | + } |
| 98 | + // Apply mime filter: |
| 99 | + var source_set = new Array(); |
| 100 | + for ( var i = 0; i < this.sources.length ; i++ ) { |
| 101 | + if ( this.sources[i].mimeType && |
| 102 | + this.sources[i].mimeType.indexOf( mimeFilter ) != -1 ) |
| 103 | + { |
| 104 | + source_set.push( this.sources[i] ); |
| 105 | + } |
| 106 | + } |
| 107 | + return source_set; |
| 108 | + }, |
| 109 | + |
| 110 | + /** |
| 111 | + * Selects a source by id |
| 112 | + * |
| 113 | + * @param {String} |
| 114 | + * source_id Id of the source to select. |
| 115 | + * @return {MediaSource} The selected mediaSource or null if not found |
| 116 | + */ |
| 117 | + getSourceById:function( source_id ) { |
| 118 | + for ( var i = 0; i < this.sources.length ; i++ ) { |
| 119 | + if ( this.sources[i].id == source_id ) { |
| 120 | + return this.sources[i]; |
| 121 | + } |
| 122 | + } |
| 123 | + return null; |
| 124 | + }, |
| 125 | + |
| 126 | + /** |
| 127 | + * Selects a particular source for playback updating the "selectedSource" |
| 128 | + * |
| 129 | + * @param {Number} |
| 130 | + * index Index of source element to set as selectedSource |
| 131 | + */ |
| 132 | + selectSource:function( index ) { |
| 133 | + mw.log( 'EmbedPlayer::mediaElement:selectSource:' + index ); |
| 134 | + var playableSources = this.getPlayableSources(); |
| 135 | + for ( var i = 0; i < playableSources.length; i++ ) { |
| 136 | + if ( i == index ) { |
| 137 | + this.selectedSource = playableSources[i]; |
| 138 | + // Update the user selected format: |
| 139 | + mw.EmbedTypes.getMediaPlayers().setFormatPreference( playableSources[i].mimeType ); |
| 140 | + break; |
| 141 | + } |
| 142 | + } |
| 143 | + }, |
| 144 | + |
| 145 | + /** |
| 146 | + * Selects the default source via cookie preference, default marked, or by |
| 147 | + * id order |
| 148 | + */ |
| 149 | + autoSelectSource: function() { |
| 150 | + mw.log( 'EmbedPlayer::mediaElement::autoSelectSource' ); |
| 151 | + var _this = this; |
| 152 | + // Select the default source |
| 153 | + var playableSources = this.getPlayableSources(); |
| 154 | + var flash_flag = ogg_flag = false; |
| 155 | + |
| 156 | + // Check if there are any playableSources |
| 157 | + if( playableSources.length == 0 ){ |
| 158 | + return false; |
| 159 | + } |
| 160 | + var setSelectedSource = function( source ){ |
| 161 | + _this.selectedSource = source; |
| 162 | + }; |
| 163 | + |
| 164 | + // Set via user-preference |
| 165 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 166 | + var mimeType = playableSources[source].mimeType; |
| 167 | + if ( mw.EmbedTypes.getMediaPlayers().preference[ 'format_preference' ] == mimeType ) { |
| 168 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via preference: ' + playableSources[source].mimeType ); |
| 169 | + setSelectedSource( playableSources[source] ); |
| 170 | + return true; |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + // Set via module driven preference: |
| 175 | + $(this).trigger( 'AutoSelectSource', [ playableSources ] ); |
| 176 | + if( _this.selectedSource ){ |
| 177 | + return true; |
| 178 | + } |
| 179 | + |
| 180 | + // Set via marked default: |
| 181 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 182 | + if ( playableSources[ source ].markedDefault ) { |
| 183 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via marked default: ' + playableSources[source].markedDefault ); |
| 184 | + setSelectedSource( playableSources[source] ); |
| 185 | + return true; |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + // Prefer native playback ( and prefer WebM over ogg and h.264 ) |
| 190 | + var namedSources = []; |
| 191 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 192 | + var mimeType = playableSources[source].mimeType; |
| 193 | + var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ); |
| 194 | + if ( player && player.library == 'Native' ) { |
| 195 | + switch( player.id ){ |
| 196 | + case 'oggNative': |
| 197 | + namedSources['ogg'] = playableSources[ source ]; |
| 198 | + break; |
| 199 | + case 'webmNative': |
| 200 | + namedSources['webm'] = playableSources[ source ]; |
| 201 | + break; |
| 202 | + case 'h264Native': |
| 203 | + namedSources['h264'] = playableSources[ source ]; |
| 204 | + break; |
| 205 | + } |
| 206 | + } |
| 207 | + } |
| 208 | + var codecPref =mw.getConfig( 'EmbedPlayer.CodecPreference'); |
| 209 | + for(var i =0; i < codecPref.length; i++){ |
| 210 | + var codec = codecPref[ i ]; |
| 211 | + if( namedSources[ codec ]){ |
| 212 | + setSelectedSource( namedSources[ codec ] ); |
| 213 | + return true; |
| 214 | + } |
| 215 | + }; |
| 216 | + |
| 217 | + |
| 218 | + // Set h264 via native or flash fallback |
| 219 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 220 | + var mimeType = playableSources[source].mimeType; |
| 221 | + var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ); |
| 222 | + if ( mimeType == 'video/h264' |
| 223 | + && player |
| 224 | + && ( |
| 225 | + player.library == 'Native' |
| 226 | + || |
| 227 | + player.library == 'Kplayer' |
| 228 | + ) |
| 229 | + ) { |
| 230 | + mw.log('EmbedPlayer::autoSelectSource: Set h264 via native or flash fallback'); |
| 231 | + setSelectedSource( playableSources[ source ] ); |
| 232 | + return true; |
| 233 | + } |
| 234 | + }; |
| 235 | + |
| 236 | + // Else just select first source |
| 237 | + if ( !this.selectedSource ) { |
| 238 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via first source:' + playableSources[0] ); |
| 239 | + setSelectedSource( playableSources[0] ); |
| 240 | + return true; |
| 241 | + } |
| 242 | + // No Source found so no source selected |
| 243 | + return false; |
| 244 | + }, |
| 245 | + |
| 246 | + /** |
| 247 | + * check if the mime is ogg |
| 248 | + */ |
| 249 | + isOgg: function( mimeType ){ |
| 250 | + if ( mimeType == 'video/ogg' |
| 251 | + || mimeType == 'ogg/video' |
| 252 | + || mimeType == 'video/annodex' |
| 253 | + || mimeType == 'application/ogg' |
| 254 | + ) { |
| 255 | + return true; |
| 256 | + } |
| 257 | + return false; |
| 258 | + }, |
| 259 | + |
| 260 | + /** |
| 261 | + * Returns the thumbnail URL for the media element. |
| 262 | + * |
| 263 | + * @returns {String} thumbnail URL |
| 264 | + */ |
| 265 | + getPosterSrc: function( ) { |
| 266 | + return this.poster; |
| 267 | + }, |
| 268 | + |
| 269 | + /** |
| 270 | + * Checks whether there is a stream of a specified MIME type. |
| 271 | + * |
| 272 | + * @param {String} |
| 273 | + * mimeType MIME type to check. |
| 274 | + * @return {Boolean} true if sources include MIME false if not. |
| 275 | + */ |
| 276 | + hasStreamOfMIMEType: function( mimeType ) |
| 277 | + { |
| 278 | + for ( var i = 0; i < this.sources.length; i++ ) |
| 279 | + { |
| 280 | + if ( this.sources[i].getMIMEType() == mimeType ){ |
| 281 | + return true; |
| 282 | + } |
| 283 | + } |
| 284 | + return false; |
| 285 | + }, |
| 286 | + |
| 287 | + /** |
| 288 | + * Checks if media is a playable type |
| 289 | + */ |
| 290 | + isPlayableType: function( mimeType ) { |
| 291 | + if ( mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ) ) { |
| 292 | + return true; |
| 293 | + } else { |
| 294 | + return false; |
| 295 | + } |
| 296 | + }, |
| 297 | + |
| 298 | + /** |
| 299 | + * Adds a single mediaSource using the provided element if the element has a |
| 300 | + * 'src' attribute. |
| 301 | + * |
| 302 | + * @param {Element} |
| 303 | + * element <video>, <source> or <mediaSource> <text> element. |
| 304 | + */ |
| 305 | + tryAddSource: function( element ) { |
| 306 | + // mw.log( 'f:tryAddSource:' + $( element ).attr( "src" ) ); |
| 307 | + var newSrc = $( element ).attr( 'src' ); |
| 308 | + if ( newSrc ) { |
| 309 | + // make sure an existing element with the same src does not already |
| 310 | + // exist: |
| 311 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 312 | + if ( this.sources[i].src == newSrc ) { |
| 313 | + // Source already exists update any new attr: |
| 314 | + this.sources[i].updateSource( element ); |
| 315 | + return this.sources[i]; |
| 316 | + } |
| 317 | + } |
| 318 | + } |
| 319 | + // Create a new source |
| 320 | + var source = new mediaSource( element ); |
| 321 | + |
| 322 | + this.sources.push( source ); |
| 323 | + // mw.log( 'tryAddSource: added source ::' + source + 'sl:' + |
| 324 | + // this.sources.length ); |
| 325 | + return source; |
| 326 | + }, |
| 327 | + |
| 328 | + /** |
| 329 | + * Get playable sources |
| 330 | + * |
| 331 | + * @returns {Array} of playable sources |
| 332 | + */ |
| 333 | + getPlayableSources: function() { |
| 334 | + var playableSources = []; |
| 335 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 336 | + if ( this.isPlayableType( this.sources[i].mimeType ) ) { |
| 337 | + playableSources.push( this.sources[i] ); |
| 338 | + } else { |
| 339 | + mw.log( "type " + this.sources[i].mimeType + ' is not playable' ); |
| 340 | + } |
| 341 | + }; |
| 342 | + return playableSources; |
| 343 | + } |
| 344 | +}; |
| 345 | + |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaElement.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 346 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js |
— | — | @@ -0,0 +1,2322 @@ |
| 2 | +/** |
| 3 | +* embedPlayer is the base class for html5 video tag javascript abstraction library |
| 4 | +* embedPlayer include a few subclasses: |
| 5 | +* |
| 6 | +* mediaPlayer Media player embed system ie: java, vlc or native. |
| 7 | +* mediaElement Represents source media elements |
| 8 | +* mw.PlayerControlBuilder Handles skinning of the player controls |
| 9 | +*/ |
| 10 | + |
| 11 | +( function( mw, $ ) { |
| 12 | +/** |
| 13 | + * The base source attribute checks also see: |
| 14 | + * http://dev.w3.org/html5/spec/Overview.html#the-source-element |
| 15 | + */ |
| 16 | +mw.mergeConfig( 'EmbedPlayer.SourceAttributes', [ |
| 17 | + // source id |
| 18 | + 'id', |
| 19 | + |
| 20 | + // media url |
| 21 | + 'src', |
| 22 | + |
| 23 | + // Title string for the source asset |
| 24 | + 'title', |
| 25 | + |
| 26 | + // boolean if we support temporal url requests on the source media |
| 27 | + 'URLTimeEncoding', |
| 28 | + |
| 29 | + // Media has a startOffset ( used for plugins that |
| 30 | + // display ogg page time rather than presentation time |
| 31 | + 'data-startoffset', |
| 32 | + |
| 33 | + // A hint to the duration of the media file so that duration |
| 34 | + // can be displayed in the player without loading the media file |
| 35 | + 'data-durationhint', |
| 36 | + |
| 37 | + // Source stream qualities ( will eventually be adaptive streaming ) |
| 38 | + 'data-shorttitle', // short title for stream ( usefull for stream switching control bar item) |
| 39 | + 'data-width', // the width of the stream |
| 40 | + 'data-height', // the height of the stream |
| 41 | + 'data-bandwith', // the overall bitrate of the stream |
| 42 | + 'data-framerate', // the framereate of the stream |
| 43 | + |
| 44 | + // Media start time |
| 45 | + 'start', |
| 46 | + |
| 47 | + // Media end time |
| 48 | + 'end', |
| 49 | + |
| 50 | + // If the source is the default source |
| 51 | + 'default' |
| 52 | +] ); |
| 53 | + |
| 54 | +/** |
| 55 | + * Merge in the default video attributes supported by embedPlayer: |
| 56 | + */ |
| 57 | +mw.mergeConfig('EmbedPlayer.Attributes', { |
| 58 | + /* |
| 59 | + * Base html element attributes: |
| 60 | + */ |
| 61 | + |
| 62 | + // id: Auto-populated if unset |
| 63 | + "id" : null, |
| 64 | + |
| 65 | + // Width: alternate to "style" to set player width |
| 66 | + "width" : null, |
| 67 | + |
| 68 | + // Height: alternative to "style" to set player height |
| 69 | + "height" : null, |
| 70 | + |
| 71 | + /* |
| 72 | + * Base html5 video element attributes / states also see: |
| 73 | + * http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html |
| 74 | + */ |
| 75 | + |
| 76 | + // Media src URI, can be relative or absolute URI |
| 77 | + "src" : null, |
| 78 | + |
| 79 | + // Poster attribute for displaying a place holder image before loading |
| 80 | + // or playing the video |
| 81 | + "poster" : null, |
| 82 | + |
| 83 | + // Autoplay if the media should start playing |
| 84 | + "autoplay" : false, |
| 85 | + |
| 86 | + // Loop attribute if the media should repeat on complete |
| 87 | + "loop" : false, |
| 88 | + |
| 89 | + // If the player controls should be displayed |
| 90 | + "controls" : true, |
| 91 | + |
| 92 | + // Video starts "paused" |
| 93 | + "paused" : true, |
| 94 | + |
| 95 | + // ReadyState an attribute informs clients of video loading state: |
| 96 | + // see: http://www.whatwg.org/specs/web-apps/current-work/#readystate |
| 97 | + "readyState" : 0, |
| 98 | + |
| 99 | + // Loading state of the video element |
| 100 | + "networkState" : 0, |
| 101 | + |
| 102 | + // Current playback position |
| 103 | + "currentTime" : 0, |
| 104 | + |
| 105 | + // Previous player set time |
| 106 | + // Lets javascript use $('#videoId').get(0).currentTime = newTime; |
| 107 | + "previousTime" : 0, |
| 108 | + |
| 109 | + // Previous player set volume |
| 110 | + // Lets javascript use $('#videoId').get(0).volume = newVolume; |
| 111 | + "previousVolume" : 1, |
| 112 | + |
| 113 | + // Initial player volume: |
| 114 | + "volume" : 0.75, |
| 115 | + |
| 116 | + // Caches the volume before a mute toggle |
| 117 | + "preMuteVolume" : 0.75, |
| 118 | + |
| 119 | + // Media duration: Value is populated via |
| 120 | + // custom data-durationhint attribute or via the media file once its played |
| 121 | + "duration" : null, |
| 122 | + |
| 123 | + // Mute state |
| 124 | + "muted" : false, |
| 125 | + |
| 126 | + /** |
| 127 | + * Custom attributes for embedPlayer player: (not part of the html5 |
| 128 | + * video spec) |
| 129 | + */ |
| 130 | + |
| 131 | + // Default video aspect ratio |
| 132 | + 'videoAspect' : '4:3', |
| 133 | + |
| 134 | + // Start time of the clip |
| 135 | + "start" : 0, |
| 136 | + |
| 137 | + // End time of the clip |
| 138 | + "end" : null, |
| 139 | + |
| 140 | + // If the player controls should be overlaid |
| 141 | + // ( Global default via config EmbedPlayer.OverlayControls in module |
| 142 | + // loader.js) |
| 143 | + "overlaycontrols" : true, |
| 144 | + |
| 145 | + // Attribute to use 'native' controls |
| 146 | + "usenativecontrols" : false, |
| 147 | + |
| 148 | + // If the player should include an attribution button: |
| 149 | + 'attributionbutton' : true, |
| 150 | + |
| 151 | + // If serving an ogg_chop segment use this to offset the presentation time |
| 152 | + // ( for some plugins that use ogg page time rather than presentation time ) |
| 153 | + "startOffset" : 0, |
| 154 | + |
| 155 | + // If the download link should be shown |
| 156 | + "download_link" : true, |
| 157 | + |
| 158 | + // Content type of the media |
| 159 | + "type" : null |
| 160 | +} ); |
| 161 | + |
| 162 | + |
| 163 | +/** |
| 164 | + * Selector based embedPlayer processing |
| 165 | + * |
| 166 | + * @param {Function=} |
| 167 | + * callback Optional Function to be called once video interfaces |
| 168 | + * are ready |
| 169 | + * |
| 170 | + */ |
| 171 | +mw.processEmbedPlayers = function( playerSelect, callback ) { |
| 172 | + mw.log( 'EmbedPlayer:: processEmbedPlayers' ); |
| 173 | + |
| 174 | + |
| 175 | + /** |
| 176 | + * Adds a player element for the embedPlayer to rewrite |
| 177 | + * |
| 178 | + * uses embedPlayer interface on audio / video elements uses mvPlayList |
| 179 | + * interface on playlist elements |
| 180 | + * |
| 181 | + * Once a player interface is established the following chain of functions |
| 182 | + * are called; |
| 183 | + * |
| 184 | + * _this.checkPlayerSources() |
| 185 | + * _this.setupSourcePlayer() |
| 186 | + * _this.inheritEmbedPlayer() |
| 187 | + * _this.selectedPlayer.load() |
| 188 | + * _this.showPlayer() |
| 189 | + * |
| 190 | + * @param {Element} |
| 191 | + * playerElement DOM element to be swapped |
| 192 | + * @param {Object} |
| 193 | + * [Optional] attributes Extra attributes to apply to the player |
| 194 | + * interface |
| 195 | + */ |
| 196 | + var addPlayerElement = function( playerElement ) { |
| 197 | + var _this = this; |
| 198 | + mw.log('EmbedPlayer:: addElement:: ' + playerElement.id ); |
| 199 | + |
| 200 | + var waitForMeta = true; |
| 201 | + |
| 202 | + // Be sure to "stop" the target ( Firefox 3x keeps playing |
| 203 | + // the video even though its been removed from the DOM ) |
| 204 | + if( playerElement.pause ){ |
| 205 | + playerElement.pause(); |
| 206 | + } |
| 207 | + |
| 208 | + // Allow modules to override the wait for metadata flag: |
| 209 | + $( mw ).trigger( 'checkPlayerWaitForMetaData', playerElement ); |
| 210 | + |
| 211 | + // Update the waitForMeta object if set to boolean false: |
| 212 | + waitForMeta = ( playerElement.waitForMeta === false )? false : true; |
| 213 | + |
| 214 | + |
| 215 | + // Confirm we want to wait for meta data ( if not already set to false by module ) |
| 216 | + if( waitForMeta ){ |
| 217 | + waitForMeta = waitForMetaCheck( playerElement ); |
| 218 | + } |
| 219 | + |
| 220 | + var ranPlayerSwapFlag = false; |
| 221 | + |
| 222 | + // Local callback to runPlayer swap once playerElement has metadata |
| 223 | + function runPlayerSwap() { |
| 224 | + // Don't run player swap twice |
| 225 | + if( ranPlayerSwapFlag ){ |
| 226 | + return ; |
| 227 | + } |
| 228 | + ranPlayerSwapFlag = true; |
| 229 | + mw.log("EmbedPlayer::runPlayerSwap::" + $( playerElement ).attr('id') ); |
| 230 | + |
| 231 | + var playerInterface = new mw.EmbedPlayer( playerElement ); |
| 232 | + var swapPlayer = swapEmbedPlayerElement( playerElement, playerInterface ); |
| 233 | + |
| 234 | + // Trigger the EmbedPlayerNewPlayer for embedPlayer interface |
| 235 | + mw.log("EmbedPlayer::EmbedPlayerNewPlayer:trigger " + playerInterface.id ); |
| 236 | + $( mw ).trigger ( 'EmbedPlayerNewPlayer', $( '#' + playerInterface.id ).get(0) ); |
| 237 | + |
| 238 | + // Issue the checkPlayerSources call to the new player |
| 239 | + // interface: make sure to use the element that is in the DOM: |
| 240 | + $( '#' + playerInterface.id ).get(0).checkPlayerSources(); |
| 241 | + } |
| 242 | + |
| 243 | + if( waitForMeta && mw.getConfig('EmbedPlayer.WaitForMeta' ) ) { |
| 244 | + mw.log('EmbedPlayer::WaitForMeta ( video missing height (' + |
| 245 | + $( playerElement ).attr('height') + '), width (' + |
| 246 | + $( playerElement ).attr('width') + ') or duration: ' + |
| 247 | + $( playerElement ).attr('duration') |
| 248 | + ); |
| 249 | + $( playerElement ).bind("loadedmetadata", runPlayerSwap ); |
| 250 | + |
| 251 | + // Time-out of 5 seconds ( maybe still playable but no timely |
| 252 | + // metadata ) |
| 253 | + setTimeout( runPlayerSwap, 5000 ); |
| 254 | + return ; |
| 255 | + } else { |
| 256 | + runPlayerSwap(); |
| 257 | + return ; |
| 258 | + } |
| 259 | + }; |
| 260 | + |
| 261 | + /** |
| 262 | + * Check if we should wait for metadata. |
| 263 | + * |
| 264 | + * @return true if the size is "likely" to be updated by waiting for metadata |
| 265 | + * false if the size has been set via an attribute or is already loaded |
| 266 | + */ |
| 267 | + var waitForMetaCheck = function( playerElement ){ |
| 268 | + var waitForMeta = false; |
| 269 | + |
| 270 | + // Don't wait for metadata for non html5 media elements |
| 271 | + if( !playerElement ){ |
| 272 | + return false; |
| 273 | + } |
| 274 | + if( !playerElement.tagName || ( playerElement.tagName.toLowerCase() != 'audio' && playerElement.tagName.toLowerCase() != 'video' ) ){ |
| 275 | + return false; |
| 276 | + } |
| 277 | + // If we don't have a native player don't wait for metadata |
| 278 | + if( !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'oggNative') && |
| 279 | + !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'webmNative') && |
| 280 | + !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'h264Native' ) ) |
| 281 | + { |
| 282 | + return false; |
| 283 | + } |
| 284 | + |
| 285 | + |
| 286 | + var width = $( playerElement ).css( 'width' ); |
| 287 | + var height = $( playerElement ).css( 'height' ); |
| 288 | + |
| 289 | + // Css video defaults ( firefox ) |
| 290 | + if( $( playerElement ).css( 'width' ) == '300px' && |
| 291 | + $( playerElement ).css( 'height' ) == '150px' |
| 292 | + ){ |
| 293 | + waitForMeta = true; |
| 294 | + } else { |
| 295 | + // Check if we should wait for duration: |
| 296 | + if( $( playerElement ).attr( 'duration') || |
| 297 | + $( playerElement ).attr('data-durationhint') |
| 298 | + ){ |
| 299 | + // height, width and duration set; do not wait for meta data: |
| 300 | + return false; |
| 301 | + } else { |
| 302 | + waitForMeta = true; |
| 303 | + } |
| 304 | + } |
| 305 | + |
| 306 | + // Firefox ~ sometimes~ gives -1 for unloaded media |
| 307 | + if ( $(playerElement).attr( 'width' ) == -1 || $(playerElement).attr( 'height' ) == -1 ) { |
| 308 | + waitForMeta = true; |
| 309 | + } |
| 310 | + |
| 311 | + // Google Chrome / safari gives 0 width height for unloaded media |
| 312 | + if( $(playerElement).attr( 'width' ) === 0 || |
| 313 | + $(playerElement).attr( 'height' ) === 0 |
| 314 | + ) { |
| 315 | + waitForMeta = true; |
| 316 | + } |
| 317 | + |
| 318 | + // Firefox default width height is ~sometimes~ 150 / 300 |
| 319 | + if( this.height == 150 && this.width == 300 ){ |
| 320 | + waitForMeta = true; |
| 321 | + } |
| 322 | + |
| 323 | + // Make sure we have a src attribute or source child |
| 324 | + // ( i.e not a video tag to be dynamically populated or looked up from |
| 325 | + // xml resource description ) |
| 326 | + if( waitForMeta && |
| 327 | + ( |
| 328 | + $( playerElement ).attr('src') || |
| 329 | + $( playerElement ).find("source[src]").length !== 0 |
| 330 | + ) |
| 331 | + ) { |
| 332 | + // Detect src type ( if no type set ) |
| 333 | + return true; |
| 334 | + } else { |
| 335 | + // playerElement is not likely to update its meta data ( no src ) |
| 336 | + return false; |
| 337 | + } |
| 338 | + }; |
| 339 | + |
| 340 | + /** |
| 341 | + * swapEmbedPlayerElement |
| 342 | + * |
| 343 | + * Takes a video element as input and swaps it out with an embed player interface |
| 344 | + * |
| 345 | + * @param {Element} |
| 346 | + * targetElement Element to be swapped |
| 347 | + * @param {Object} |
| 348 | + * playerInterface Interface to swap into the target element |
| 349 | + */ |
| 350 | + var swapEmbedPlayerElement = function( targetElement, playerInterface ) { |
| 351 | + mw.log( 'EmbedPlayer::swapEmbedPlayerElement: ' + targetElement.id ); |
| 352 | + // Create a new element to swap the player interface into |
| 353 | + var swapPlayerElement = document.createElement('div'); |
| 354 | + |
| 355 | + // Add a class that identifies all embedPlayers: |
| 356 | + $( swapPlayerElement ).addClass( 'mwEmbedPlayer' ); |
| 357 | + |
| 358 | + // Get properties / methods from playerInterface: |
| 359 | + for ( var method in playerInterface ) { |
| 360 | + if ( method != 'readyState' ) { // readyState crashes IE ( don't include ) |
| 361 | + swapPlayerElement[ method ] = playerInterface[ method ]; |
| 362 | + } |
| 363 | + } |
| 364 | + // Check if we are using native controls or Persistent player ( should keep the video embed around ) |
| 365 | + if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) { |
| 366 | + $( targetElement ) |
| 367 | + .attr( 'id', playerInterface.pid ) |
| 368 | + .addClass( 'nativeEmbedPlayerPid' ) |
| 369 | + .show() |
| 370 | + .after( |
| 371 | + $( swapPlayerElement ).css( 'display', 'none' ) |
| 372 | + ); |
| 373 | + |
| 374 | + } else { |
| 375 | + $( targetElement ).replaceWith( swapPlayerElement ); |
| 376 | + } |
| 377 | + |
| 378 | + |
| 379 | + // Set swapPlayerElement has height / width set and set to loading: |
| 380 | + $( swapPlayerElement ).css( { |
| 381 | + 'width' : playerInterface.width + 'px', |
| 382 | + 'height' : playerInterface.height + 'px' |
| 383 | + } ); |
| 384 | + |
| 385 | + // If we don't already have a loadSpiner add one: |
| 386 | + if( $('#loadingSpinner_' + playerInterface.id ).length == 0 ){ |
| 387 | + if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) { |
| 388 | + var $spinner = $( targetElement ) |
| 389 | + .getAbsoluteOverlaySpinner(); |
| 390 | + }else{ |
| 391 | + var $spinner = $( swapPlayerElement ).getAbsoluteOverlaySpinner(); |
| 392 | + } |
| 393 | + $spinner.attr('id', 'loadingSpinner_' + playerInterface.id ); |
| 394 | + } |
| 395 | + return swapPlayerElement; |
| 396 | + }; |
| 397 | + |
| 398 | + // Add a loader for <div> embed player rewrites: |
| 399 | + $( playerSelect ).each( function( index, playerElement) { |
| 400 | + |
| 401 | + // Make sure the playerElement has an id: |
| 402 | + if( !$( playerElement ).attr('id') ){ |
| 403 | + $( playerElement ).attr( "id", 'mwe_v' + ( index ) ); |
| 404 | + } |
| 405 | + |
| 406 | + // If we are dynamically embedding on a "div" check if we can |
| 407 | + // add a poster image behind the loader: |
| 408 | + if( playerElement.nodeName.toLowerCase() == 'div' |
| 409 | + && ( attributes.poster || $(playerElement).attr( 'poster' ) ) ){ |
| 410 | + var posterSrc = ( attributes.poster ) ? attributes.poster : $(playerElement).attr( 'poster' ); |
| 411 | + |
| 412 | + // Set image size: |
| 413 | + var width = $( playerElement ).width(); |
| 414 | + var height = $( playerElement ).height(); |
| 415 | + if( !width ){ |
| 416 | + var width = ( attributes.width ) ? attributes.width : '100%'; |
| 417 | + } |
| 418 | + if( !height ){ |
| 419 | + var height = ( attributes.height ) ? attributes.height : '100%'; |
| 420 | + } |
| 421 | + |
| 422 | + mw.log('EmbedPlayer:: set loading background: ' + posterSrc); |
| 423 | + $( playerElement ).append( |
| 424 | + $( '<img />' ) |
| 425 | + .attr( 'src', posterSrc) |
| 426 | + .css({ |
| 427 | + 'position' : 'absolute', |
| 428 | + 'width' : width, |
| 429 | + 'height' : height |
| 430 | + }) |
| 431 | + ); |
| 432 | + } |
| 433 | + }); |
| 434 | + |
| 435 | + // Create the Global Embed Player Manager ( if not already created ) |
| 436 | + // legacy EmbedPlayerManagerReady event ( should remove ) |
| 437 | + $( mw ).trigger( 'EmbedPlayerManagerReady' ); |
| 438 | + |
| 439 | + // Make sure we have user preference setup for setting preferences on video selection |
| 440 | + var addedToPlayerManager = false; |
| 441 | + mw.log("EmbedPlayer:: do: " + $( playerSelect ).length + ' players '); |
| 442 | + |
| 443 | + // Add each selected element to the player manager: |
| 444 | + $( playerSelect ).each( function( index, playerElement) { |
| 445 | + // Make sure the video tag was not generated by our library: |
| 446 | + if( $( playerElement ).hasClass( 'nativeEmbedPlayerPid' ) ){ |
| 447 | + $('#loadingSpinner_' + $( playerElement ).attr('id') ).remove(); |
| 448 | + mw.log( 'EmbedPlayer::$j.embedPlayer skip embedPlayer gennerated video: ' + playerElement ); |
| 449 | + } else { |
| 450 | + addedToPlayerManager = true; |
| 451 | + // Add the player |
| 452 | + addPlayerElement( playerElement ); |
| 453 | + } |
| 454 | + }); |
| 455 | + if( addedToPlayerManager ){ |
| 456 | + if( callback ){ |
| 457 | + $( mw ).bind( "playersReadyEvent", callback ); |
| 458 | + } |
| 459 | + } else { |
| 460 | + // Run the callback directly if no players were added |
| 461 | + if( callback ){ |
| 462 | + callback(); |
| 463 | + } |
| 464 | + } |
| 465 | +}; |
| 466 | + |
| 467 | + |
| 468 | +/** |
| 469 | + * Base embedPlayer object |
| 470 | + * |
| 471 | + * @param {Element} |
| 472 | + * element, the element used for initialization. |
| 473 | + * @constructor |
| 474 | + */ |
| 475 | +mw.EmbedPlayer = function( element ) { |
| 476 | + return this.init( element ); |
| 477 | +}; |
| 478 | + |
| 479 | +mw.EmbedPlayer.prototype = { |
| 480 | + |
| 481 | + // The mediaElement object containing all mediaSource objects |
| 482 | + 'mediaElement' : null, |
| 483 | + |
| 484 | + // Object that describes the supported feature set of the underling plugin / |
| 485 | + // Support list is described in PlayerControlBuilder components |
| 486 | + 'supports': { }, |
| 487 | + |
| 488 | + // Preview mode flag, |
| 489 | + // some plugins don't seek accurately but in preview mode we need |
| 490 | + // accurate seeks so we do tricks like hide the image until its ready |
| 491 | + 'previewMode' : false, |
| 492 | + |
| 493 | + // Ready to play |
| 494 | + // NOTE: we should switch over to setting the html5 video ready state |
| 495 | + 'readyToPlay' : false, |
| 496 | + |
| 497 | + // Stores the loading errors |
| 498 | + 'loadError' : false, |
| 499 | + |
| 500 | + // Thumbnail updating flag ( to avoid rewriting an thumbnail thats already |
| 501 | + // being updated) |
| 502 | + 'thumbnail_updating' : false, |
| 503 | + |
| 504 | + // Poster display flag |
| 505 | + 'posterDisplayed' : true, |
| 506 | + |
| 507 | + // Local variable to hold CMML meeta data about the current clip |
| 508 | + // for more on CMML see: http://wiki.xiph.org/CMML |
| 509 | + 'cmmlData': null, |
| 510 | + |
| 511 | + // Stores the seek time request, Updated by the doSeek function |
| 512 | + 'serverSeekTime' : 0, |
| 513 | + |
| 514 | + // If the embedPlayer is current 'seeking' |
| 515 | + 'seeking' : false, |
| 516 | + |
| 517 | + // Percent of the clip buffered: |
| 518 | + 'bufferedPercent' : 0, |
| 519 | + |
| 520 | + // Holds the timer interval function |
| 521 | + 'monitorTimerId' : null, |
| 522 | + |
| 523 | + // Buffer flags |
| 524 | + 'bufferStartFlag' : false, |
| 525 | + 'bufferEndFlag' : false, |
| 526 | + |
| 527 | + // For supporting media fragments stores the play end time |
| 528 | + 'pauseTime' : null, |
| 529 | + |
| 530 | + // On done playing |
| 531 | + 'donePlayingCount' : 0 |
| 532 | + , |
| 533 | + // if player events should be Propagated |
| 534 | + '_propagateEvents': true, |
| 535 | + |
| 536 | + // If the onDone interface should be displayed |
| 537 | + 'onDoneInterfaceFlag': true, |
| 538 | + |
| 539 | + |
| 540 | + /** |
| 541 | + * embedPlayer |
| 542 | + * |
| 543 | + * @constructor |
| 544 | + * |
| 545 | + * @param {Element} |
| 546 | + * element DOM element that we are building the player interface for. |
| 547 | + */ |
| 548 | + init: function( element ) { |
| 549 | + var _this = this; |
| 550 | + mw.log('EmbedPlayer: initEmbedPlayer: ' + $(element).width() ); |
| 551 | + |
| 552 | + var playerAttributes = mw.getConfig( 'EmbedPlayer.Attributes' ); |
| 553 | + |
| 554 | + // Setup the player Interface from supported attributes: |
| 555 | + for ( var attr in playerAttributes ) { |
| 556 | + // We can't use $(element).attr( attr ) because we have to check for boolean attributes: |
| 557 | + if ( element.getAttribute( attr ) != null ) { |
| 558 | + // boolean attributes |
| 559 | + if( element.getAttribute( attr ) == '' ){ |
| 560 | + this[ attr ] = true; |
| 561 | + } else { |
| 562 | + this[ attr ] = element.getAttribute( attr ); |
| 563 | + } |
| 564 | + } else { |
| 565 | + this[attr] = playerAttributes[attr]; |
| 566 | + } |
| 567 | + // string -> boolean |
| 568 | + if( this[ attr ] == "false" ) this[attr] = false; |
| 569 | + if( this[ attr ] == "true" ) this[attr] = true; |
| 570 | + } |
| 571 | + // Hide "controls" if using native player controls: |
| 572 | + if( this.useNativePlayerControls() ){ |
| 573 | + _this.controls = false; |
| 574 | + } |
| 575 | + if ( $( element ).attr( 'poster' ) ) { |
| 576 | + _this.poster = $( element ).attr( 'poster' ); |
| 577 | + } |
| 578 | + |
| 579 | + // Set the skin name from the class |
| 580 | + var sn = $(element).attr( 'class' ); |
| 581 | + |
| 582 | + if ( sn && sn != '' ) { |
| 583 | + var skinList = mw.getConfig('EmbedPlayer.SkinList'); |
| 584 | + for ( var n = 0; n < skinList.length; n++ ) { |
| 585 | + if ( sn.indexOf( skinList[n].toLowerCase() ) !== -1 ) { |
| 586 | + this.skinName = skinList[ n ]; |
| 587 | + } |
| 588 | + } |
| 589 | + } |
| 590 | + |
| 591 | + // Set the default skin if unset: |
| 592 | + if ( !this.skinName ) { |
| 593 | + this.skinName = mw.getConfig( 'EmbedPlayer.DefaultSkin' ); |
| 594 | + } |
| 595 | + if( !this.monitorRate ){ |
| 596 | + this.monitorRate = mw.getConfig( 'EmbedPlayer.MonitorRate' ); |
| 597 | + } |
| 598 | + |
| 599 | + // Make sure startOffset is cast as an float: |
| 600 | + if ( this.startOffset && this.startOffset.split( ':' ).length >= 2 ) { |
| 601 | + this.startOffset = parseFloat( mw.npt2seconds( this.startOffset ) ); |
| 602 | + } |
| 603 | + |
| 604 | + // Make sure offset is in float: |
| 605 | + this.startOffset = parseFloat( this.startOffset ); |
| 606 | + |
| 607 | + // Set the source duration ( if provided in the element metaData or |
| 608 | + // data-durationhint ) |
| 609 | + if ( $( element ).attr( 'duration' ) ) { |
| 610 | + _this.duration = $( element ).attr( 'duration' ); |
| 611 | + } |
| 612 | + |
| 613 | + if ( !_this.duration && $( element ).attr( 'data-durationhint' ) ) { |
| 614 | + _this['data-durationhint'] = $( element ).attr( 'data-durationhint' ); |
| 615 | + // Convert duration hint if needed: |
| 616 | + _this.duration = mw.npt2seconds( _this['data-durationhint'] ); |
| 617 | + } |
| 618 | + |
| 619 | + // Make sure duration is a float: |
| 620 | + this.duration = parseFloat( this.duration ); |
| 621 | + mw.log( 'EmbedPlayer::mediaElement:' + this.id + " duration is: " + this.duration ); |
| 622 | + |
| 623 | + // Set the player size attributes based loaded video element: |
| 624 | + this.loadPlayerSize( element ); |
| 625 | + // Set the plugin id |
| 626 | + this.pid = 'pid_' + this.id; |
| 627 | + |
| 628 | + // Grab any innerHTML and set it to missing_plugin_html |
| 629 | + // NOTE: we should strip "source" tags instead of checking and skipping |
| 630 | + if ( element.innerHTML != '' && element.getElementsByTagName( 'source' ).length == 0 ) { |
| 631 | + // mw.log( 'innerHTML: ' + element.innerHTML ); |
| 632 | + this.user_missing_plugin_html = element.innerHTML; |
| 633 | + } |
| 634 | + // Add the mediaElement object with the elements sources: |
| 635 | + this.mediaElement = new mediaElement( element ); |
| 636 | + }, |
| 637 | + /** |
| 638 | + * Stop events from Propagation and blocks interface updates and trigger events. |
| 639 | + * @return |
| 640 | + */ |
| 641 | + stopEventPropagation: function(){ |
| 642 | + this.stopMonitor(); |
| 643 | + this._propagateEvents = false; |
| 644 | + }, |
| 645 | + /** |
| 646 | + * Restores event propagation |
| 647 | + * @return |
| 648 | + */ |
| 649 | + restoreEventPropagation: function(){ |
| 650 | + this._propagateEvents = true; |
| 651 | + this.startMonitor(); |
| 652 | + }, |
| 653 | + |
| 654 | + enableSeekBar: function(){ |
| 655 | + this.controlBuilder.enableSeekBar(); |
| 656 | + $( this ).trigger( 'onEnableSeekBar'); |
| 657 | + }, |
| 658 | + disableSeekBar: function(){ |
| 659 | + this.controlBuilder.disableSeekBar(); |
| 660 | + $( this ).trigger( 'ondisableSeekBar'); |
| 661 | + }, |
| 662 | + |
| 663 | + /** |
| 664 | + * For plugin-players to update supported features |
| 665 | + */ |
| 666 | + updateFeatureSupport: function(){ |
| 667 | + $( this ).trigger('updateFeatureSupportEvent', this.supports ); |
| 668 | + return ; |
| 669 | + }, |
| 670 | + |
| 671 | + /** |
| 672 | + * Set the width & height from css style attribute, element attribute, or by |
| 673 | + * default value if no css or attribute is provided set a callback to |
| 674 | + * resize. |
| 675 | + * |
| 676 | + * Updates this.width & this.height |
| 677 | + * |
| 678 | + * @param {Element} |
| 679 | + * element Source element to grab size from |
| 680 | + */ |
| 681 | + loadPlayerSize: function( element ) { |
| 682 | + this.height = $(element).css( 'height' ); |
| 683 | + this.width = $(element).css( 'width' ); |
| 684 | + // Special check for chrome 100% with re-mapping to 32px |
| 685 | + // ( hopefully no one embeds video at 32x32 ) |
| 686 | + if( this.height == '32px' || this.height =='32px' ){ |
| 687 | + this.width = '100%'; |
| 688 | + this.height = '100%'; |
| 689 | + } |
| 690 | + mw.log('EmbedPlayer::loadPlayerSize: css size:' + this.width + ' h: ' + this.height); |
| 691 | + |
| 692 | + // Set to parent size ( resize events will cause player size updates) |
| 693 | + if( this.height.indexOf('100%') != -1 || this.width.indexOf('100%') != -1 ){ |
| 694 | + $relativeParent = $(element).parents().filter(function() { |
| 695 | + // reduce to only relative position or "body" elements |
| 696 | + return $(this).is('body') || $(this).css('position') == 'relative'; |
| 697 | + }).slice(0,1); // grab only the "first" |
| 698 | + this.width = $relativeParent.width(); |
| 699 | + this.height = $relativeParent.height(); |
| 700 | + } |
| 701 | + // Make sure height and width are a number |
| 702 | + this.height = parseInt( this.height ); |
| 703 | + this.width = parseInt( this.width ); |
| 704 | + |
| 705 | + // Set via attribute if CSS is zero or NaN and we have an attribute value: |
| 706 | + this.height = ( this.height==0 || isNaN( this.height ) |
| 707 | + && $(element).attr( 'height' ) ) ? |
| 708 | + parseInt( $(element).attr( 'height' ) ): this.height; |
| 709 | + this.width = ( this.width == 0 || isNaN( this.width ) |
| 710 | + && $(element).attr( 'width' ) )? |
| 711 | + parseInt( $(element).attr( 'width' ) ): this.width; |
| 712 | + |
| 713 | + |
| 714 | + // Special case for audio |
| 715 | + // Firefox sets audio height to "0px" while webkit uses 32px .. force |
| 716 | + // zero: |
| 717 | + if( element.tagName.toLowerCase() == 'audio' && this.height == '32' ) { |
| 718 | + this.height = 0; |
| 719 | + } |
| 720 | + |
| 721 | + // Use default aspect ration to get height or width ( if rewriting a |
| 722 | + // non-audio player ) |
| 723 | + if( element.tagName.toLowerCase() != 'audio' && this.videoAspect ) { |
| 724 | + var aspect = this.videoAspect.split( ':' ); |
| 725 | + if( this.height && !this.width ) { |
| 726 | + this.width = parseInt( this.height * ( aspect[0] / aspect[1] ) ); |
| 727 | + } |
| 728 | + if( this.width && !this.height ) { |
| 729 | + var apectRatio = ( aspect[1] / aspect[0] ); |
| 730 | + this.height = parseInt( this.width * ( aspect[1] / aspect[0] ) ); |
| 731 | + } |
| 732 | + } |
| 733 | + |
| 734 | + // On load sometimes attr is temporally -1 as we don't have video metadata yet. |
| 735 | + // or in IE we get NaN for width height |
| 736 | + // |
| 737 | + // NOTE: browsers that do support height width should set "waitForMeta" flag in addElement |
| 738 | + if( ( isNaN( this.height )|| isNaN( this.width ) ) || |
| 739 | + ( this.height == -1 || this.width == -1 ) || |
| 740 | + // Check for firefox defaults |
| 741 | + // Note: ideally firefox would not do random guesses at css |
| 742 | + // values |
| 743 | + ( (this.height == 150 || this.height == 64 ) && this.width == 300 ) |
| 744 | + ) { |
| 745 | + var defaultSize = mw.getConfig( 'EmbedPlayer.DefaultSize' ).split( 'x' ); |
| 746 | + if( isNaN( this.width ) ){ |
| 747 | + this.width = defaultSize[0]; |
| 748 | + } |
| 749 | + |
| 750 | + // Special height default for audio tag ( if not set ) |
| 751 | + if( element.tagName.toLowerCase() == 'audio' ) { |
| 752 | + this.height = 0; |
| 753 | + }else{ |
| 754 | + this.height = defaultSize[1]; |
| 755 | + } |
| 756 | + } |
| 757 | + }, |
| 758 | + /** |
| 759 | + * Resize the player to a new size preserving aspect ratio Wraps the |
| 760 | + * controlBuilder.resizePlayer function |
| 761 | + */ |
| 762 | + resizePlayer: function( size , animate, callback){ |
| 763 | + mw.log("EmbedPlayer::resizePlayer:" + size.width + ' x ' + size.height ); |
| 764 | + |
| 765 | + // Check if we are native display then resize the playerElement directly |
| 766 | + if( this.useNativePlayerControls() ){ |
| 767 | + if( animate ){ |
| 768 | + $( this.getPlayerElement() ).animate( size , callback); |
| 769 | + } else { |
| 770 | + $( this.getPlayerElement() ).css( size ); |
| 771 | + if( callback ) { |
| 772 | + callback(); |
| 773 | + } |
| 774 | + } |
| 775 | + } else { |
| 776 | + this.controlBuilder.resizePlayer( size, animate, callback); |
| 777 | + } |
| 778 | + $( this ).trigger( 'onResizePlayer', [size, animate] ); |
| 779 | + }, |
| 780 | + |
| 781 | + /** |
| 782 | + * Get the player pixel width not including controls |
| 783 | + * |
| 784 | + * @return {Number} pixel height of the video |
| 785 | + */ |
| 786 | + getPlayerWidth: function() { |
| 787 | + return $( this ).width(); |
| 788 | + }, |
| 789 | + |
| 790 | + /** |
| 791 | + * Get the player pixel height not including controls |
| 792 | + * |
| 793 | + * @return {Number} pixel height of the video |
| 794 | + */ |
| 795 | + getPlayerHeight: function() { |
| 796 | + return $( this ).height(); |
| 797 | + }, |
| 798 | + |
| 799 | + /** |
| 800 | + * Check player for sources. If we need to get media sources form an |
| 801 | + * external file that request is issued here |
| 802 | + */ |
| 803 | + checkPlayerSources: function() { |
| 804 | + mw.log( 'EmbedPlayer::checkPlayerSources: ' + this.id ); |
| 805 | + var _this = this; |
| 806 | + |
| 807 | + // Allow plugins to block on sources lookup: |
| 808 | + $( _this ).triggerQueueCallback( 'CheckPlayerSourcesEvent', function(){ |
| 809 | + _this.setupSourcePlayer(); |
| 810 | + }); |
| 811 | + }, |
| 812 | + |
| 813 | + /** |
| 814 | + * Empty the player sources |
| 815 | + */ |
| 816 | + emptySources: function(){ |
| 817 | + if( this.mediaElement ){ |
| 818 | + this.mediaElement.sources = []; |
| 819 | + this.mediaElement.selectedSource = null; |
| 820 | + } |
| 821 | + }, |
| 822 | + |
| 823 | + /** |
| 824 | + * Insert and play a video source ( useful for ads or bumper videos ) |
| 825 | + * |
| 826 | + * Only works while video is in active play back. Only tested with native |
| 827 | + * playback atm. |
| 828 | + */ |
| 829 | + switchPlaySrc: function( source ){ |
| 830 | + mw.log("Error: only native playback supports insertAndPlaySource right now"); |
| 831 | + }, |
| 832 | + |
| 833 | + /** |
| 834 | + * Set up the select source player |
| 835 | + * |
| 836 | + * issues autoSelectSource call |
| 837 | + * |
| 838 | + * Sets load error if no source is playable |
| 839 | + */ |
| 840 | + setupSourcePlayer: function() { |
| 841 | + mw.log("EmbedPlayer::setupSourcePlayer: " + this.id + ' sources: ' + this.mediaElement.sources.length ); |
| 842 | + |
| 843 | + // Autoseletct the media source |
| 844 | + this.mediaElement.autoSelectSource(); |
| 845 | + |
| 846 | + // Auto select player based on default order |
| 847 | + if ( !this.mediaElement.selectedSource ) { |
| 848 | + mw.log( 'setupSourcePlayer:: no sources, type:' + this.type ); |
| 849 | + } else { |
| 850 | + this.selectedPlayer = mw.EmbedTypes.getMediaPlayers().defaultPlayer( this.mediaElement.selectedSource.mimeType ); |
| 851 | + } |
| 852 | + |
| 853 | + if ( this.selectedPlayer ) { |
| 854 | + // Inherit the playback system of the selected player: |
| 855 | + this.inheritEmbedPlayer(); |
| 856 | + } else { |
| 857 | + this.showPluginMissingHTML(); |
| 858 | + } |
| 859 | + }, |
| 860 | + |
| 861 | + /** |
| 862 | + * Load and inherit methods from the selected player interface |
| 863 | + * |
| 864 | + * @param {Function} |
| 865 | + * callback Function to be called once playback-system has been |
| 866 | + * inherited |
| 867 | + */ |
| 868 | + inheritEmbedPlayer: function( callback ) { |
| 869 | + mw.log( "EmbedPlayer::inheritEmbedPlayer:duration is: " + this.getDuration() + ' p: ' + this.id ); |
| 870 | + |
| 871 | + // Clear out any non-base embedObj methods: |
| 872 | + if ( this.instanceOf ) { |
| 873 | + eval( 'var tmpObj = mw.EmbedPlayer' + this.instanceOf ); |
| 874 | + for ( var i in tmpObj ) { // for in loop oky for object |
| 875 | + if ( this[ 'parent_' + i ] ) { |
| 876 | + this[i] = this[ 'parent_' + i]; |
| 877 | + } else { |
| 878 | + this[i] = null; |
| 879 | + } |
| 880 | + } |
| 881 | + } |
| 882 | + |
| 883 | + // Set up the new embedObj |
| 884 | + mw.log( 'EmbedPlayer::inheritEmbedPlayer: embedding with ' + this.selectedPlayer.library ); |
| 885 | + var _this = this; |
| 886 | + |
| 887 | + // Load the selected player |
| 888 | + this.selectedPlayer.load( function() { |
| 889 | + mw.log( 'EmbedPlayer::inheritEmbedPlayer ' + _this.selectedPlayer.library + " player loaded for " + _this.id ); |
| 890 | + |
| 891 | + // Get embed library player Interface |
| 892 | + var playerInterface = mw[ 'EmbedPlayer' + _this.selectedPlayer.library ]; |
| 893 | + |
| 894 | + for ( var method in playerInterface ) { |
| 895 | + if ( _this[method] && !_this['parent_' + method] ) { |
| 896 | + _this['parent_' + method] = _this[method]; |
| 897 | + } |
| 898 | + _this[ method ] = playerInterface[method]; |
| 899 | + } |
| 900 | + |
| 901 | + // Update feature support |
| 902 | + _this.updateFeatureSupport(); |
| 903 | + |
| 904 | + _this.getDuration(); |
| 905 | + |
| 906 | + // Run player display with timeout to avoid function stacking |
| 907 | + setTimeout(function(){ |
| 908 | + _this.showPlayer(); |
| 909 | + |
| 910 | + // Run the callback if provided |
| 911 | + if ( typeof callback == 'function' ){ |
| 912 | + callback(); |
| 913 | + } |
| 914 | + },0); |
| 915 | + |
| 916 | + } ); |
| 917 | + }, |
| 918 | + |
| 919 | + /** |
| 920 | + * Select a player playback system |
| 921 | + * |
| 922 | + * @param {Object} |
| 923 | + * player Player playback system to be selected player playback |
| 924 | + * system include vlc, native, java etc. |
| 925 | + */ |
| 926 | + selectPlayer: function( player ) { |
| 927 | + var _this = this; |
| 928 | + if ( this.selectedPlayer.id != player.id ) { |
| 929 | + this.selectedPlayer = player; |
| 930 | + this.inheritEmbedPlayer( function(){ |
| 931 | + // Hide / remove track container |
| 932 | + _this.$interface.find( '.track' ).remove(); |
| 933 | + // We have to re-bind hoverIntent ( has to happen in this scope |
| 934 | + // ) |
| 935 | + if( _this.controls && _this.controlBuilder.checkOverlayControls() ){ |
| 936 | + _this.controlBuilder.showControlBar(); |
| 937 | + _this.$interface.hoverIntent({ |
| 938 | + 'sensitivity': 4, |
| 939 | + 'timeout' : 2000, |
| 940 | + 'over' : function(){ |
| 941 | + _this.controlBuilder.showControlBar(); |
| 942 | + }, |
| 943 | + 'out' : function(){ |
| 944 | + _this.controlBuilder.hideControlBar(); |
| 945 | + } |
| 946 | + }); |
| 947 | + } |
| 948 | + }); |
| 949 | + } |
| 950 | + }, |
| 951 | + |
| 952 | + /** |
| 953 | + * Get a time range from the media start and end time |
| 954 | + * |
| 955 | + * @return start_npt and end_npt time if present |
| 956 | + */ |
| 957 | + getTimeRange: function() { |
| 958 | + var end_time = (this.controlBuilder.longTimeDisp)? '/' + mw.seconds2npt( this.getDuration() ) : ''; |
| 959 | + var default_time_range = '0:00' + end_time; |
| 960 | + if ( !this.mediaElement ) |
| 961 | + return default_time_range; |
| 962 | + if ( !this.mediaElement.selectedSource ) |
| 963 | + return default_time_range; |
| 964 | + if ( !this.mediaElement.selectedSource.end_npt ) |
| 965 | + return default_time_range; |
| 966 | + return this.mediaElement.selectedSource.start_npt + this.mediaElement.selectedSource.end_npt; |
| 967 | + }, |
| 968 | + |
| 969 | + /** |
| 970 | + * Get the duration of the embed player |
| 971 | + */ |
| 972 | + getDuration: function() { |
| 973 | + return this.duration; |
| 974 | + }, |
| 975 | + |
| 976 | + /** |
| 977 | + * Get the player height |
| 978 | + */ |
| 979 | + getHeight: function() { |
| 980 | + return this.height; |
| 981 | + }, |
| 982 | + |
| 983 | + /** |
| 984 | + * Get the player width |
| 985 | + */ |
| 986 | + getWidth: function(){ |
| 987 | + return this.width; |
| 988 | + }, |
| 989 | + |
| 990 | + /** |
| 991 | + * Check if the selected source is an audio element: |
| 992 | + */ |
| 993 | + isAudio: function(){ |
| 994 | + return ( this.mediaElement.selectedSource.mimeType.indexOf('audio/') !== -1 ); |
| 995 | + }, |
| 996 | + |
| 997 | + /** |
| 998 | + * Get the plugin embed html ( should be implemented by embed player |
| 999 | + * interface ) |
| 1000 | + */ |
| 1001 | + doEmbedHTML: function() { |
| 1002 | + return 'Error: function doEmbedHTML should be implemented by embed player interface '; |
| 1003 | + }, |
| 1004 | + |
| 1005 | + /** |
| 1006 | + * Seek function ( should be implemented by embedPlayer interface |
| 1007 | + * playerNative, playerKplayer etc. ) embedPlayer doSeek only handles URL |
| 1008 | + * time seeks |
| 1009 | + */ |
| 1010 | + doSeek: function( percent ) { |
| 1011 | + var _this = this; |
| 1012 | + |
| 1013 | + this.seeking = true; |
| 1014 | + |
| 1015 | + // See if we should do a server side seek ( player independent ) |
| 1016 | + if ( this.supportsURLTimeEncoding() ) { |
| 1017 | + mw.log( 'EmbedPlayer::doSeek:: updated serverSeekTime: ' + mw.seconds2npt ( this.serverSeekTime ) + |
| 1018 | + ' currentTime: ' + _this.currentTime ); |
| 1019 | + // make sure we need to seek: |
| 1020 | + if( _this.currentTime == _this.serverSeekTime ){ |
| 1021 | + return ; |
| 1022 | + } |
| 1023 | + |
| 1024 | + this.stop(); |
| 1025 | + this.didSeekJump = true; |
| 1026 | + // Make sure this.serverSeekTime is up-to-date: |
| 1027 | + this.serverSeekTime = mw.npt2seconds( this.start_npt ) + parseFloat( percent * this.getDuration() ); |
| 1028 | + // Update the slider |
| 1029 | + this.updatePlayHead( percent ); |
| 1030 | + } |
| 1031 | + |
| 1032 | + // Do play request in 100ms ( give the dom time to swap out the embed player ) |
| 1033 | + setTimeout( function() { |
| 1034 | + _this.seeking = false; |
| 1035 | + _this.play(); |
| 1036 | + _this.monitor(); |
| 1037 | + }, 100 ); |
| 1038 | + |
| 1039 | + // Run the onSeeking interface update |
| 1040 | + // NOTE controlBuilder should really bind to html5 events rather |
| 1041 | + // than explicitly calling it or inheriting stuff. |
| 1042 | + this.controlBuilder.onSeek(); |
| 1043 | + }, |
| 1044 | + |
| 1045 | + /** |
| 1046 | + * Seeks to the requested time and issues a callback when ready (should be |
| 1047 | + * overwritten by client that supports frame serving) |
| 1048 | + */ |
| 1049 | + setCurrentTime: function( time, callback ) { |
| 1050 | + mw.log( 'Error: base embed setCurrentTime can not frame serve (override via plugin)' ); |
| 1051 | + }, |
| 1052 | + |
| 1053 | + /** |
| 1054 | + * On clip done action. Called once a clip is done playing |
| 1055 | + */ |
| 1056 | + onClipDone: function() { |
| 1057 | + var _this = this; |
| 1058 | + // don't run onclipdone if _propagateEvents is off |
| 1059 | + if( !_this._propagateEvents ){ |
| 1060 | + return ; |
| 1061 | + } |
| 1062 | + mw.log( 'EmbedPlayer::onClipDone:' + this.id + ' doneCount:' + this.donePlayingCount + ' stop state:' +this.isStopped() ); |
| 1063 | + // Only run stopped once: |
| 1064 | + if( !this.isStopped() ){ |
| 1065 | + // Stop the monitor and event propagation |
| 1066 | + this.stopEventPropagation(); |
| 1067 | + |
| 1068 | + // Show the control bar: |
| 1069 | + this.controlBuilder.showControlBar(); |
| 1070 | + |
| 1071 | + // Update the clip done playing count: |
| 1072 | + this.donePlayingCount ++; |
| 1073 | + |
| 1074 | + // Run the ended trigger ( allow the ended object to prevent default actions ) |
| 1075 | + mw.log("EmbedPlayer::onClipDone:Trigger ended"); |
| 1076 | + |
| 1077 | + // TOOD we should improve the end event flow |
| 1078 | + $( this ).trigger( 'ended' ); |
| 1079 | + |
| 1080 | + // if the ended event did not trigger more timeline actions run the actual stop: |
| 1081 | + if( this.onDoneInterfaceFlag ){ |
| 1082 | + this.stop(); |
| 1083 | + // restore event propagation |
| 1084 | + this.restoreEventPropagation(); |
| 1085 | + // Check if we have the "loop" property set |
| 1086 | + if( this.loop ) { |
| 1087 | + this.play(); |
| 1088 | + return; |
| 1089 | + } |
| 1090 | + |
| 1091 | + // Stop the clip (load the thumbnail etc) |
| 1092 | + this.serverSeekTime = 0; |
| 1093 | + this.updatePlayHead( 0 ); |
| 1094 | + |
| 1095 | + // Make sure we are not in preview mode( no end clip actions in |
| 1096 | + // preview mode) |
| 1097 | + if ( this.previewMode ) { |
| 1098 | + return ; |
| 1099 | + } |
| 1100 | + |
| 1101 | + // Do the controlBuilder onClip done interface |
| 1102 | + this.controlBuilder.onClipDone(); |
| 1103 | + } |
| 1104 | + } |
| 1105 | + }, |
| 1106 | + |
| 1107 | + |
| 1108 | + /** |
| 1109 | + * Shows the video Thumbnail, updates pause state |
| 1110 | + */ |
| 1111 | + showThumbnail: function() { |
| 1112 | + var _this = this; |
| 1113 | + mw.log( 'EmbedPlayer::showThumbnail' + this.posterDisplayed ); |
| 1114 | + |
| 1115 | + // Close Menu Overlay: |
| 1116 | + this.controlBuilder.closeMenuOverlay(); |
| 1117 | + |
| 1118 | + // update the thumbnail html: |
| 1119 | + this.updatePosterHTML(); |
| 1120 | + |
| 1121 | + this.paused = true; |
| 1122 | + this.posterDisplayed = true; |
| 1123 | + // Make sure the controlBuilder bindings are up-to-date |
| 1124 | + this.controlBuilder.addControlBindings(); |
| 1125 | + |
| 1126 | + // Once the thumbnail is shown run the mediaReady trigger (if not using native controls) |
| 1127 | + // Native controls has a native mediaLoaded event |
| 1128 | + if( !this.useNativePlayerControls() ){ |
| 1129 | + mw.log("mediaLoaded"); |
| 1130 | + $( this ).trigger( 'mediaLoaded' ); |
| 1131 | + } |
| 1132 | + }, |
| 1133 | + |
| 1134 | + /** |
| 1135 | + * Show the player |
| 1136 | + */ |
| 1137 | + showPlayer: function () { |
| 1138 | + mw.log( 'EmbedPlayer:: Show player: ' + this.id + ' interace: w:' + this.width + ' h:' + this.height ); |
| 1139 | + var _this = this; |
| 1140 | + // Set-up the local controlBuilder instance: |
| 1141 | + this.controlBuilder = new mw.PlayerControlBuilder( this ); |
| 1142 | + var _this = this; |
| 1143 | + |
| 1144 | + // Remove loader |
| 1145 | + $('#loadingSpinner_' + this.id ).remove(); |
| 1146 | + |
| 1147 | + // Make sure we have mwplayer_interface |
| 1148 | + if( $( this ).parent( '.mwplayer_interface' ).length == 0 ) { |
| 1149 | + // Select "player" |
| 1150 | + $( this ).wrap( |
| 1151 | + $('<div>') |
| 1152 | + .addClass( 'mwplayer_interface ' + this.controlBuilder.playerClass ) |
| 1153 | + .css({ |
| 1154 | + 'width' : this.width + 'px', |
| 1155 | + 'height' : this.height + 'px', |
| 1156 | + 'position' : 'relative' |
| 1157 | + }) |
| 1158 | + ) |
| 1159 | + // position the "player" absolute inside the relative interface |
| 1160 | + // parent: |
| 1161 | + .css('position', 'absolute'); |
| 1162 | + } |
| 1163 | + |
| 1164 | + // Set up local jQuery object reference to "mwplayer_interface" |
| 1165 | + this.$interface = $( this ).parent( '.mwplayer_interface' ); |
| 1166 | + // If a isPersistentNativePlayer ( overlay the controls ) |
| 1167 | + if( !this.useNativePlayerControls() && this.isPersistentNativePlayer() ){ |
| 1168 | + this.$interface.css({ |
| 1169 | + 'position' : 'absolute', |
| 1170 | + 'top' : '0px', |
| 1171 | + 'left' : '0px', |
| 1172 | + 'background': null |
| 1173 | + }); |
| 1174 | + |
| 1175 | + if( this.isPersistentNativePlayer() && !_this.controlBuilder.checkOverlayControls() ){ |
| 1176 | + // if Persistent native player always give it the player height |
| 1177 | + $('#' + this.pid ).css('height', this.height - _this.controlBuilder.height ); |
| 1178 | + } |
| 1179 | + $( this ).show(); |
| 1180 | + this.controls = true; |
| 1181 | + }; |
| 1182 | + |
| 1183 | + if( !this.useNativePlayerControls() && !this.isPersistentNativePlayer() && !_this.controlBuilder.checkOverlayControls() ){ |
| 1184 | + // Update the video size per available control space. |
| 1185 | + $(this).css('height', this.height - _this.controlBuilder.height ); |
| 1186 | + } |
| 1187 | + |
| 1188 | + // Update Thumbnail for the "player" |
| 1189 | + this.updatePosterHTML(); |
| 1190 | + |
| 1191 | + // Add controls if enabled: |
| 1192 | + if ( this.controls ) { |
| 1193 | + this.controlBuilder.addControls(); |
| 1194 | + } else { |
| 1195 | + // Need to think about this some more... |
| 1196 | + // Interface is hidden if controls are "off" |
| 1197 | + this.$interface.hide(); |
| 1198 | + } |
| 1199 | + |
| 1200 | + // Update temporal url if present |
| 1201 | + this.updateTemporalUrl(); |
| 1202 | + |
| 1203 | + |
| 1204 | + if ( this.autoplay ) { |
| 1205 | + mw.log( 'EmbedPlayer::showPlayer::activating autoplay' ); |
| 1206 | + // Issue a non-blocking play request |
| 1207 | + setTimeout(function(){ |
| 1208 | + _this.play(); |
| 1209 | + },1); |
| 1210 | + } |
| 1211 | + |
| 1212 | + }, |
| 1213 | + /** |
| 1214 | + * Media fragments handler based on: |
| 1215 | + * http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#fragment-dimensions |
| 1216 | + * |
| 1217 | + * We support seconds and npt ( normal play time ) |
| 1218 | + * |
| 1219 | + * Updates the player per fragment url info if present |
| 1220 | + * |
| 1221 | + */ |
| 1222 | + updateTemporalUrl: function(){ |
| 1223 | + var sourceHash = /[^\#]+$/.exec( this.getSrc() ).toString(); |
| 1224 | + if( sourceHash.indexOf('t=') === 0 ){ |
| 1225 | + // parse the times |
| 1226 | + var times = sourceHash.substr(2).split(','); |
| 1227 | + if( times[0] ){ |
| 1228 | + // update the current time |
| 1229 | + this.currentTime = mw.npt2seconds( times[0].toString() ); |
| 1230 | + } |
| 1231 | + if( times[1] ){ |
| 1232 | + this.pauseTime = mw.npt2seconds( times[1].toString() ); |
| 1233 | + // ignore invalid ranges: |
| 1234 | + if( this.pauseTime < this.currentTime ){ |
| 1235 | + this.pauseTime = null; |
| 1236 | + } |
| 1237 | + } |
| 1238 | + // Update the play head |
| 1239 | + this.updatePlayHead( this.currentTime / this.duration ); |
| 1240 | + // Update status: |
| 1241 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) ); |
| 1242 | + } |
| 1243 | + }, |
| 1244 | + /** |
| 1245 | + * Get missing plugin html (check for user included code) |
| 1246 | + * |
| 1247 | + * @param {String} |
| 1248 | + * [misssingType] missing type mime |
| 1249 | + */ |
| 1250 | + showPluginMissingHTML: function( ) { |
| 1251 | + mw.log("EmbedPlayer::showPluginMissingHTML"); |
| 1252 | + // Control builder ( for play button ) |
| 1253 | + this.controlBuilder = new mw.PlayerControlBuilder( this ); |
| 1254 | + |
| 1255 | + this.controlBuilder.doWarningBindinng( 'EmbedPlayer.DirectFileLinkWarning', |
| 1256 | + gM( 'mwe-embedplayer-download-warn', mw.getConfig('EmbedPlayer.FirefoxLink') ) |
| 1257 | + ); |
| 1258 | + // Hide loader |
| 1259 | + $('#loadingSpinner_' + this.id ).remove(); |
| 1260 | + $(this).css('position', 'relative'); |
| 1261 | + |
| 1262 | + // Get mime type for un-supported formats: |
| 1263 | + this.updatePosterHTML(); |
| 1264 | + |
| 1265 | + // Set the play button to the first available source: |
| 1266 | + $(this).find('.play-btn-large') |
| 1267 | + .unbind('click') |
| 1268 | + .wrap( |
| 1269 | + $('<a />').attr( { |
| 1270 | + 'href': this.mediaElement.sources[0].getSrc(), |
| 1271 | + 'title' : gM('mwe-embedplayer-play_clip') |
| 1272 | + } ) |
| 1273 | + ); |
| 1274 | + }, |
| 1275 | + |
| 1276 | + /** |
| 1277 | + * Update the video time request via a time request string |
| 1278 | + * |
| 1279 | + * @param {String} |
| 1280 | + * time_req |
| 1281 | + */ |
| 1282 | + updateVideoTimeReq: function( time_req ) { |
| 1283 | + mw.log( 'EmbedPlayer::updateVideoTimeReq:' + time_req ); |
| 1284 | + var time_parts = time_req.split( '/' ); |
| 1285 | + this.updateVideoTime( time_parts[0], time_parts[1] ); |
| 1286 | + }, |
| 1287 | + |
| 1288 | + /** |
| 1289 | + * Update Video time from provided start_npt and end_npt values |
| 1290 | + * |
| 1291 | + * @param {String} |
| 1292 | + * start_npt the new start time in npt format |
| 1293 | + * @pamra {String} end_npt the new end time in npt format |
| 1294 | + */ |
| 1295 | + updateVideoTime: function( start_npt, end_npt ) { |
| 1296 | + // update media |
| 1297 | + this.mediaElement.updateSourceTimes( start_npt, end_npt ); |
| 1298 | + |
| 1299 | + // update mv_time |
| 1300 | + this.controlBuilder.setStatus( start_npt + '/' + end_npt ); |
| 1301 | + |
| 1302 | + // reset slider |
| 1303 | + this.updatePlayHead( 0 ); |
| 1304 | + |
| 1305 | + // reset seek_offset: |
| 1306 | + if ( this.mediaElement.selectedSource.URLTimeEncoding ) { |
| 1307 | + this.serverSeekTime = 0; |
| 1308 | + } else { |
| 1309 | + this.serverSeekTime = mw.npt2seconds( start_npt ); |
| 1310 | + } |
| 1311 | + }, |
| 1312 | + |
| 1313 | + |
| 1314 | + /** |
| 1315 | + * Update Thumb time with npt formated time |
| 1316 | + * |
| 1317 | + * @param {String} |
| 1318 | + * time NPT formated time to update thumbnail |
| 1319 | + */ |
| 1320 | + updateThumbTimeNPT: function( time ) { |
| 1321 | + this.updateThumbTime( mw.npt2seconds( time ) - parseInt( this.startOffset ) ); |
| 1322 | + }, |
| 1323 | + |
| 1324 | + /** |
| 1325 | + * Update the thumb with a new time |
| 1326 | + * |
| 1327 | + * @param {Float} |
| 1328 | + * floatSeconds Time to update the thumb to |
| 1329 | + */ |
| 1330 | + updateThumbTime:function( floatSeconds ) { |
| 1331 | + // mw.log('updateThumbTime:'+floatSeconds); |
| 1332 | + var _this = this; |
| 1333 | + if ( typeof this.org_thum_src == 'undefined' ) { |
| 1334 | + this.org_thum_src = this.poster; |
| 1335 | + } |
| 1336 | + if ( this.org_thum_src.indexOf( 't=' ) !== -1 ) { |
| 1337 | + this.last_thumb_url = mw.replaceUrlParams( this.org_thum_src, |
| 1338 | + { |
| 1339 | + 't' : mw.seconds2npt( floatSeconds + parseInt( this.startOffset ) ) |
| 1340 | + } |
| 1341 | + ); |
| 1342 | + if ( !this.thumbnail_updating ) { |
| 1343 | + this.updatePoster( this.last_thumb_url , false ); |
| 1344 | + this.last_thumb_url = null; |
| 1345 | + } |
| 1346 | + } |
| 1347 | + }, |
| 1348 | + |
| 1349 | + /** |
| 1350 | + * Updates the displayed thumbnail via percent of the stream |
| 1351 | + * |
| 1352 | + * @param {Float} |
| 1353 | + * percent Percent of duration to update thumb |
| 1354 | + */ |
| 1355 | + updateThumbPerc:function( percent ) { |
| 1356 | + return this.updateThumbTime( ( this.getDuration() * percent ) ); |
| 1357 | + }, |
| 1358 | + |
| 1359 | + /** |
| 1360 | + * Updates the thumbnail if the thumbnail is being displayed |
| 1361 | + * |
| 1362 | + * @param {String} |
| 1363 | + * src New src of thumbnail |
| 1364 | + * @param {Boolean} |
| 1365 | + * quick_switch true switch happens instantly false / undefined |
| 1366 | + * animated cross fade |
| 1367 | + */ |
| 1368 | + updatePosterSrc: function( src, quick_switch ) { |
| 1369 | + // make sure we don't go to the same url if we are not already updating: |
| 1370 | + if ( !this.thumbnail_updating && $( '#img_thumb_' + this.id ).attr( 'src' ) == src ) |
| 1371 | + return false; |
| 1372 | + // if we are already updating don't issue a new update: |
| 1373 | + if ( this.thumbnail_updating && $( '#new_img_thumb_' + this.id ).attr( 'src' ) == src ) |
| 1374 | + return false; |
| 1375 | + |
| 1376 | + mw.log( 'update thumb: ' + src ); |
| 1377 | + |
| 1378 | + if ( quick_switch ) { |
| 1379 | + $( '#img_thumb_' + this.id ).attr( 'src', src ); |
| 1380 | + } else { |
| 1381 | + var _this = this; |
| 1382 | + // if still animating remove new_img_thumb_ |
| 1383 | + if ( this.thumbnail_updating == true ) |
| 1384 | + $( '#new_img_thumb_' + this.id ).stop().remove(); |
| 1385 | + |
| 1386 | + if ( this.posterDisplayed ) { |
| 1387 | + mw.log( 'set to thumb:' + src ); |
| 1388 | + this.thumbnail_updating = true; |
| 1389 | + $( this ).append( |
| 1390 | + $('<img />') |
| 1391 | + .attr({ |
| 1392 | + 'src' : src, |
| 1393 | + 'id' : 'new_img_thumb_' + this.id, |
| 1394 | + 'width' : this.width, |
| 1395 | + 'height': this.height |
| 1396 | + }) |
| 1397 | + .css( { |
| 1398 | + 'display' : 'none', |
| 1399 | + 'position' : 'absolute', |
| 1400 | + 'z-index' : 2, |
| 1401 | + 'top' : '0px', |
| 1402 | + 'left' : '0px' |
| 1403 | + }) |
| 1404 | + ); |
| 1405 | + // mw.log('appended: new_img_thumb_'); |
| 1406 | + $( '#new_img_thumb_' + this.id ).fadeIn( "slow", function() { |
| 1407 | + // once faded in remove org and rename new: |
| 1408 | + $( '#img_thumb_' + _this.id ).remove(); |
| 1409 | + $( '#new_img_thumb_' + _this.id ).attr( 'id', 'img_thumb_' + _this.id ); |
| 1410 | + $( '#img_thumb_' + _this.id ).css( 'z-index', '1' ); |
| 1411 | + _this.thumbnail_updating = false; |
| 1412 | + // mw.log("done fadding in "+ |
| 1413 | + // $('#img_thumb_'+_this.id).attr("src")); |
| 1414 | + |
| 1415 | + // if we have a thumb queued update to that |
| 1416 | + if ( _this.last_thumb_url ) { |
| 1417 | + var src_url = _this.last_thumb_url; |
| 1418 | + _this.last_thumb_url = null; |
| 1419 | + _this.updatePosterSrc( src_url ); |
| 1420 | + } |
| 1421 | + } ); |
| 1422 | + } |
| 1423 | + } |
| 1424 | + }, |
| 1425 | + // update the video poster: |
| 1426 | + updatePosterSrc: function( posterSrc ){ |
| 1427 | + this.poster = posterSrc; |
| 1428 | + }, |
| 1429 | + /** |
| 1430 | + * Returns the HTML code for the video when it is in thumbnail mode. |
| 1431 | + * playing, configuring the player, inline cmml display, HTML linkback, |
| 1432 | + * download, and embed code. |
| 1433 | + */ |
| 1434 | + updatePosterHTML: function () { |
| 1435 | + mw.log( 'EmbedPlayer:updatePosterHTML::' + this.id ); |
| 1436 | + var thumb_html = ''; |
| 1437 | + var class_atr = ''; |
| 1438 | + var style_atr = ''; |
| 1439 | + |
| 1440 | + if( this.useNativePlayerControls() ){ |
| 1441 | + this.showNativePlayer(); |
| 1442 | + return ; |
| 1443 | + } |
| 1444 | + |
| 1445 | + // Set by default thumb value if not found |
| 1446 | + var posterSrc = ( this.poster ) ? this.poster : |
| 1447 | + mw.getConfig( 'imagesPath' ) + 'vid_default_thumb.jpg'; |
| 1448 | + |
| 1449 | + // Update PersistentNativePlayer poster: |
| 1450 | + if( this.isPersistentNativePlayer() ){ |
| 1451 | + $( '#' + this.pid ).attr('poster', posterSrc); |
| 1452 | + } else { |
| 1453 | + // Poster support is not very consistent in browsers |
| 1454 | + // use a jpg poster image: |
| 1455 | + $( this ).html( |
| 1456 | + $( '<img />' ) |
| 1457 | + .css({ |
| 1458 | + 'position' : 'relative', |
| 1459 | + 'width' : '100%', |
| 1460 | + 'height' : '100%' |
| 1461 | + }) |
| 1462 | + .attr({ |
| 1463 | + 'id' : 'img_thumb_' + this.id, |
| 1464 | + 'src' : posterSrc |
| 1465 | + }) |
| 1466 | + .addClass( 'playerPoster' ) |
| 1467 | + ); |
| 1468 | + } |
| 1469 | + if ( this.controls && this.controlBuilder |
| 1470 | + && this.height > this.controlBuilder.getComponentHeight( 'playButtonLarge' ) |
| 1471 | + ) { |
| 1472 | + $( this ).append( |
| 1473 | + this.controlBuilder.getComponent( 'playButtonLarge' ) |
| 1474 | + ); |
| 1475 | + } |
| 1476 | + }, |
| 1477 | + |
| 1478 | + /** |
| 1479 | + * Checks if native controls should be used |
| 1480 | + * |
| 1481 | + * @param [player] |
| 1482 | + * Object Optional player object to check controls attribute |
| 1483 | + * @returns boolean true if the mwEmbed player interface should be used |
| 1484 | + * false if the mwEmbed player interface should not be used |
| 1485 | + */ |
| 1486 | + useNativePlayerControls: function() { |
| 1487 | + |
| 1488 | + if( this.usenativecontrols === true ){ |
| 1489 | + return true; |
| 1490 | + } |
| 1491 | + if( mw.getConfig('EmbedPlayer.NativeControls') === true ) { |
| 1492 | + return true; |
| 1493 | + } |
| 1494 | + |
| 1495 | + // Do some device detection devices that don't support overlays |
| 1496 | + // and go into full screen once play is clicked: |
| 1497 | + if( mw.isAndroid2() || mw.isIpod() || mw.isIphone() ){ |
| 1498 | + return true; |
| 1499 | + } |
| 1500 | + // iPad can use html controls if its a persistantPlayer in the dom before loading ) |
| 1501 | + // else it needs to use native controls: |
| 1502 | + if( mw.isIpad() ){ |
| 1503 | + if( this.isPersistentNativePlayer() && mw.getConfig('EmbedPlayer.EnableIpadHTMLControls') ){ |
| 1504 | + return false; |
| 1505 | + } else { |
| 1506 | + // Set warning that your trying to do iPad controls without persistent native player: |
| 1507 | + if( mw.getConfig('EmbedPlayer.EnableIpadHTMLControls') ){ |
| 1508 | + mw.log("Error:: Trying to set EnableIpadHTMLControls without Persistent Native Player"); |
| 1509 | + } |
| 1510 | + return true; |
| 1511 | + } |
| 1512 | + } |
| 1513 | + return false; |
| 1514 | + }, |
| 1515 | + |
| 1516 | + isPersistentNativePlayer: function(){ |
| 1517 | + return $('#' + this.pid ).hasClass('persistentNativePlayer'); |
| 1518 | + }, |
| 1519 | + |
| 1520 | + |
| 1521 | + /** |
| 1522 | + * Show the native player embed code |
| 1523 | + * |
| 1524 | + * This is for cases where the main library needs to "get out of the way" |
| 1525 | + * since the device only supports a limited subset of the html5 and won't |
| 1526 | + * work with an html javascirpt interface |
| 1527 | + */ |
| 1528 | + showNativePlayer: function(){ |
| 1529 | + var _this = this; |
| 1530 | + // Empty the player of any child nodes |
| 1531 | + $(this).empty(); |
| 1532 | + |
| 1533 | + // Remove the player loader spinner if it exists |
| 1534 | + $('#loadingSpinner_' + this.id ).remove(); |
| 1535 | + |
| 1536 | + // Setup the source |
| 1537 | + var source = this.mediaElement.getSources( 'video/h264' )[0]; |
| 1538 | + // Support fake user agent |
| 1539 | + if( !source || !source.src ){ |
| 1540 | + mw.log( 'Warning: Your probably fakeing the iPhone userAgent ( no h.264 source )' ); |
| 1541 | + source = this.mediaElement.getSources( 'video/ogg' )[0]; |
| 1542 | + } |
| 1543 | + |
| 1544 | + // Setup videoAttribues |
| 1545 | + var videoAttribues = { |
| 1546 | + 'poster': _this.poster, |
| 1547 | + 'src' : source.src, |
| 1548 | + 'controls' : 'true' |
| 1549 | + }; |
| 1550 | + if( this.loop ){ |
| 1551 | + videoAttribues[ 'loop' ] = 'true'; |
| 1552 | + } |
| 1553 | + var cssStyle = { |
| 1554 | + 'width' : _this.width, |
| 1555 | + 'height' : _this.height |
| 1556 | + }; |
| 1557 | + |
| 1558 | + // If not a persistentNativePlayer swap the video tag |
| 1559 | + // completely instead of just updating properties: |
| 1560 | + $( '#' + this.pid ).replaceWith( |
| 1561 | + _this.getNativePlayerHtml( videoAttribues, cssStyle ) |
| 1562 | + ); |
| 1563 | + |
| 1564 | + // Bind native events: |
| 1565 | + this.applyMediaElementBindings(); |
| 1566 | + |
| 1567 | + // Android only can play with a special play button ( no native controls |
| 1568 | + // persistentNativePlayer has no controls: |
| 1569 | + if( mw.isAndroid2() ){ |
| 1570 | + this.addPlayBtnLarge(); |
| 1571 | + } |
| 1572 | + return ; |
| 1573 | + }, |
| 1574 | + addPlayBtnLarge:function(){ |
| 1575 | + var _this = this; |
| 1576 | + var $pid = $( '#' + _this.pid ); |
| 1577 | + $pid.siblings('.play-btn-large').remove(); |
| 1578 | + $playButton = this.controlBuilder.getComponent('playButtonLarge'); |
| 1579 | + $pid.after( |
| 1580 | + $playButton |
| 1581 | + .css({ |
| 1582 | + 'left' : parseInt( $pid.position().left ) + parseInt( $playButton.css('left') ), |
| 1583 | + 'top' : parseInt( $pid.position().top ) + parseInt( $playButton.css('top') ) |
| 1584 | + }) |
| 1585 | + ); |
| 1586 | + }, |
| 1587 | + /** |
| 1588 | + * Should be set via native embed support |
| 1589 | + */ |
| 1590 | + getNativePlayerHtml: function(){ |
| 1591 | + return $('<div />' ) |
| 1592 | + .css( 'width', this.getWidth() ) |
| 1593 | + .html( 'Error: Trying to get native html5 player without native support for codec' ); |
| 1594 | + }, |
| 1595 | + /** |
| 1596 | + * Should be set via native embed support |
| 1597 | + */ |
| 1598 | + applyMediaElementBindings: function(){ |
| 1599 | + return ; |
| 1600 | + }, |
| 1601 | + |
| 1602 | + /** |
| 1603 | + * Gets code to embed the player remotely for "share" this player links |
| 1604 | + */ |
| 1605 | + getEmbeddingHTML: function() { |
| 1606 | + switch( mw.getConfig( 'EmbedPlayer.ShareEmbedMode' ) ){ |
| 1607 | + case 'iframe': |
| 1608 | + return this.getShareIframeObject(); |
| 1609 | + break; |
| 1610 | + case 'videojs': |
| 1611 | + return this.getShareEmbedVideoJs(); |
| 1612 | + break; |
| 1613 | + } |
| 1614 | + }, |
| 1615 | + |
| 1616 | + /** |
| 1617 | + * Get the share embed object code |
| 1618 | + * |
| 1619 | + * NOTE this could probably share a bit more code with getShareEmbedVideoJs |
| 1620 | + */ |
| 1621 | + getShareIframeObject: function(){ |
| 1622 | + // allow modules to generate the iframe: |
| 1623 | + var iframeEmbedCode ={}; |
| 1624 | + $( this ).trigger( 'GetShareIframeCode', [ iframeEmbedCode ] ); |
| 1625 | + if( iframeEmbedCode.code ){ |
| 1626 | + return frameEmbedCode.code; |
| 1627 | + } |
| 1628 | + |
| 1629 | + // old style embed: |
| 1630 | + var iframeUrl = mw.getMwEmbedPath() + 'mwEmbedFrame.php?'; |
| 1631 | + var params = {'src[]':[]}; |
| 1632 | + |
| 1633 | + |
| 1634 | + // Output all the video sources: |
| 1635 | + for( var i=0; i < this.mediaElement.sources.length; i++ ){ |
| 1636 | + var source = this.mediaElement.sources[i]; |
| 1637 | + if( source.src ) { |
| 1638 | + params['src[]'].push(mw.absoluteUrl( source.src )); |
| 1639 | + } |
| 1640 | + } |
| 1641 | + // Output the poster attr |
| 1642 | + if( this.poster ){ |
| 1643 | + params.poster = this.poster; |
| 1644 | + } |
| 1645 | + |
| 1646 | + |
| 1647 | + // Set the skin if set to something other than default |
| 1648 | + if( this.skinName ){ |
| 1649 | + params.skin = this.skinName; |
| 1650 | + } |
| 1651 | + |
| 1652 | + if( this.duration ) { |
| 1653 | + params['data-durationhint'] = parseFloat( this.duration ); |
| 1654 | + } |
| 1655 | + iframeUrl += $j.param( params ); |
| 1656 | + |
| 1657 | + // Set up embedFrame src path |
| 1658 | + var embedCode = '<iframe src="' + mw.html.escape( iframeUrl ) + '" '; |
| 1659 | + |
| 1660 | + // Set width / height of embed object |
| 1661 | + embedCode += 'width="' + this.getPlayerWidth() +'" '; |
| 1662 | + embedCode += 'height="' + this.getPlayerHeight() + '" '; |
| 1663 | + embedCode += 'frameborder="0" '; |
| 1664 | + |
| 1665 | + // Close up the embedCode tag: |
| 1666 | + embedCode+='></iframe>'; |
| 1667 | + |
| 1668 | + // Return the embed code |
| 1669 | + return embedCode; |
| 1670 | + }, |
| 1671 | + |
| 1672 | + /** |
| 1673 | + * Get the share embed Video tag code |
| 1674 | + */ |
| 1675 | + getShareEmbedVideoJs: function(){ |
| 1676 | + |
| 1677 | + // Set the embed tag type: |
| 1678 | + var embedtag = ( this.isAudio() )? 'audio': 'video'; |
| 1679 | + |
| 1680 | + // Set up the mwEmbed js include: |
| 1681 | + var embedCode = '<script type="text/javascript" ' + |
| 1682 | + 'src="' + |
| 1683 | + mw.html.escape( |
| 1684 | + mw.absoluteUrl( |
| 1685 | + mw.getMwEmbedSrc() |
| 1686 | + ) |
| 1687 | + ) + '"></script>' + |
| 1688 | + '<' + embedtag + ' '; |
| 1689 | + |
| 1690 | + if( this.poster ) { |
| 1691 | + embedCode += 'poster="' + |
| 1692 | + mw.html.escape( mw.absoluteUrl( this.poster ) ) + |
| 1693 | + '" '; |
| 1694 | + } |
| 1695 | + |
| 1696 | + // Set the skin if set to something other than default |
| 1697 | + if( this.skinName ){ |
| 1698 | + embedCode += 'class="' + |
| 1699 | + mw.html.escape( this.skinName ) + |
| 1700 | + '" '; |
| 1701 | + } |
| 1702 | + |
| 1703 | + if( this.duration ) { |
| 1704 | + embedCode +='data-durationhint="' + parseFloat( this.duration ) + '" '; |
| 1705 | + } |
| 1706 | + |
| 1707 | + if( this.width || this.height ){ |
| 1708 | + embedCode +='style="'; |
| 1709 | + embedCode += ( this.width )? 'width:' + this.width +'px;': ''; |
| 1710 | + embedCode += ( this.height )? 'height:' + this.height +'px;': ''; |
| 1711 | + embedCode += '" '; |
| 1712 | + } |
| 1713 | + |
| 1714 | + // TODO move to mediaWiki Support module |
| 1715 | + if( this.apiTitleKey ) { |
| 1716 | + embedCode += 'apiTitleKey="' + mw.html.escape( this.apiTitleKey ) + '" '; |
| 1717 | + if ( this.apiProvider ) { |
| 1718 | + embedCode += 'apiProvider="' + mw.html.escape( this.apiProvider ) + '" '; |
| 1719 | + } |
| 1720 | + // close the video tag |
| 1721 | + embedCode += '></video>'; |
| 1722 | + |
| 1723 | + } else { |
| 1724 | + // Close the video attr |
| 1725 | + embedCode += '>'; |
| 1726 | + |
| 1727 | + // Output all the video sources: |
| 1728 | + for( var i=0; i < this.mediaElement.sources.length; i++ ){ |
| 1729 | + var source = this.mediaElement.sources[i]; |
| 1730 | + if( source.src ) { |
| 1731 | + embedCode +='<source src="' + |
| 1732 | + mw.absoluteUrl( source.src ) + |
| 1733 | + '" ></source>'; |
| 1734 | + } |
| 1735 | + } |
| 1736 | + // Close the video tag |
| 1737 | + embedCode += '</video>'; |
| 1738 | + } |
| 1739 | + |
| 1740 | + return embedCode; |
| 1741 | + }, |
| 1742 | + |
| 1743 | + /** |
| 1744 | + * Base Embed Controls |
| 1745 | + */ |
| 1746 | + |
| 1747 | + /** |
| 1748 | + * The Play Action |
| 1749 | + * |
| 1750 | + * Handles play requests, updates relevant states: seeking =false paused = |
| 1751 | + * false Updates pause button Starts the "monitor" |
| 1752 | + */ |
| 1753 | + play: function() { |
| 1754 | + var _this = this; |
| 1755 | + mw.log( "EmbedPlayer:: play: " + this._propagateEvents ); |
| 1756 | + if( ! this._propagateEvents ){ |
| 1757 | + return ; |
| 1758 | + } |
| 1759 | + // Hide any overlay: |
| 1760 | + this.controlBuilder.closeMenuOverlay(); |
| 1761 | + |
| 1762 | + // Check if thumbnail is being displayed and embed html |
| 1763 | + if ( this.posterDisplayed ) { |
| 1764 | + if ( !this.selectedPlayer ) { |
| 1765 | + this.showPluginMissingHTML(); |
| 1766 | + return; |
| 1767 | + } else { |
| 1768 | + this.posterDisplayed = false; |
| 1769 | + // Hide any button if present: |
| 1770 | + this.$interface.find( '.play-btn-large' ).remove(); |
| 1771 | + this.doEmbedHTML(); |
| 1772 | + } |
| 1773 | + } |
| 1774 | + |
| 1775 | + if( this.paused === true ){ |
| 1776 | + this.paused = false; |
| 1777 | + // Check if we should Trigger the play event |
| 1778 | + mw.log("EmbedPlayer:: trigger play even::" + !this.paused); |
| 1779 | + if( ! this.doMethodsAutoTrigger() ) { |
| 1780 | + $( this ).trigger( 'play' ); |
| 1781 | + } |
| 1782 | + } |
| 1783 | + |
| 1784 | + // If we previously finished playing this clip run the "replay hook" |
| 1785 | + if( this.donePlayingCount > 0 && !this.paused) { |
| 1786 | + mw.log("replayEvent"); |
| 1787 | + $( this ).trigger( 'replayEvent' ); |
| 1788 | + } |
| 1789 | + |
| 1790 | + this.$interface.find('.play-btn span') |
| 1791 | + .removeClass( 'ui-icon-play' ) |
| 1792 | + .addClass( 'ui-icon-pause' ); |
| 1793 | + |
| 1794 | + this.$interface.find( '.play-btn' ) |
| 1795 | + .unbind() |
| 1796 | + .buttonHover() |
| 1797 | + .click( function( ) { |
| 1798 | + _this.pause(); |
| 1799 | + } ) |
| 1800 | + .attr( 'title', gM( 'mwe-embedplayer-pause_clip' ) ); |
| 1801 | + |
| 1802 | + // Start the monitor if not already started |
| 1803 | + this.monitor(); |
| 1804 | + }, |
| 1805 | + /** |
| 1806 | + * Base embed pause Updates the play/pause button state. |
| 1807 | + * |
| 1808 | + * There is no general way to pause the video must be overwritten by embed |
| 1809 | + * object to support this functionality. |
| 1810 | + */ |
| 1811 | + pause: function( event ) { |
| 1812 | + var _this = this; |
| 1813 | + // Trigger the pause event if not already paused and using native |
| 1814 | + // controls: |
| 1815 | + if( this.paused === false ){ |
| 1816 | + this.paused = true; |
| 1817 | + mw.log('EmbedPlayer:trigger pause:' + this.paused); |
| 1818 | + if( ! this.doMethodsAutoTrigger() ){ |
| 1819 | + $( this ).trigger( 'pause' ); |
| 1820 | + } |
| 1821 | + } |
| 1822 | + |
| 1823 | + // update the ctrl "paused state" |
| 1824 | + this.$interface.find('.play-btn span' ) |
| 1825 | + .removeClass( 'ui-icon-pause' ) |
| 1826 | + .addClass( 'ui-icon-play' ); |
| 1827 | + |
| 1828 | + this.$interface.find('.play-btn' ) |
| 1829 | + .unbind('click') |
| 1830 | + .buttonHover() |
| 1831 | + .click( function() { |
| 1832 | + _this.play(); |
| 1833 | + } ) |
| 1834 | + .attr( 'title', gM( 'mwe-embedplayer-play_clip' ) ); |
| 1835 | + }, |
| 1836 | + // special per browser check for autoTrigger events |
| 1837 | + // ideally jQuery would not have this inconsistency. |
| 1838 | + doMethodsAutoTrigger: function(){ |
| 1839 | + if( $j.browser.mozilla && ! mw.versionIsAtLeast('2.0', $j.browser.version ) ){ |
| 1840 | + return true; |
| 1841 | + } |
| 1842 | + return false; |
| 1843 | + }, |
| 1844 | + |
| 1845 | + /** |
| 1846 | + * Maps the html5 load request. There is no general way to "load" clips so |
| 1847 | + * underling plugin-player libs should override. |
| 1848 | + */ |
| 1849 | + load: function() { |
| 1850 | + // should be done by child (no base way to pre-buffer video) |
| 1851 | + mw.log( 'baseEmbed:load call' ); |
| 1852 | + }, |
| 1853 | + |
| 1854 | + |
| 1855 | + /** |
| 1856 | + * Base embed stop |
| 1857 | + * |
| 1858 | + * Updates the player to the stop state shows Thumbnail resets Buffer resets |
| 1859 | + * Playhead slider resets Status |
| 1860 | + */ |
| 1861 | + stop: function() { |
| 1862 | + var _this = this; |
| 1863 | + mw.log( 'EmbedPlayer::stop:' + this.id ); |
| 1864 | + |
| 1865 | + // no longer seeking: |
| 1866 | + this.didSeekJump = false; |
| 1867 | + |
| 1868 | + // Reset current time and prev time and seek offset |
| 1869 | + this.currentTime = this.previousTime = this.serverSeekTime = 0; |
| 1870 | + |
| 1871 | + this.stopMonitor(); |
| 1872 | + |
| 1873 | + // Issue pause to update interface (only call this parent) |
| 1874 | + if( !this.paused ){ |
| 1875 | + this.paused = true; |
| 1876 | + // update the interface |
| 1877 | + if ( this['parent_pause'] ) { |
| 1878 | + this.parent_pause(); |
| 1879 | + } else { |
| 1880 | + this.pause(); |
| 1881 | + } |
| 1882 | + } |
| 1883 | + // Native player controls: |
| 1884 | + if( this.useNativePlayerControls() ){ |
| 1885 | + this.getPlayerElement().currentTime = 0; |
| 1886 | + this.getPlayerElement().pause(); |
| 1887 | + // If on android when we "stop" we re add the large play button |
| 1888 | + if( mw.isAndroid2() ){ |
| 1889 | + this.addPlayBtnLarge(); |
| 1890 | + } |
| 1891 | + } else { |
| 1892 | + // Rewrite the html to thumbnail disp |
| 1893 | + this.showThumbnail(); |
| 1894 | + this.bufferedPercent = 0; // reset buffer state |
| 1895 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 1896 | + |
| 1897 | + // Reset the playhead |
| 1898 | + mw.log("EmbedPlayer::Stop:: Reset play head"); |
| 1899 | + this.updatePlayHead( 0 ); |
| 1900 | + |
| 1901 | + } |
| 1902 | + }, |
| 1903 | + |
| 1904 | + /** |
| 1905 | + * Base Embed mute |
| 1906 | + * |
| 1907 | + * Handles interface updates for toggling mute. Plug-in / player interface |
| 1908 | + * must handle the actual media player update |
| 1909 | + */ |
| 1910 | + toggleMute: function() { |
| 1911 | + mw.log( 'f:toggleMute:: (old state:) ' + this.muted ); |
| 1912 | + if ( this.muted ) { |
| 1913 | + this.muted = false; |
| 1914 | + var percent = this.preMuteVolume; |
| 1915 | + } else { |
| 1916 | + this.muted = true; |
| 1917 | + this.preMuteVolume = this.volume; |
| 1918 | + var percent = 0; |
| 1919 | + } |
| 1920 | + this.setVolume( percent ); |
| 1921 | + // Update the interface |
| 1922 | + this.setInterfaceVolume( percent ); |
| 1923 | + }, |
| 1924 | + |
| 1925 | + /** |
| 1926 | + * Update volume function ( called from interface updates ) |
| 1927 | + * |
| 1928 | + * @param {float} |
| 1929 | + * percent Percent of full volume |
| 1930 | + */ |
| 1931 | + setVolume: function( percent ) { |
| 1932 | + // ignore NaN percent: |
| 1933 | + if( isNaN( percent ) ){ |
| 1934 | + return ; |
| 1935 | + } |
| 1936 | + // Set the local volume attribute |
| 1937 | + this.previousVolume = this.volume = percent; |
| 1938 | + |
| 1939 | + // Un-mute if setting positive volume |
| 1940 | + if( percent != 0 ){ |
| 1941 | + this.muted = false; |
| 1942 | + } |
| 1943 | + |
| 1944 | + // Update the playerElement volume |
| 1945 | + this.setPlayerElementVolume( percent ); |
| 1946 | + |
| 1947 | + // mw.log(" setVolume:: " + percent + ' this.volume is: ' + |
| 1948 | + // this.volume); |
| 1949 | + $( this ).trigger('volumeChanged', percent ); |
| 1950 | + }, |
| 1951 | + |
| 1952 | + /** |
| 1953 | + * Updates the interface volume |
| 1954 | + * |
| 1955 | + * TODO should move to controlBuilder |
| 1956 | + * |
| 1957 | + * @param {float} |
| 1958 | + * percent Percentage volume to update interface |
| 1959 | + */ |
| 1960 | + setInterfaceVolume: function( percent ) { |
| 1961 | + if( this.supports[ 'volumeControl' ] && |
| 1962 | + this.$interface.find( '.volume-slider' ).length |
| 1963 | + ) { |
| 1964 | + this.$interface.find( '.volume-slider' ).slider( 'value', percent * 100 ); |
| 1965 | + } |
| 1966 | + }, |
| 1967 | + |
| 1968 | + /** |
| 1969 | + * Abstract Update volume Method must be override by plug-in / player |
| 1970 | + * interface |
| 1971 | + */ |
| 1972 | + setPlayerElementVolume: function( percent ) { |
| 1973 | + mw.log('Error player does not support volume adjustment' ); |
| 1974 | + }, |
| 1975 | + |
| 1976 | + /** |
| 1977 | + * Abstract get volume Method must be override by plug-in / player interface |
| 1978 | + * (if player does not override we return the abstract player value ) |
| 1979 | + */ |
| 1980 | + getPlayerElementVolume: function(){ |
| 1981 | + // mw.log(' error player does not support getting volume property' ); |
| 1982 | + return this.volume; |
| 1983 | + }, |
| 1984 | + |
| 1985 | + /** |
| 1986 | + * Abstract get volume muted property must be overwritten by plug-in / |
| 1987 | + * player interface (if player does not override we return the abstract |
| 1988 | + * player value ) |
| 1989 | + */ |
| 1990 | + getPlayerElementMuted: function(){ |
| 1991 | + // mw.log(' error player does not support getting mute property' ); |
| 1992 | + return this.muted; |
| 1993 | + }, |
| 1994 | + |
| 1995 | + /** |
| 1996 | + * Passes a fullscreen request to the controlBuilder interface |
| 1997 | + */ |
| 1998 | + fullscreen: function() { |
| 1999 | + this.controlBuilder.toggleFullscreen(); |
| 2000 | + }, |
| 2001 | + |
| 2002 | + /** |
| 2003 | + * Abstract method to be run post embedding the player Generally should be |
| 2004 | + * overwritten by the plug-in / player |
| 2005 | + */ |
| 2006 | + postEmbedJS:function() { |
| 2007 | + return ; |
| 2008 | + }, |
| 2009 | + |
| 2010 | + /** |
| 2011 | + * Checks the player state based on thumbnail display & paused state |
| 2012 | + * |
| 2013 | + * @return {Boolean} true if playing false if not playing |
| 2014 | + */ |
| 2015 | + isPlaying : function() { |
| 2016 | + if ( this.posterDisplayed ) { |
| 2017 | + // in stopped state |
| 2018 | + return false; |
| 2019 | + } else if ( this.paused ) { |
| 2020 | + // paused state |
| 2021 | + return false; |
| 2022 | + } else { |
| 2023 | + return true; |
| 2024 | + } |
| 2025 | + }, |
| 2026 | + |
| 2027 | + /** |
| 2028 | + * Get paused state |
| 2029 | + * |
| 2030 | + * @return {Boolean} true if playing false if not playing |
| 2031 | + */ |
| 2032 | + isPaused: function() { |
| 2033 | + return this.paused; |
| 2034 | + }, |
| 2035 | + |
| 2036 | + /** |
| 2037 | + * Get Stopped state |
| 2038 | + * |
| 2039 | + * @return {Boolean} true if stopped false if playing |
| 2040 | + */ |
| 2041 | + isStopped: function() { |
| 2042 | + return this.posterDisplayed; |
| 2043 | + }, |
| 2044 | + |
| 2045 | + // TODO temporary hack we need a better stop monitor system |
| 2046 | + stopMonitor: function(){ |
| 2047 | + clearInterval( this.monitorInterval ); |
| 2048 | + this.monitorInterval = 0; |
| 2049 | + }, |
| 2050 | + // TODO temporary hack we need a better stop monitor system |
| 2051 | + startMonitor: function(){ |
| 2052 | + this.monitor(); |
| 2053 | + }, |
| 2054 | + |
| 2055 | + /** |
| 2056 | + * Checks if the currentTime was updated outside of the getPlayerElementTime |
| 2057 | + * function |
| 2058 | + */ |
| 2059 | + checkForCurrentTimeSeek: function(){ |
| 2060 | + var _this = this; |
| 2061 | + // Check if a javascript currentTime change based seek has occurred |
| 2062 | + if( _this.previousTime != _this.currentTime && !this.userSlide && !this.seeking){ |
| 2063 | + // If the time has been updated and is in range issue a seek |
| 2064 | + if( _this.getDuration() && _this.currentTime <= _this.getDuration() ){ |
| 2065 | + var seekPercent = _this.currentTime / _this.getDuration(); |
| 2066 | + mw.log("checkForCurrentTimeSeek::" + _this.previousTime + ' != ' + |
| 2067 | + _this.currentTime + " javascript based currentTime update to " + |
| 2068 | + seekPercent + ' == ' + _this.currentTime ); |
| 2069 | + _this.previousTime = _this.currentTime; |
| 2070 | + this.doSeek( seekPercent ); |
| 2071 | + } |
| 2072 | + } |
| 2073 | + }, |
| 2074 | + |
| 2075 | + /** |
| 2076 | + * Monitor playback and update interface components. underling plugin |
| 2077 | + * objects are responsible for updating currentTime |
| 2078 | + */ |
| 2079 | + monitor: function() { |
| 2080 | + var _this = this; |
| 2081 | + |
| 2082 | + // Check for current time update outside of embed player |
| 2083 | + this.checkForCurrentTimeSeek(); |
| 2084 | + |
| 2085 | + |
| 2086 | + // Update currentTime via embedPlayer |
| 2087 | + _this.currentTime = _this.getPlayerElementTime(); |
| 2088 | + |
| 2089 | + // Update any offsets from server seek |
| 2090 | + if( _this.serverSeekTime && _this.supportsURLTimeEncoding() ){ |
| 2091 | + _this.currentTime = parseInt( _this.serverSeekTime ) + parseInt( _this.getPlayerElementTime() ); |
| 2092 | + } |
| 2093 | + |
| 2094 | + // Update the previousTime ( so we can know if the user-javascript |
| 2095 | + // changed currentTime ) |
| 2096 | + _this.previousTime = _this.currentTime; |
| 2097 | + |
| 2098 | + if( _this.pauseTime && _this.currentTime > _this.pauseTime ){ |
| 2099 | + _this.pause(); |
| 2100 | + _this.pauseTime = null; |
| 2101 | + } |
| 2102 | + |
| 2103 | + |
| 2104 | + // Check if volume was set outside of embed player function |
| 2105 | + // mw.log( ' this.volume: ' + _this.volume + ' prev Volume:: ' + |
| 2106 | + // _this.previousVolume ); |
| 2107 | + if( Math.round( _this.volume * 100 ) != Math.round( _this.previousVolume * 100 ) ) { |
| 2108 | + _this.setInterfaceVolume( _this.volume ); |
| 2109 | + if( _this._propagateEvents ){ |
| 2110 | + $( this ).trigger('volumeChanged', _this.volume ); |
| 2111 | + } |
| 2112 | + } |
| 2113 | + |
| 2114 | + // Update the previous volume |
| 2115 | + _this.previousVolume = _this.volume; |
| 2116 | + |
| 2117 | + // Update the volume from the player element |
| 2118 | + _this.volume = this.getPlayerElementVolume(); |
| 2119 | + |
| 2120 | + // update the mute state from the player element |
| 2121 | + if( _this.muted != _this.getPlayerElementMuted() && ! _this.isStopped() ){ |
| 2122 | + mw.log( "EmbedPlayer::monitor: muted does not mach embed player" ); |
| 2123 | + _this.toggleMute(); |
| 2124 | + // Make sure they match: |
| 2125 | + _this.muted = _this.getPlayerElementMuted(); |
| 2126 | + } |
| 2127 | + |
| 2128 | + //mw.log( 'Monitor:: ' + this.currentTime + ' duration: ' + ( parseInt( |
| 2129 | + // this.getDuration() ) + 1 ) + ' is seeking: ' + this.seeking ); |
| 2130 | + |
| 2131 | + if ( this.currentTime >= 0 && this.duration ) { |
| 2132 | + if ( !this.userSlide && !this.seeking ) { |
| 2133 | + if ( parseInt( this.startOffset ) != 0 ) { |
| 2134 | + // If start offset include that calculation |
| 2135 | + this.updatePlayHead( ( this.currentTime - this.startOffset ) / this.duration ); |
| 2136 | + var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( parseFloat( this.startOffset ) + parseFloat( this.duration ) ) : ''; |
| 2137 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et ); |
| 2138 | + } else { |
| 2139 | + this.updatePlayHead( this.currentTime / this.duration ); |
| 2140 | + // Only include the end time if longTimeDisp is enabled: |
| 2141 | + var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( this.duration ) : ''; |
| 2142 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et ); |
| 2143 | + } |
| 2144 | + } |
| 2145 | + // Check if we are "done" |
| 2146 | + var endPresentationTime = ( this.startOffset ) ? ( this.startOffset + this.duration ) : this.duration; |
| 2147 | + if ( this.currentTime >= endPresentationTime ) { |
| 2148 | + //mw.log( "mWEmbedPlayer::should run clip done :: " + this.currentTime + ' > ' + endPresentationTime ); |
| 2149 | + this.onClipDone(); |
| 2150 | + } |
| 2151 | + } else { |
| 2152 | + // Media lacks duration just show end time |
| 2153 | + if ( this.isStopped() ) { |
| 2154 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 2155 | + } else if ( this.isPaused() ) { |
| 2156 | + this.controlBuilder.setStatus( gM( 'mwe-embedplayer-paused' ) ); |
| 2157 | + } else if ( this.isPlaying() ) { |
| 2158 | + if ( this.currentTime && ! this.duration ) |
| 2159 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + ' /' ); |
| 2160 | + else |
| 2161 | + this.controlBuilder.setStatus( " - - - " ); |
| 2162 | + } else { |
| 2163 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 2164 | + } |
| 2165 | + } |
| 2166 | + |
| 2167 | + // Update buffer information |
| 2168 | + this.updateBufferStatus(); |
| 2169 | + |
| 2170 | + // run the "native" progress event on the virtual html5 object if set |
| 2171 | + if( this.progressEventData ) { |
| 2172 | + // mw.log("trigger:progress event on html5 proxy"); |
| 2173 | + if( _this._propagateEvents ){ |
| 2174 | + $( this ).trigger( 'progress', this.progressEventData ); |
| 2175 | + } |
| 2176 | + } |
| 2177 | + |
| 2178 | + // Call monitor at 250ms interval. ( use setInterval to avoid stacking |
| 2179 | + // monitor requests ) |
| 2180 | + if( ! this.isStopped() ) { |
| 2181 | + if( !this.monitorInterval ){ |
| 2182 | + this.monitorInterval = setInterval( function(){ |
| 2183 | + if( _this.monitor ) |
| 2184 | + _this.monitor(); |
| 2185 | + }, this.monitorRate ); |
| 2186 | + } |
| 2187 | + } else { |
| 2188 | + // If stopped "stop" monitor: |
| 2189 | + this.stopMonitor(); |
| 2190 | + } |
| 2191 | + |
| 2192 | + // mw.log('trigger:monitor:: ' + this.currentTime ); |
| 2193 | + if( _this._propagateEvents ){ |
| 2194 | + $( this ).trigger( 'monitorEvent' ); |
| 2195 | + } |
| 2196 | + }, |
| 2197 | + |
| 2198 | + /** |
| 2199 | + * Abstract getPlayerElementTime function |
| 2200 | + */ |
| 2201 | + getPlayerElementTime: function(){ |
| 2202 | + mw.log("Error: getPlayerElementTime should be implemented by embed library"); |
| 2203 | + }, |
| 2204 | + |
| 2205 | + /** |
| 2206 | + * Update the Buffer status based on the local bufferedPercent var |
| 2207 | + */ |
| 2208 | + updateBufferStatus: function() { |
| 2209 | + |
| 2210 | + // Get the buffer target based for playlist vs clip |
| 2211 | + $buffer = this.$interface.find( '.mw_buffer' ); |
| 2212 | + |
| 2213 | + //mw.log(' set bufferd %:' + this.bufferedPercent ); |
| 2214 | + // Update the buffer progress bar (if available ) |
| 2215 | + if ( this.bufferedPercent != 0 ) { |
| 2216 | + // mw.log('Update buffer css: ' + ( this.bufferedPercent * 100 ) + |
| 2217 | + // '% ' + $buffer.length ); |
| 2218 | + if ( this.bufferedPercent > 1 ){ |
| 2219 | + this.bufferedPercent = 1; |
| 2220 | + } |
| 2221 | + $buffer.css({ |
| 2222 | + "width" : ( this.bufferedPercent * 100 ) + '%' |
| 2223 | + }); |
| 2224 | + $( this ).trigger( 'updateBufferPercent', this.bufferedPercent ); |
| 2225 | + } else { |
| 2226 | + $buffer.css( "width", '0px' ); |
| 2227 | + } |
| 2228 | + |
| 2229 | + // if we have not already run the buffer start hook |
| 2230 | + if( this.bufferedPercent > 0 && !this.bufferStartFlag ) { |
| 2231 | + this.bufferStartFlag = true; |
| 2232 | + mw.log("bufferStart"); |
| 2233 | + $( this ).trigger( 'bufferStartEvent' ); |
| 2234 | + } |
| 2235 | + |
| 2236 | + // if we have not already run the buffer end hook |
| 2237 | + if( this.bufferedPercent == 1 && !this.bufferEndFlag){ |
| 2238 | + this.bufferEndFlag = true; |
| 2239 | + $( this ).trigger( 'bufferEndEvent' ); |
| 2240 | + } |
| 2241 | + }, |
| 2242 | + |
| 2243 | + /** |
| 2244 | + * Update the player playhead |
| 2245 | + * |
| 2246 | + * @param {Float} |
| 2247 | + * perc Value between 0 and 1 for position of playhead |
| 2248 | + */ |
| 2249 | + updatePlayHead: function( perc ) { |
| 2250 | + //mw.log( 'EmbedPlayer: updatePlayHead: '+ perc); |
| 2251 | + $playHead = this.$interface.find( '.play_head' ); |
| 2252 | + if ( this.controls && $playHead.length != 0 ) { |
| 2253 | + var val = parseInt( perc * 1000 ); |
| 2254 | + $playHead.slider( 'value', val ); |
| 2255 | + } |
| 2256 | + $( this ).trigger('updatePlayHeadPercent', perc); |
| 2257 | + }, |
| 2258 | + |
| 2259 | + |
| 2260 | + /** |
| 2261 | + * Helper Functions for selected source |
| 2262 | + */ |
| 2263 | + |
| 2264 | + /** |
| 2265 | + * Get the current selected media source or first source |
| 2266 | + * |
| 2267 | + * @param {Number} Requested time in seconds to be passed to the server if the server supports |
| 2268 | + * supportsURLTimeEncoding |
| 2269 | + * @return src url |
| 2270 | + */ |
| 2271 | + getSrc: function( serverSeekTime ) { |
| 2272 | + if( serverSeekTime ){ |
| 2273 | + this.serverSeekTime = serverSeekTime; |
| 2274 | + } |
| 2275 | + if( this.currentTime && !this.serverSeekTime){ |
| 2276 | + this.serverSeekTime = this.currentTime; |
| 2277 | + } |
| 2278 | + |
| 2279 | + // No media element we can't return src |
| 2280 | + if( !this.mediaElement ){ |
| 2281 | + return false; |
| 2282 | + } |
| 2283 | + |
| 2284 | + // If no source selected auto select the source: |
| 2285 | + if( !this.mediaElement.selectedSource ){ |
| 2286 | + this.mediaElement.autoSelectSource(); |
| 2287 | + }; |
| 2288 | + |
| 2289 | + // Return selected source: |
| 2290 | + if( this.mediaElement.selectedSource ){ |
| 2291 | + // get the first source: |
| 2292 | + return this.mediaElement.selectedSource.getSrc( this.serverSeekTime ); |
| 2293 | + } |
| 2294 | + // No selected source return false: |
| 2295 | + return false; |
| 2296 | + }, |
| 2297 | + |
| 2298 | + /** |
| 2299 | + * If the selected src supports URL time encoding |
| 2300 | + * |
| 2301 | + * @return {Boolean} true if the src supports url time requests false if the |
| 2302 | + * src does not support url time requests |
| 2303 | + */ |
| 2304 | + supportsURLTimeEncoding: function() { |
| 2305 | + var timeUrls = mw.getConfig('EmbedPlayer.EnableURLTimeEncoding') ; |
| 2306 | + if( timeUrls == 'none' ){ |
| 2307 | + return false; |
| 2308 | + } else if( timeUrls == 'always' ){ |
| 2309 | + return this.mediaElement.selectedSource.URLTimeEncoding; |
| 2310 | + } else if( timeUrls == 'flash' ){ |
| 2311 | + if( this.mediaElement.selectedSource.URLTimeEncoding){ |
| 2312 | + // see if the current selected player is flash: |
| 2313 | + return ( this.instanceOf == 'Kplayer' ); |
| 2314 | + } |
| 2315 | + } else { |
| 2316 | + mw.log("Error:: invalid config value for EmbedPlayer.EnableURLTimeEncoding:: " + mw.getConfig('EmbedPlayer.EnableURLTimeEncoding') ); |
| 2317 | + } |
| 2318 | + return false; |
| 2319 | + } |
| 2320 | +}; |
| 2321 | + |
| 2322 | + |
| 2323 | +} )( mediaWiki, jQuery ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 2324 | + text/plain |
Added: svn:eol-style |
2 | 2325 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaPlayer.js |
— | — | @@ -0,0 +1,100 @@ |
| 2 | +/** |
| 3 | + * mediaPlayer represents a media player plugin. |
| 4 | + * |
| 5 | + * @param {String} |
| 6 | + * id id used for the plugin. |
| 7 | + * @param {Array} |
| 8 | + * supported_types an array of supported MIME types. |
| 9 | + * @param {String} |
| 10 | + * library external script containing the plugin interface code. |
| 11 | + * @constructor |
| 12 | + */ |
| 13 | + |
| 14 | + |
| 15 | +function mediaPlayer( id, supported_types, library ) |
| 16 | +{ |
| 17 | + this.id = id; |
| 18 | + this.supported_types = supported_types; |
| 19 | + this.library = library; |
| 20 | + this.loaded = false; |
| 21 | + this.loading_callbacks = new Array(); |
| 22 | + return this; |
| 23 | +} |
| 24 | +mediaPlayer.prototype = { |
| 25 | + // Id of the mediaPlayer |
| 26 | + id:null, |
| 27 | + |
| 28 | + // Mime types supported by this player |
| 29 | + supported_types:null, |
| 30 | + |
| 31 | + // Player library ie: native, vlc, java etc. |
| 32 | + library:null, |
| 33 | + |
| 34 | + // Flag stores the mediaPlayer load state |
| 35 | + loaded:false, |
| 36 | + |
| 37 | + /** |
| 38 | + * Checks support for a given MIME type |
| 39 | + * |
| 40 | + * @param {String} |
| 41 | + * type Mime type to check against supported_types |
| 42 | + * @return {Boolean} true if mime type is supported false if mime type is |
| 43 | + * unsupported |
| 44 | + */ |
| 45 | + supportsMIMEType: function( type ) { |
| 46 | + for ( var i = 0; i < this.supported_types.length; i++ ) { |
| 47 | + if ( this.supported_types[i] == type ) |
| 48 | + return true; |
| 49 | + } |
| 50 | + return false; |
| 51 | + }, |
| 52 | + |
| 53 | + /** |
| 54 | + * Get the "name" of the player from a predictable msg key |
| 55 | + */ |
| 56 | + getName: function() { |
| 57 | + return gM( 'mwe-embedplayer-ogg-player-' + this.id ); |
| 58 | + }, |
| 59 | + |
| 60 | + /** |
| 61 | + * Loads the player library & player skin config ( if needed ) and then |
| 62 | + * calls the callback. |
| 63 | + * |
| 64 | + * @param {Function} |
| 65 | + * callback Function to be called once player library is loaded. |
| 66 | + */ |
| 67 | + load: function( callback ) { |
| 68 | + // Load player library ( upper case the first letter of the library ) |
| 69 | + mw.load( [ |
| 70 | + 'mw.EmbedPlayer' + this.library.substr(0,1).toUpperCase() + this.library.substr(1) |
| 71 | + ], function() { |
| 72 | + callback(); |
| 73 | + } ); |
| 74 | + } |
| 75 | +}; |
| 76 | + |
| 77 | +/** |
| 78 | + * players and supported mime types In an ideal world we would query the plugin |
| 79 | + * to get what mime types it supports in practice not always reliable/available |
| 80 | + * |
| 81 | + * We can't cleanly store these values per library since player library is sometimes |
| 82 | + * loaded post player detection |
| 83 | + */ |
| 84 | + |
| 85 | +// Flash based players: |
| 86 | +var kplayer = new mediaPlayer('kplayer', ['video/x-flv', 'video/h264'], 'Kplayer'); |
| 87 | + |
| 88 | +// Java based player |
| 89 | +var cortadoPlayer = new mediaPlayer( 'cortado', ['video/ogg', 'audio/ogg', 'application/ogg'], 'Java' ); |
| 90 | + |
| 91 | +// Native html5 players |
| 92 | +var oggNativePlayer = new mediaPlayer( 'oggNative', ['video/ogg', 'audio/ogg', 'application/ogg' ], 'Native' ); |
| 93 | +var h264NativePlayer = new mediaPlayer( 'h264Native', ['video/h264'], 'Native' ); |
| 94 | +var webmNativePlayer = new mediaPlayer( 'webmNative', ['video/webm'], 'Native' ); |
| 95 | + |
| 96 | +// VLC player |
| 97 | +var vlcMineList = ['video/ogg', 'audio/ogg', 'application/ogg', 'video/x-flv', 'video/mp4', 'video/h264', 'video/x-msvideo', 'video/mpeg']; |
| 98 | +var vlcPlayer = new mediaPlayer( 'vlc-player', vlcMineList, 'Vlc' ); |
| 99 | + |
| 100 | +// Generic plugin |
| 101 | +var oggPluginPlayer = new mediaPlayer( 'oggPlugin', ['video/ogg', 'application/ogg'], 'Generic' ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaPlayer.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 102 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.png |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.png |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 103 | + application/octet-stream |
Added: svn:executable |
3 | 104 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.gif |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 105 | + application/octet-stream |
Added: svn:executable |
5 | 106 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/ksprite.png |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/ksprite.png |
___________________________________________________________________ |
Added: svn:mime-type |
6 | 107 | + application/octet-stream |
Added: svn:executable |
7 | 108 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/mw.style.PlayerSkinKskin.css |
— | — | @@ -0,0 +1,432 @@ |
| 2 | +/* |
| 3 | +* K-skin player |
| 4 | +*/ |
| 5 | +.k-player { |
| 6 | + color: #FFF; |
| 7 | + background-color: #000; |
| 8 | +} |
| 9 | +.k-player .ui-widget-content { |
| 10 | + color: #999; |
| 11 | +} |
| 12 | +.k-player .ui-widget-content a{ |
| 13 | + color: #999; |
| 14 | +} |
| 15 | +/* large play button */ |
| 16 | +.k-player .play-btn-large { |
| 17 | + width: 70px; |
| 18 | + height: 55px; |
| 19 | + background: url(images/ksprite.png) no-repeat 0px -433px; |
| 20 | + position: absolute; |
| 21 | + cursor: pointer; |
| 22 | + border: none; |
| 23 | +} |
| 24 | +@print { |
| 25 | + .k-player .play-btn-large { |
| 26 | + display: none; |
| 27 | + } |
| 28 | +} |
| 29 | +/*.ui-state-default */ |
| 30 | +.k-player .play-btn-large:hover { |
| 31 | + background: url(images/ksprite.png) no-repeat 0px -377px; |
| 32 | +} |
| 33 | + |
| 34 | +/* control icons: */ |
| 35 | +.k-player .ui-state-default .ui-icon,.k-player .ui-state-hover .ui-icon{ |
| 36 | + background: transparent url(images/ksprite.png) no-repeat scroll 0 -48px; |
| 37 | +} |
| 38 | + |
| 39 | +.k-player .ui-state-default .ui-icon-arrow-4-diag { |
| 40 | + background-position: 0 -32px; |
| 41 | +} |
| 42 | +/* fullscreen */ |
| 43 | +.k-player .ui-state-hover .ui-icon-arrow-4-diag { |
| 44 | + background-position: -16px -32px; |
| 45 | +} |
| 46 | + |
| 47 | +.k-player .ui-state-hover .ui-icon-volume-on{ |
| 48 | + background-position: -16px -48px; |
| 49 | +} |
| 50 | + |
| 51 | +/* cc icon */ |
| 52 | +.k-player .ui-state-default .ui-icon-comment { |
| 53 | + background-position: 0px -65px; |
| 54 | +} |
| 55 | +.k-player .ui-state-hover .ui-icon-comment { |
| 56 | + background-position: -17px -65px; |
| 57 | +} |
| 58 | + |
| 59 | +.k-player .ui-state-default .ui-icon-play { |
| 60 | + background: url(images/ksprite.png) no-repeat 0 0; |
| 61 | +} |
| 62 | + |
| 63 | +.k-player .ui-state-hover .ui-icon-play { |
| 64 | + background-position: -16px 0; |
| 65 | +} |
| 66 | + |
| 67 | +.k-player .ui-state-default .ui-icon-pause { |
| 68 | + background: url(images/ksprite.png) no-repeat 0 -17px; |
| 69 | +} |
| 70 | + |
| 71 | +.k-player .ui-state-hover .ui-icon-pause { |
| 72 | + background-position: -16px -17px; |
| 73 | +} |
| 74 | + |
| 75 | +.k-player .control-bar { |
| 76 | + border:1px solid #c8c8c8; |
| 77 | + border-top: 0px; |
| 78 | + border-right: 0px; |
| 79 | + height: 21px; |
| 80 | + padding: 2px 0 0 6px; |
| 81 | + margin-top: 0px; |
| 82 | + background: url(images/ksprite.png) repeat-x 0 -81px; |
| 83 | + font: normal 11px arial, sans-serif; |
| 84 | + color: #555; |
| 85 | +} |
| 86 | + |
| 87 | +.k-player .play_head { |
| 88 | + background: url("images/ksprite.png") repeat-x scroll 0 -350px |
| 89 | + transparent; |
| 90 | + display: inline; |
| 91 | + float: left; |
| 92 | + margin-left: 10px; |
| 93 | + border: 1px solid #EEEEEE; |
| 94 | + height: 8px; |
| 95 | + margin: 5px 2px 0 0px; |
| 96 | + position: relative; |
| 97 | +} |
| 98 | + |
| 99 | +.k-player .play_head .ui-slider-handle { |
| 100 | + background: url("images/ksprite.png") no-repeat scroll -67px -341px |
| 101 | + transparent; |
| 102 | + border: 1px solid #888888; |
| 103 | + display: block; |
| 104 | + height: 8px; |
| 105 | + margin: -1px 0 0 -5px; |
| 106 | + position: absolute; |
| 107 | + top: 0; |
| 108 | + width: 8px; |
| 109 | + cursor: pointer; |
| 110 | + -moz-border-radius:5px 5px 5px 5px; |
| 111 | +} |
| 112 | +.k-player .ui-corner-all { |
| 113 | + -moz-border-radius:5px 5px 5px 5px; |
| 114 | +} |
| 115 | +.k-player .time-disp { |
| 116 | + border: medium none; |
| 117 | + display: inline; |
| 118 | + color: #555555; |
| 119 | + font: 11px arial, sans-serif; |
| 120 | + line-height: 20px; |
| 121 | + overflow: hidden; |
| 122 | + width: 48px; |
| 123 | + float: right; |
| 124 | +} |
| 125 | + |
| 126 | +.k-player .source-switch{ |
| 127 | + border: medium none; |
| 128 | + display: inline; |
| 129 | + color: #555555; |
| 130 | + font: 11px arial, sans-serif; |
| 131 | + line-height: 20px; |
| 132 | + overflow: hidden; |
| 133 | + width: 50px; |
| 134 | + cursor: pointer; |
| 135 | + float: right; |
| 136 | +} |
| 137 | + |
| 138 | + |
| 139 | +.k-player .lButton { |
| 140 | + cursor: pointer; |
| 141 | + float: left; |
| 142 | + list-style: none outside none; |
| 143 | + margin: 2px; |
| 144 | + padding: 0px 0; |
| 145 | + width: 24px; |
| 146 | + height: 16px; |
| 147 | + position: relative; |
| 148 | + background: none repeat scroll 0 0 transparent; |
| 149 | + border: medium none; |
| 150 | +} |
| 151 | + |
| 152 | +.k-player .rButton { |
| 153 | + cursor: pointer; |
| 154 | + float: right; |
| 155 | + list-style: none outside none; |
| 156 | + margin-top: 2px; |
| 157 | + padding: 0px 0; |
| 158 | + width: 22px; |
| 159 | + height: 16px; |
| 160 | + position: relative; |
| 161 | + background: none repeat scroll 0 0 transparent; |
| 162 | + border: medium none; |
| 163 | +} |
| 164 | + |
| 165 | +.k-player .k-options { |
| 166 | + border: 1px solid #AAAAAA !important; |
| 167 | + color: #555555; |
| 168 | + float: right; |
| 169 | + height: 21px; |
| 170 | + margin-top: -2px; |
| 171 | + margin-right: 0px; |
| 172 | + width: 50px; |
| 173 | + float: right; |
| 174 | + background: none repeat scroll 0 0 transparent; |
| 175 | + font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; |
| 176 | + font-size: 11px; |
| 177 | + text-transform: uppercase; |
| 178 | + text-align:center; |
| 179 | +} |
| 180 | + |
| 181 | +.k-player .k-options span { |
| 182 | + position: relative; |
| 183 | + top: 4px; |
| 184 | +} |
| 185 | + |
| 186 | +.k-player .k-menu-screens { |
| 187 | + float: left; |
| 188 | + font-size: 11px; |
| 189 | + text-align: left; |
| 190 | + padding: 13px 10px 15px 15px; |
| 191 | +} |
| 192 | + |
| 193 | +.k-player ul.k-menu-bar { |
| 194 | + background: url("images/ksprite.png") no-repeat scroll -99px -104px |
| 195 | + transparent; |
| 196 | + bottom: 5px; |
| 197 | + height: 128px; |
| 198 | + list-style: none outside none; |
| 199 | + padding: 0 0 5px; |
| 200 | + position: absolute; |
| 201 | + right: 0; |
| 202 | +} |
| 203 | + |
| 204 | +.k-player .k-menu { |
| 205 | + background: none repeat scroll 0 0 #181818; |
| 206 | + border: medium none; |
| 207 | + display: none; |
| 208 | + left: 0; |
| 209 | + position: absolute; |
| 210 | + top: 0; |
| 211 | + z-index: 2; |
| 212 | +} |
| 213 | + |
| 214 | +.k-player .k-menu-bar li a { |
| 215 | + background: url("images/ksprite.png") no-repeat scroll -51px -110px |
| 216 | + transparent; |
| 217 | + display: block; |
| 218 | + height: 32px; |
| 219 | + margin-left: 1px; |
| 220 | + overflow: hidden; |
| 221 | + text-indent: 99999px; |
| 222 | + width: 49px; |
| 223 | +} |
| 224 | + |
| 225 | +.k-menu-bar li a:hover { |
| 226 | + background-position: -1px -110px; |
| 227 | +} |
| 228 | + |
| 229 | +.k-menu-bar li.k-download-btn a { |
| 230 | + background-position: -51px -203px; |
| 231 | +} |
| 232 | + |
| 233 | +.k-menu-bar li.k-download-btn a:hover { |
| 234 | + background-position: -1px -203px; |
| 235 | +} |
| 236 | + |
| 237 | +.k-menu-bar li.k-share-btn a { |
| 238 | + background-position: -51px -172px; |
| 239 | +} |
| 240 | + |
| 241 | +.k-menu-bar li.k-share-btn a:hover { |
| 242 | + background-position: -1px -172px; |
| 243 | +} |
| 244 | + |
| 245 | +.k-menu-bar li.k-credits-btn a { |
| 246 | + background-position: -51px -141px; |
| 247 | +} |
| 248 | + |
| 249 | +.k-menu-bar li.k-credits-btn a:hover { |
| 250 | + background-position: -1px -141px; |
| 251 | +} |
| 252 | + |
| 253 | + |
| 254 | + |
| 255 | +.k-menu-screens p { |
| 256 | + margin: 6px 0; |
| 257 | +} |
| 258 | + |
| 259 | +.k-menu-screens a img { |
| 260 | + border: none; |
| 261 | +} |
| 262 | + |
| 263 | +.k-menu-screens ul { |
| 264 | + padding: 0; |
| 265 | + margin: 6px 0 0; |
| 266 | + list-style: none outside none; |
| 267 | +} |
| 268 | + |
| 269 | +.k-edit-screen { |
| 270 | + width: 370px; |
| 271 | + height: 223px; |
| 272 | + padding-top: 77px; |
| 273 | + text-align: center; |
| 274 | + background: #181818; |
| 275 | + color: #fff; |
| 276 | +} |
| 277 | + |
| 278 | +.k-edit-screen div { |
| 279 | + |
| 280 | +} |
| 281 | + |
| 282 | +.k-edit-screen a { |
| 283 | + color: #7BB8FC; |
| 284 | +} |
| 285 | + |
| 286 | +.k-edit-screen a img { |
| 287 | + border: none; |
| 288 | +} |
| 289 | + |
| 290 | + |
| 291 | +.k-menu-screens h2, .k-menu-screens h3 { |
| 292 | + padding: 0 0 5px 1px; |
| 293 | + clear: both; |
| 294 | + font-size: 12px; |
| 295 | + color: #999; |
| 296 | +} |
| 297 | + |
| 298 | +.k-menu-screens p { |
| 299 | + margin: 6px 0; |
| 300 | +} |
| 301 | + |
| 302 | +.k-menu-screens a img { |
| 303 | + border: none; |
| 304 | +} |
| 305 | + |
| 306 | +.k-menu-screens ul { |
| 307 | + padding: 0; |
| 308 | + margin: 6px 0 0; |
| 309 | + list-style: none outside none; |
| 310 | +} |
| 311 | + |
| 312 | +.k-menu-screens li { |
| 313 | + margin-bottom: 6px; |
| 314 | +} |
| 315 | + |
| 316 | +.k-menu-screens li a { |
| 317 | + padding-left: 22px; |
| 318 | + background: url(images/ksprite.png) no-repeat -85px -274px; |
| 319 | + text-decoration: none; |
| 320 | + color: #BBB; |
| 321 | +} |
| 322 | + |
| 323 | +.k-menu-screens li a.active,.k-menu-screens li a:hover .active { |
| 324 | + background-position: -85px -247px; |
| 325 | +} |
| 326 | + |
| 327 | +.k-menu-screens li a:hover { |
| 328 | + background-position: -85px -260px; |
| 329 | +} |
| 330 | + |
| 331 | +.k-menu textarea { |
| 332 | + background: none repeat scroll 0 0 transparent; |
| 333 | + border-color: #000000 -moz-use-text-color -moz-use-text-color #000000; |
| 334 | + border-style: solid none none solid; |
| 335 | + border-width: 2px medium medium 2px; |
| 336 | + color: #CCCCCC; |
| 337 | + font: 11px arial, sans-serif; |
| 338 | + overflow: hidden; |
| 339 | + padding-left: 2px; |
| 340 | + width: 95%; |
| 341 | +} |
| 342 | + |
| 343 | +.menu-screen.menu-share button { |
| 344 | + background: url("images/ksprite.png") no-repeat scroll 0 -81px #D4D4D4; |
| 345 | + border: 1px solid #000000; |
| 346 | + color: #000000; |
| 347 | + float: right; |
| 348 | + height: 34px; |
| 349 | + padding: 0 5px 3px; |
| 350 | + width: 84px; |
| 351 | + font-size: 1em; |
| 352 | +} |
| 353 | + |
| 354 | +.k-player .menu-screen { |
| 355 | + height: 100%; |
| 356 | + overflow-y: auto; |
| 357 | + overflow-x: hidden; |
| 358 | +} |
| 359 | + |
| 360 | + |
| 361 | +.k-player .menu-screen.menu-share div.ui-state-highlight { |
| 362 | + background: none repeat scroll 0 0 transparent; |
| 363 | + border-color: #554926; |
| 364 | + color: #FFE96E; |
| 365 | + float: left; |
| 366 | + padding: 2px 5px; |
| 367 | +} |
| 368 | + |
| 369 | +.k-player .menu-screen.menu-share div.ui-state-highlight a { |
| 370 | + color: #FFE96E; |
| 371 | + font-weight: bold; |
| 372 | +} |
| 373 | + |
| 374 | +.k-player .volume_control { |
| 375 | + margin-right: 2px; |
| 376 | + width: 16px; |
| 377 | +} |
| 378 | + |
| 379 | +.k-player .volume_control span { |
| 380 | + margin-right: 0px; |
| 381 | +} |
| 382 | + |
| 383 | +.k-player .volume-slider { |
| 384 | + width: 20px; |
| 385 | +} |
| 386 | + |
| 387 | +.k-player .volume-slider .ui-slider-range { |
| 388 | + -moz-border-radius: 0 0 0 0; |
| 389 | + background: url("images/ksprite.png") repeat-x scroll -66px -306px |
| 390 | + transparent; |
| 391 | + height: 17px; |
| 392 | + position: absolute; |
| 393 | +} |
| 394 | + |
| 395 | +.k-player .volume-slider a.ui-slider-handle { |
| 396 | + background: none repeat scroll 0 0 transparent; |
| 397 | + border: medium none; |
| 398 | + display: block; |
| 399 | + height: 18px; |
| 400 | + margin: -3px 5px 0 -1px; |
| 401 | + position: absolute; |
| 402 | + width: 8px; |
| 403 | +} |
| 404 | + |
| 405 | +.k-player .credits_box { |
| 406 | + background-attachment:scroll; |
| 407 | + background-color:white; |
| 408 | + background-image:none; |
| 409 | + background-position:0 0; |
| 410 | + bottom:30px; |
| 411 | + left:15px; |
| 412 | + position:absolute; |
| 413 | + right:30px; |
| 414 | + top:48px; |
| 415 | +} |
| 416 | +.k-player .credits_box a{ |
| 417 | + color:#666; |
| 418 | +} |
| 419 | +.k-player .creditline img { |
| 420 | + float: left; |
| 421 | + width: 90px; |
| 422 | + margin: 4px; |
| 423 | +} |
| 424 | + |
| 425 | +.k-player .k-attribution{ |
| 426 | + position:absolute; |
| 427 | + bottom: 15px; |
| 428 | + right : 30px; |
| 429 | + background: url("images/kaltura_open_source_video_platform.png"); |
| 430 | + width : 51px; |
| 431 | + height : 12px; |
| 432 | + cursor: pointer; |
| 433 | +} |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/mw.style.PlayerSkinKskin.css |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 434 | + text/plain |
Added: svn:eol-style |
2 | 435 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/mw.PlayerSkinKskin.js |
— | — | @@ -0,0 +1,382 @@ |
| 2 | +/** |
| 3 | +* Skin js allows you to override contrlBuilder html/class output |
| 4 | +*/ |
| 5 | +( function( mw, $ ) { |
| 6 | + |
| 7 | +mw.PlayerSkinKskin = { |
| 8 | + |
| 9 | + // The parent class for all kskin css: |
| 10 | + playerClass: 'k-player', |
| 11 | + |
| 12 | + // Display time string length |
| 13 | + longTimeDisp: false, |
| 14 | + |
| 15 | + // Default control bar height |
| 16 | + height: 20, |
| 17 | + |
| 18 | + // Volume control layout is horizontal |
| 19 | + volume_layout: 'horizontal', |
| 20 | + |
| 21 | + // Skin "kskin" is specific for wikimedia we have an |
| 22 | + // api Title key so the "credits" menu item can be showed. |
| 23 | + supportedMenuItems: { |
| 24 | + 'credits': true |
| 25 | + }, |
| 26 | + |
| 27 | + // Extends base components with kskin specific options: |
| 28 | + components: { |
| 29 | + 'playButtonLarge' : { |
| 30 | + 'h' : 55 |
| 31 | + }, |
| 32 | + 'options': { |
| 33 | + 'w':50, |
| 34 | + 'o':function( ctrlObj ) { |
| 35 | + return $( '<div />' ) |
| 36 | + .attr( 'title', gM( 'mwe-embedplayer-player_options' ) ) |
| 37 | + .addClass( "ui-state-default ui-corner-bl rButton k-options" ) |
| 38 | + .append( |
| 39 | + $( '<span />' ) |
| 40 | + .text( gM( 'mwe-embedplayer-menu_btn' ) ) |
| 41 | + ); |
| 42 | + } |
| 43 | + }, |
| 44 | + 'volumeControl': { |
| 45 | + 'w':40 |
| 46 | + }, |
| 47 | + // No attributionButton component for kSkin ( its integrated into the credits screen ) |
| 48 | + 'attributionButton' : false, |
| 49 | + |
| 50 | + // Time display: |
| 51 | + 'timeDisplay': { |
| 52 | + 'w':45 |
| 53 | + }, |
| 54 | + 'optionsMenu': { |
| 55 | + 'w' : 0, |
| 56 | + 'o' : function( ctrlObj ) { |
| 57 | + var embedPlayer = ctrlObj.embedPlayer; |
| 58 | + |
| 59 | + $menuOverlay = $( '<div />') |
| 60 | + .addClass( 'overlay-win k-menu ui-widget-content' ) |
| 61 | + .css( { |
| 62 | + 'width' : '100%', |
| 63 | + 'position': 'absolute', |
| 64 | + 'top' : '0px', |
| 65 | + 'bottom' : ( ctrlObj.getHeight() + 2 ) + 'px' |
| 66 | + } ); |
| 67 | + |
| 68 | + // Note safari can't display video overlays with text: |
| 69 | + // see bug https://bugs.webkit.org/show_bug.cgi?id=48379 |
| 70 | + |
| 71 | + var userAgent = navigator.userAgent.toLowerCase(); |
| 72 | + if( userAgent.indexOf('safari') != -1 ){ |
| 73 | + $menuOverlay.css('opacity', '0.9'); |
| 74 | + } |
| 75 | + // Setup menu offset ( if player height < getOverlayHeight ) |
| 76 | + // This displays the menu outside of the player on small embeds |
| 77 | + if ( embedPlayer.getPlayerHeight() < ctrlObj.getOverlayHeight() ) { |
| 78 | + var topPos = ( ctrlObj.checkOverlayControls() ) |
| 79 | + ? embedPlayer.getPlayerHeight() |
| 80 | + : embedPlayer.getPlayerHeight() + ctrlObj.getHeight(); |
| 81 | + |
| 82 | + $menuOverlay.css( { |
| 83 | + 'top' : topPos + 'px', |
| 84 | + 'bottom' : null, |
| 85 | + 'width' : ctrlObj.getOverlayWidth(), |
| 86 | + 'height' : ctrlObj.getOverlayHeight() + 'px' |
| 87 | + }); |
| 88 | + // Special common overflow hack for thumbnail display of player |
| 89 | + $( embedPlayer ).parents( '.thumbinner' ).css( 'overflow', 'visible' ); |
| 90 | + } |
| 91 | + |
| 92 | + $menuBar = $( '<ul />' ) |
| 93 | + .addClass( 'k-menu-bar' ); |
| 94 | + |
| 95 | + // dont include about player menu item ( FIXME should be moved to a init function ) |
| 96 | + delete ctrlObj.supportedMenuItems['aboutPlayerLibrary']; |
| 97 | + |
| 98 | + // Output menu item containers: |
| 99 | + for ( var menuItem in ctrlObj.supportedMenuItems ) { |
| 100 | + $menuBar.append( |
| 101 | + $( '<li />') |
| 102 | + // Add the menu item class: |
| 103 | + .addClass( 'k-' + menuItem + '-btn' ) |
| 104 | + .attr( 'rel', menuItem ) |
| 105 | + .append( |
| 106 | + $( '<a />' ) |
| 107 | + .attr( { |
| 108 | + 'title' : gM( 'mwe-embedplayer-' + menuItem ), |
| 109 | + 'href' : '#' |
| 110 | + }) |
| 111 | + ) |
| 112 | + ); |
| 113 | + } |
| 114 | + |
| 115 | + // Add the menuBar to the menuOverlay |
| 116 | + $menuOverlay.append( $menuBar ); |
| 117 | + |
| 118 | + var $menuScreens = $( '<div />' ) |
| 119 | + .addClass( 'k-menu-screens' ) |
| 120 | + .css( { |
| 121 | + 'position' : 'absolute', |
| 122 | + 'top' : '0px', |
| 123 | + 'left' : '0px', |
| 124 | + 'bottom' : '0px', |
| 125 | + 'right' : '45px', |
| 126 | + 'overflow' : 'hidden' |
| 127 | + } ); |
| 128 | + for ( var menuItem in ctrlObj.supportedMenuItems ) { |
| 129 | + $menuScreens.append( |
| 130 | + $( '<div />' ) |
| 131 | + .addClass( 'menu-screen menu-' + menuItem ) |
| 132 | + ); |
| 133 | + } |
| 134 | + |
| 135 | + // Add the menuScreens to the menuOverlay |
| 136 | + $menuOverlay.append( $menuScreens ); |
| 137 | + |
| 138 | + return $menuOverlay; |
| 139 | + |
| 140 | + } |
| 141 | + } |
| 142 | + }, |
| 143 | + |
| 144 | + /** |
| 145 | + * Get minimal width for interface overlay |
| 146 | + */ |
| 147 | + getOverlayWidth: function(){ |
| 148 | + return ( this.embedPlayer.getPlayerWidth() < 200 )? 200 : this.embedPlayer.getPlayerWidth(); |
| 149 | + }, |
| 150 | + |
| 151 | + /** |
| 152 | + * Get minimal height for interface overlay |
| 153 | + */ |
| 154 | + getOverlayHeight: function(){ |
| 155 | + return ( this.embedPlayer.getPlayerHeight() < 160 )? 160 : this.embedPlayer.getPlayerHeight(); |
| 156 | + }, |
| 157 | + |
| 158 | + /** |
| 159 | + * Adds the skin Control Bindings |
| 160 | + */ |
| 161 | + addSkinControlBindings: function() { |
| 162 | + var embedPlayer = this.embedPlayer; |
| 163 | + var _this = this; |
| 164 | + |
| 165 | + // Set up control bar pointer |
| 166 | + this.$playerTarget = embedPlayer.$interface; |
| 167 | + // Set the menu target: |
| 168 | + |
| 169 | + |
| 170 | + // Options menu display: |
| 171 | + this.$playerTarget.find( '.k-options' ) |
| 172 | + .unbind() |
| 173 | + .click( function() { |
| 174 | + _this.checkMenuOverlay(); |
| 175 | + var $kmenu = _this.$playerTarget.find( '.k-menu' ); |
| 176 | + if ( $kmenu.is( ':visible' ) ) { |
| 177 | + _this.closeMenuOverlay( ); |
| 178 | + } else { |
| 179 | + _this.showMenuOverlay( ); |
| 180 | + } |
| 181 | + } ); |
| 182 | + |
| 183 | + }, |
| 184 | + |
| 185 | + /** |
| 186 | + * checks for menu overlay and runs menu bindings if unset |
| 187 | + */ |
| 188 | + checkMenuOverlay: function(){ |
| 189 | + var _this = this; |
| 190 | + var embedPlayer = this.embedPlayer; |
| 191 | + if ( _this.$playerTarget.find( '.k-menu' ).length == 0 ) { |
| 192 | + // Stop the player if it does not support overlays: |
| 193 | + if ( !embedPlayer.supports['overlays'] ) { |
| 194 | + embedPlayer.stop(); |
| 195 | + } |
| 196 | + |
| 197 | + // Add the menu binding |
| 198 | + _this.addMenuBinding(); |
| 199 | + } |
| 200 | + }, |
| 201 | + |
| 202 | + /** |
| 203 | + * Close the menu overlay |
| 204 | + */ |
| 205 | + closeMenuOverlay: function() { |
| 206 | + mw.log("PlayerSkin: close menu overlay" ); |
| 207 | + |
| 208 | + var $optionsMenu = this.$playerTarget.find( '.k-options' ); |
| 209 | + var $kmenu = this.$playerTarget.find( '.k-menu' ); |
| 210 | + $kmenu.fadeOut( "fast", function() { |
| 211 | + $optionsMenu.find( 'span' ) |
| 212 | + .text ( gM( 'mwe-embedplayer-menu_btn' ) ); |
| 213 | + } ); |
| 214 | + this.$playerTarget.find( '.play-btn-large' ).fadeIn( 'fast' ); |
| 215 | + |
| 216 | + // re display the control bar if hidden: |
| 217 | + this.showControlBar(); |
| 218 | + |
| 219 | + // Set close overlay menu flag: |
| 220 | + this.displayOptionsMenuFlag = false; |
| 221 | + }, |
| 222 | + |
| 223 | + /** |
| 224 | + * Show the menu overlay |
| 225 | + */ |
| 226 | + showMenuOverlay: function( $ktxt ) { |
| 227 | + var $optionsMenu = this.$playerTarget.find( '.k-options' ); |
| 228 | + var $kmenu = this.$playerTarget.find( '.k-menu' ); |
| 229 | + |
| 230 | + $kmenu.fadeIn( "fast", function() { |
| 231 | + $optionsMenu.find( 'span' ) |
| 232 | + .text ( gM( 'mwe-embedplayer-close_btn' ) ); |
| 233 | + } ); |
| 234 | + this.$playerTarget.find( '.play-btn-large' ).fadeOut( 'fast' ); |
| 235 | + |
| 236 | + $(this.embedPlayer).trigger( 'displayMenuOverlay' ); |
| 237 | + |
| 238 | + // Set the Options Menu display flag to true: |
| 239 | + this.displayOptionsMenuFlag = true; |
| 240 | + }, |
| 241 | + |
| 242 | + /** |
| 243 | + * Adds binding for the options menu |
| 244 | + * |
| 245 | + * @param {Object} $tp Target video container for |
| 246 | + */ |
| 247 | + addMenuBinding: function() { |
| 248 | + var _this = this; |
| 249 | + var embedPlayer = this.embedPlayer; |
| 250 | + // Set local player target pointer: |
| 251 | + var $playerTarget = embedPlayer.$interface; |
| 252 | + |
| 253 | + // Check if k-menu already exists: |
| 254 | + if ( $playerTarget.find( '.k-menu' ).length != 0 ) |
| 255 | + return false; |
| 256 | + |
| 257 | + // Add options menu to top of player target children: |
| 258 | + $playerTarget.prepend( |
| 259 | + _this.getComponent( 'optionsMenu' ) |
| 260 | + ); |
| 261 | + |
| 262 | + // By default its hidden: |
| 263 | + $playerTarget.find( '.k-menu' ).hide(); |
| 264 | + |
| 265 | + // Add menu-items bindings: |
| 266 | + for ( var menuItem in _this.supportedMenuItems ) { |
| 267 | + $playerTarget.find( '.k-' + menuItem + '-btn' ).click( function( ) { |
| 268 | + |
| 269 | + // Grab the context from the "clicked" menu item |
| 270 | + var mk = $( this ).attr( 'rel' ); |
| 271 | + |
| 272 | + // hide all menu items |
| 273 | + $targetItem = $playerTarget.find( '.menu-' + mk ); |
| 274 | + |
| 275 | + // call the function showMenuItem |
| 276 | + _this.showMenuItem( mk ); |
| 277 | + |
| 278 | + // Hide the others |
| 279 | + $playerTarget.find( '.menu-screen' ).hide(); |
| 280 | + |
| 281 | + // Show the target menu item: |
| 282 | + $targetItem.fadeIn( "fast" ); |
| 283 | + |
| 284 | + // Don't follow the # link |
| 285 | + return false; |
| 286 | + } ); |
| 287 | + } |
| 288 | + }, |
| 289 | + |
| 290 | + /** |
| 291 | + * onClipDone action |
| 292 | + * onClipDone for k-skin (with apiTitleKey) show the "credits" screen: |
| 293 | + */ |
| 294 | + onClipDone: function(){ |
| 295 | + if( this.embedPlayer.apiTitleKey ){ |
| 296 | + this.checkMenuOverlay( ); |
| 297 | + this.showMenuOverlay(); |
| 298 | + this.showMenuItem( 'credits' ); |
| 299 | + } |
| 300 | + }, |
| 301 | + |
| 302 | + /** |
| 303 | + * Shows a selected menu_item |
| 304 | + * |
| 305 | + * NOTE: this should be merged with parent mw.PlayerControlBuilder optionMenuItems |
| 306 | + * binding mode |
| 307 | + * |
| 308 | + * @param {String} menu_itme Menu item key to display |
| 309 | + */ |
| 310 | + showMenuItem:function( menuItem ) { |
| 311 | + var embedPlayer = this.embedPlayer; |
| 312 | + //handle special k-skin specific display; |
| 313 | + switch( menuItem ){ |
| 314 | + case 'credits': |
| 315 | + this.showCredits(); |
| 316 | + break; |
| 317 | + case 'playerSelect': |
| 318 | + embedPlayer.$interface.find( '.menu-playerSelect').html( |
| 319 | + this.getPlayerSelect() |
| 320 | + ); |
| 321 | + break; |
| 322 | + case 'download' : |
| 323 | + embedPlayer.$interface.find( '.menu-download').text( |
| 324 | + gM('mwe-loading_txt' ) |
| 325 | + ); |
| 326 | + // Call show download with the target to be populated |
| 327 | + this.showDownload( |
| 328 | + embedPlayer.$interface.find( '.menu-download') |
| 329 | + ); |
| 330 | + break; |
| 331 | + case 'share': |
| 332 | + embedPlayer.$interface.find( '.menu-share' ).html( |
| 333 | + this.getShare() |
| 334 | + ); |
| 335 | + break; |
| 336 | + } |
| 337 | + }, |
| 338 | + |
| 339 | + /** |
| 340 | + * Show the credit screen ( presently specific to kaltura skin ) |
| 341 | + */ |
| 342 | + showCredits: function() { |
| 343 | + // Set up the shortcuts: |
| 344 | + var embedPlayer = this.embedPlayer; |
| 345 | + var _this = this; |
| 346 | + |
| 347 | + var $target = embedPlayer.$interface.find( '.menu-credits' ); |
| 348 | + |
| 349 | + $target.empty().append( |
| 350 | + $('<h2 />') |
| 351 | + .text( gM( 'mwe-embedplayer-credits' ) ), |
| 352 | + $('<div />') |
| 353 | + .addClass( "credits_box ui-corner-all" ) |
| 354 | + .loadingSpinner() |
| 355 | + ); |
| 356 | + |
| 357 | + if( mw.getConfig( 'EmbedPlayer.KalturaAttribution' ) == true ){ |
| 358 | + $target.append( |
| 359 | + $( '<div />' ) |
| 360 | + .addClass( 'k-attribution' ) |
| 361 | + .attr({ |
| 362 | + 'title': gM('mwe-embedplayer-kaltura-platform-title') |
| 363 | + }) |
| 364 | + .click( function( ) { |
| 365 | + window.location = 'http://kaltura.com'; |
| 366 | + }) |
| 367 | + ); |
| 368 | + } |
| 369 | + var $creditsTarget = embedPlayer.$interface.find( '.menu-credits .credits_box' ); |
| 370 | + |
| 371 | + // Allow modules to load and add credits |
| 372 | + $( embedPlayer ).triggerQueueCallback( 'ShowCredits', $creditsTarget, function( status ){ |
| 373 | + // Check if the first ShowCredits binding returned false: |
| 374 | + if( !status || status[0] == false ){ |
| 375 | + $creditsTarget.text( |
| 376 | + gM('mwe-embedplayer-no-video_credits') |
| 377 | + ); |
| 378 | + } |
| 379 | + }); |
| 380 | + } |
| 381 | +}; |
| 382 | + |
| 383 | +} )( window.mediaWiki, window.jQuery ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/mw.PlayerSkinKskin.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 384 | + text/plain |
Added: svn:eol-style |
2 | 385 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js |
— | — | @@ -0,0 +1,1920 @@ |
| 2 | +/** |
| 3 | +* Msg text is inherited from embedPlayer |
| 4 | +*/ |
| 5 | + |
| 6 | +( function( mw, $ ) { |
| 7 | +/** |
| 8 | +* mw.PlayerControlBuilder object |
| 9 | +* @param the embedPlayer element we are targeting |
| 10 | +*/ |
| 11 | +mw.PlayerControlBuilder = function( embedPlayer, options ) { |
| 12 | + return this.init( embedPlayer, options ); |
| 13 | +}; |
| 14 | + |
| 15 | +/** |
| 16 | + * ControlsBuilder prototype: |
| 17 | + */ |
| 18 | +mw.PlayerControlBuilder.prototype = { |
| 19 | + //Default Local values: |
| 20 | + |
| 21 | + // Parent css Class name |
| 22 | + playerClass : 'mv-player', |
| 23 | + |
| 24 | + // Long string display of time value |
| 25 | + longTimeDisp: true, |
| 26 | + |
| 27 | + // Default volume layout is "vertical" |
| 28 | + volume_layout : 'vertical', |
| 29 | + |
| 30 | + // Default control bar height |
| 31 | + height: 31, |
| 32 | + |
| 33 | + // Default supported components is merged with embedPlayer set of supported types |
| 34 | + supportedComponets: { |
| 35 | + // All playback types support options |
| 36 | + 'options': true |
| 37 | + }, |
| 38 | + |
| 39 | + // Default supported menu items is merged with skin menu items |
| 40 | + supportedMenuItems: { |
| 41 | + // Player Select |
| 42 | + 'playerSelect' : true, |
| 43 | + |
| 44 | + // Download the file menu |
| 45 | + 'download' : true, |
| 46 | + |
| 47 | + // Share the video menu |
| 48 | + 'share' : true, |
| 49 | + |
| 50 | + // Player library link |
| 51 | + 'aboutPlayerLibrary': true |
| 52 | + }, |
| 53 | + |
| 54 | + // Flag to store the current fullscreen mode |
| 55 | + fullscreenMode: false, |
| 56 | + |
| 57 | + // Flag to store if a warning binding has been added |
| 58 | + addWarningFlag: false, |
| 59 | + |
| 60 | + // Flag to store state of overlay on player |
| 61 | + keepControlBarOnScreen: false, |
| 62 | + |
| 63 | + /** |
| 64 | + * Initialization Object for the control builder |
| 65 | + * |
| 66 | + * @param {Object} embedPlayer EmbedPlayer interface |
| 67 | + */ |
| 68 | + init: function( embedPlayer ) { |
| 69 | + var _this = this; |
| 70 | + this.embedPlayer = embedPlayer; |
| 71 | + |
| 72 | + // Check for skin overrides for controlBuilder |
| 73 | + var skinClass = embedPlayer.skinName.substr(0,1).toUpperCase() + embedPlayer.skinName.substr( 1 ); |
| 74 | + if ( mw['PlayerSkin' + skinClass ]) { |
| 75 | + |
| 76 | + // Clone as to not override prototype with the skin config |
| 77 | + var _this = $j.extend( true, { }, this, mw['PlayerSkin' + skinClass ] ); |
| 78 | + return _this; |
| 79 | + } |
| 80 | + // Return the controlBuilder Object: |
| 81 | + return this; |
| 82 | + }, |
| 83 | + |
| 84 | + /** |
| 85 | + * Get the control bar height |
| 86 | + * @return {Number} control bar height |
| 87 | + */ |
| 88 | + getHeight: function(){ |
| 89 | + return this.height; |
| 90 | + }, |
| 91 | + |
| 92 | + /** |
| 93 | + * Add the controls html to player interface |
| 94 | + */ |
| 95 | + addControls: function() { |
| 96 | + // Set up local pointer to the embedPlayer |
| 97 | + var embedPlayer = this.embedPlayer; |
| 98 | + |
| 99 | + // Set up local controlBuilder |
| 100 | + var _this = this; |
| 101 | + |
| 102 | + // Remove any old controls & old overlays: |
| 103 | + embedPlayer.$interface.find( '.control-bar,.overlay-win' ).remove(); |
| 104 | + |
| 105 | + // Reset flag: |
| 106 | + _this.keepControlBarOnScreen = false; |
| 107 | + |
| 108 | + |
| 109 | + // Setup the controlBar container ( starts hidden ) |
| 110 | + var $controlBar = $('<div />') |
| 111 | + .addClass( 'ui-state-default ui-widget-header ui-helper-clearfix control-bar' ) |
| 112 | + .css( 'height', this.height ); |
| 113 | + |
| 114 | + // Controls are hidden by default if overlaying controls: |
| 115 | + if( _this.checkOverlayControls() ){ |
| 116 | + $controlBar.hide(); |
| 117 | + } |
| 118 | + |
| 119 | + $controlBar.css( { |
| 120 | + 'position': 'absolute', |
| 121 | + 'bottom' : '0px', |
| 122 | + 'left' : '0px', |
| 123 | + 'right' : '0px' |
| 124 | + } ); |
| 125 | + |
| 126 | + // Check for overlay controls: |
| 127 | + /*if( ! _this.checkOverlayControls() && ! embedPlayer.controls === false ) { |
| 128 | + // Add some space to interface for the control bar ( if not overlaying controls ) |
| 129 | + $( embedPlayer ).css( { |
| 130 | + 'height' : parseInt( embedPlayer.height ) - parseInt( this.height ) |
| 131 | + } ); |
| 132 | + }*/ |
| 133 | + // Make room for audio controls in the interface: |
| 134 | + if( embedPlayer.isAudio() && embedPlayer.$interface.height() == 0 ){ |
| 135 | + embedPlayer.$interface.css( { |
| 136 | + 'height' : this.height |
| 137 | + } ); |
| 138 | + } |
| 139 | + |
| 140 | + // Add the controls to the interface |
| 141 | + embedPlayer.$interface.append( $controlBar ); |
| 142 | + |
| 143 | + // Add the Controls Component |
| 144 | + this.addControlComponents(); |
| 145 | + |
| 146 | + // Add top level Controls bindings |
| 147 | + this.addControlBindings(); |
| 148 | + }, |
| 149 | + |
| 150 | + /** |
| 151 | + * Add control components as defined per this.components |
| 152 | + */ |
| 153 | + addControlComponents: function( ) { |
| 154 | + var _this = this; |
| 155 | + |
| 156 | + // Set up local pointer to the embedPlayer |
| 157 | + var embedPlayer = this.embedPlayer; |
| 158 | + |
| 159 | + //Set up local var to control container: |
| 160 | + var $controlBar = embedPlayer.$interface.find( '.control-bar' ); |
| 161 | + |
| 162 | + this.available_width = embedPlayer.getPlayerWidth(); |
| 163 | + |
| 164 | + mw.log( 'PlayerControlsBuilder:: addControlComponents into:' + this.available_width ); |
| 165 | + // Build the supportedComponets list |
| 166 | + this.supportedComponets = $j.extend( this.supportedComponets, embedPlayer.supports ); |
| 167 | + |
| 168 | + $( embedPlayer ).trigger( 'addControlBarComponent', this); |
| 169 | + |
| 170 | + // Check for Attribution button |
| 171 | + if( mw.getConfig( 'EmbedPlayer.AttributionButton' ) && embedPlayer.attributionbutton ){ |
| 172 | + this.supportedComponets[ 'attributionButton' ] = true; |
| 173 | + } |
| 174 | + |
| 175 | + // Check global fullscreen enabled flag |
| 176 | + if( mw.getConfig( 'EmbedPlayer.EnableFullscreen' ) === false ){ |
| 177 | + this.supportedComponets[ 'fullscreen'] = false; |
| 178 | + } |
| 179 | + // Check if the options item is available |
| 180 | + if( mw.getConfig( 'EmbedPlayer.EnableOptionsMenu' ) === false ){ |
| 181 | + this.supportedComponets[ 'options'] = false; |
| 182 | + } |
| 183 | + |
| 184 | + |
| 185 | + var addComponent = function( component_id ){ |
| 186 | + if ( _this.supportedComponets[ component_id ] ) { |
| 187 | + if ( _this.available_width > _this.components[ component_id ].w ) { |
| 188 | + // Append the component |
| 189 | + $controlBar.append( |
| 190 | + _this.getComponent( component_id ) |
| 191 | + ); |
| 192 | + _this.available_width -= _this.components[ component_id ].w; |
| 193 | + } else { |
| 194 | + mw.log( 'Not enough space for control component:' + component_id ); |
| 195 | + } |
| 196 | + } |
| 197 | + }; |
| 198 | + |
| 199 | + // Output components |
| 200 | + for ( var component_id in this.components ) { |
| 201 | + // Check for (component === false ) and skip |
| 202 | + if( this.components[ component_id ] === false ){ |
| 203 | + continue; |
| 204 | + } |
| 205 | + |
| 206 | + // Special case with playhead and time ( to make sure they are to the left of everything else ) |
| 207 | + if ( component_id == 'playHead' || component_id == 'timeDisplay'){ |
| 208 | + continue; |
| 209 | + } |
| 210 | + |
| 211 | + // Skip "fullscreen" button for assets or where height is 0px ( audio ) |
| 212 | + if( component_id == 'fullscreen' && this.embedPlayer.isAudio() ){ |
| 213 | + continue; |
| 214 | + } |
| 215 | + addComponent( component_id ); |
| 216 | + } |
| 217 | + // Add special case remaining components: |
| 218 | + addComponent( 'timeDisplay' ); |
| 219 | + if( this.available_width > 30 ){ |
| 220 | + addComponent( 'playHead' ); |
| 221 | + } |
| 222 | + |
| 223 | + }, |
| 224 | + |
| 225 | + /** |
| 226 | + * Get a window size for the player while preserving aspect ratio: |
| 227 | + * |
| 228 | + * @param {object} windowSize |
| 229 | + * object that set { 'width': {width}, 'height':{height} } of target window |
| 230 | + * @return {object} |
| 231 | + * css settings for fullscreen player |
| 232 | + */ |
| 233 | + getAspectPlayerWindowCss: function( windowSize ) { |
| 234 | + var embedPlayer = this.embedPlayer; |
| 235 | + var _this = this; |
| 236 | + // Setup target height width based on max window size |
| 237 | + if( !windowSize ){ |
| 238 | + var windowSize = { |
| 239 | + 'width' : $( window ).width(), |
| 240 | + 'height' : $( window ).height() |
| 241 | + }; |
| 242 | + } |
| 243 | + // Set target width |
| 244 | + var targetWidth = windowSize.width; |
| 245 | + var targetHeight = targetWidth * ( embedPlayer.getHeight() / embedPlayer.getWidth() ); |
| 246 | + |
| 247 | + // Check if it exceeds the height constraint: |
| 248 | + if( targetHeight > windowSize.height ){ |
| 249 | + targetHeight = windowSize.height; |
| 250 | + targetWidth = targetHeight * ( embedPlayer.getWidth() / embedPlayer.getHeight() ); |
| 251 | + } |
| 252 | + var offsetTop = ( targetHeight < windowSize.height )? ( windowSize.height- targetHeight ) / 2 : 0; |
| 253 | + var offsetLeft = ( targetWidth < windowSize.width )? ( windowSize.width- targetWidth ) / 2 : 0; |
| 254 | + |
| 255 | + // See if we need to leave space for control bar |
| 256 | + if( !_this.checkOverlayControls() ){ |
| 257 | + targetHeight = targetHeight - this.height; |
| 258 | + offsetTop = offsetTop - this.height; |
| 259 | + if( offsetTop < 0 ) offsetTop = 0; |
| 260 | + } |
| 261 | + //mw.log("left: " + offsetLeft + " targetWidth: " + targetWidth + ' windowSize.width: ' + windowSize.width + ' :: ' + ( windowSize.width- targetWidth ) / 2 ); |
| 262 | + return { |
| 263 | + 'position' : 'absolute', |
| 264 | + 'height': targetHeight, |
| 265 | + 'width' : targetWidth, |
| 266 | + 'top' : offsetTop, |
| 267 | + 'left': offsetLeft |
| 268 | + }; |
| 269 | + }, |
| 270 | + |
| 271 | + /** |
| 272 | + * Get the fullscreen play button css |
| 273 | + */ |
| 274 | + getFullscreenPlayButtonCss: function( size ) { |
| 275 | + var _this = this; |
| 276 | + var pos = this.getAspectPlayerWindowCss( size ); |
| 277 | + if( !_this.checkOverlayControls() ){ |
| 278 | + pos.top = pos.top - this.height; |
| 279 | + } |
| 280 | + return { |
| 281 | + 'left' : ( ( pos.width - this.getComponentWidth( 'playButtonLarge' ) ) / 2 ), |
| 282 | + 'top' : ( ( pos.height - this.getComponentHeight( 'playButtonLarge' ) ) / 2 ) |
| 283 | + }; |
| 284 | + }, |
| 285 | + |
| 286 | + /** |
| 287 | + * Toggles full screen by calling |
| 288 | + * doFullScreenPlayer to enable fullscreen mode |
| 289 | + * restoreWindowPlayer to restore window mode |
| 290 | + */ |
| 291 | + toggleFullscreen: function( forceClose ) { |
| 292 | + var _this = this; |
| 293 | + |
| 294 | + // Check if iFrame mode ( fullscreen is handled by the iframe parent dom ) |
| 295 | + if( mw.getConfig('EmbedPlayer.IsIframePlayer' ) ){ |
| 296 | + if( this.fullscreenMode ){ |
| 297 | + $( _this.embedPlayer ).trigger( 'onCloseFullScreen' ); |
| 298 | + this.fullscreenMode = false; |
| 299 | + } else { |
| 300 | + $( _this.embedPlayer ).trigger( 'onOpenFullScreen' ); |
| 301 | + this.fullscreenMode = true; |
| 302 | + } |
| 303 | + return ; |
| 304 | + } |
| 305 | + |
| 306 | + // Do normal in-page fullscreen handling: |
| 307 | + if( this.fullscreenMode ){ |
| 308 | + this.restoreWindowPlayer(); |
| 309 | + }else{ |
| 310 | + this.doFullScreenPlayer(); |
| 311 | + } |
| 312 | + }, |
| 313 | + |
| 314 | + /** |
| 315 | + * Do full-screen mode |
| 316 | + */ |
| 317 | + doFullScreenPlayer: function( callback) { |
| 318 | + mw.log(" controlBuilder :: toggle full-screen "); |
| 319 | + // Setup pointer to control builder : |
| 320 | + var _this = this; |
| 321 | + |
| 322 | + // Setup local reference to embed player: |
| 323 | + var embedPlayer = this.embedPlayer; |
| 324 | + |
| 325 | + // Setup a local reference to the player interface: |
| 326 | + var $interface = embedPlayer.$interface; |
| 327 | + |
| 328 | + |
| 329 | + // Check fullscreen state ( if already true do nothing ) |
| 330 | + if( this.fullscreenMode == true ){ |
| 331 | + return ; |
| 332 | + } |
| 333 | + this.fullscreenMode = true; |
| 334 | + |
| 335 | + //Remove any old mw-fullscreen-overlay |
| 336 | + $( '.mw-fullscreen-overlay' ).remove(); |
| 337 | + |
| 338 | + // Special hack for mediawiki monobook skin search box |
| 339 | + if( $( '#p-search,#p-logo' ).length ) { |
| 340 | + $( '#p-search,#p-logo,#ca-nstab-project a' ).css('z-index', 1); |
| 341 | + } |
| 342 | + |
| 343 | + // Add the css fixed fullscreen black overlay as a sibling to the video element |
| 344 | + $interface.after( |
| 345 | + $( '<div />' ) |
| 346 | + .addClass( 'mw-fullscreen-overlay' ) |
| 347 | + // Set some arbitrary high z-index |
| 348 | + .css('z-index', mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) ) |
| 349 | + .hide() |
| 350 | + .fadeIn("slow") |
| 351 | + ); |
| 352 | + |
| 353 | + |
| 354 | + // Change the interface to absolute positioned: |
| 355 | + this.windowPositionStyle = $interface.css( 'position' ); |
| 356 | + this.windowZindex = $interface.css( 'z-index' ); |
| 357 | + |
| 358 | + // Get the base offset: |
| 359 | + this.windowOffset = $interface.offset(); |
| 360 | + this.windowOffset.top = this.windowOffset.top - $(document).scrollTop(); |
| 361 | + this.windowOffset.left = this.windowOffset.left - $(document).scrollLeft(); |
| 362 | + |
| 363 | + // Change the z-index of the interface |
| 364 | + $interface.css( { |
| 365 | + 'position' : 'fixed', |
| 366 | + 'z-index' : mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) + 1, |
| 367 | + 'top' : this.windowOffset.top, |
| 368 | + 'left' : this.windowOffset.left |
| 369 | + } ); |
| 370 | + |
| 371 | + // If native persistent native player update z-index: |
| 372 | + if( embedPlayer.isPersistentNativePlayer() ){ |
| 373 | + $( embedPlayer.getPlayerElement() ).css( { |
| 374 | + 'z-index': mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) + 1, |
| 375 | + 'position': 'absolute' |
| 376 | + }); |
| 377 | + } |
| 378 | + |
| 379 | + // Empty out the parent absolute index |
| 380 | + _this.parentsAbsolute = []; |
| 381 | + |
| 382 | + // Hide the body scroll bar |
| 383 | + $('body').css( 'overflow', 'hidden' ); |
| 384 | + |
| 385 | + |
| 386 | + var topOffset = '0px'; |
| 387 | + var leftOffset = '0px'; |
| 388 | + |
| 389 | + // Check if we have an offsetParent |
| 390 | + if( $interface.offsetParent().get(0).tagName.toLowerCase() != 'body' ) { |
| 391 | + topOffset = -this.windowOffset.top + 'px'; |
| 392 | + leftOffset = -this.windowOffset.left + 'px'; |
| 393 | + } |
| 394 | + |
| 395 | + |
| 396 | + // Set the player height width: |
| 397 | + $( embedPlayer ).css( { |
| 398 | + 'position' : 'relative' |
| 399 | + } ); |
| 400 | + |
| 401 | + // Overflow hidden in fullscreen: |
| 402 | + $interface.css( 'overlow', 'hidden' ); |
| 403 | + |
| 404 | + // Resize the player keeping aspect and with the widow scroll offset: |
| 405 | + embedPlayer.resizePlayer({ |
| 406 | + 'top' : topOffset, |
| 407 | + 'left' : leftOffset, |
| 408 | + 'width' : $( window ).width(), |
| 409 | + 'height' : $( window ).height() |
| 410 | + }, true, function(){ |
| 411 | + // Trigger the enter fullscreen event |
| 412 | + $( _this.embedPlayer ).trigger( 'onOpenFullScreen' ); |
| 413 | + }); |
| 414 | + |
| 415 | + // Remove absolute css of the interface parents |
| 416 | + $interface.parents().each( function() { |
| 417 | + //mw.log(' parent : ' + $( this ).attr('id' ) + ' class: ' + $( this ).attr('class') + ' pos: ' + $( this ).css( 'position' ) ); |
| 418 | + if( $( this ).css( 'position' ) == 'absolute' ) { |
| 419 | + _this.parentsAbsolute.push( $( this ) ); |
| 420 | + $( this ).css( 'position', null ); |
| 421 | + mw.log(' should update position: ' + $( this ).css( 'position' ) ); |
| 422 | + } |
| 423 | + }); |
| 424 | + |
| 425 | + |
| 426 | + |
| 427 | + // Bind mouse move in interface to hide control bar |
| 428 | + _this.mouseMovedFlag = false; |
| 429 | + $interface.mousemove( function(e){ |
| 430 | + _this.mouseMovedFlag = true; |
| 431 | + }); |
| 432 | + |
| 433 | + // Check every 2 seconds reset flag status if controls are overlay |
| 434 | + if( _this.checkOverlayControls() ){ |
| 435 | + function checkMovedMouse(){ |
| 436 | + if( _this.fullscreenMode ){ |
| 437 | + if( _this.mouseMovedFlag ){ |
| 438 | + _this.mouseMovedFlag = false; |
| 439 | + _this.showControlBar(); |
| 440 | + // Once we move the mouse keep displayed for 4 seconds |
| 441 | + setTimeout(checkMovedMouse, 4000); |
| 442 | + } else { |
| 443 | + // Check for mouse movement every 250ms |
| 444 | + _this.hideControlBar(); |
| 445 | + setTimeout(checkMovedMouse, 250 ); |
| 446 | + } |
| 447 | + } |
| 448 | + }; |
| 449 | + checkMovedMouse(); |
| 450 | + } |
| 451 | + |
| 452 | + // Bind Scroll position update |
| 453 | + |
| 454 | + // Bind resize resize window to resize window |
| 455 | + $( window ).resize( function() { |
| 456 | + if( _this.fullscreenMode ){ |
| 457 | + embedPlayer.resizePlayer({ |
| 458 | + 'width' : $( window ).width(), |
| 459 | + 'height' : $( window ).height() |
| 460 | + }); |
| 461 | + } |
| 462 | + }); |
| 463 | + |
| 464 | + // Bind escape to restore in page clip |
| 465 | + $( window ).keyup( function(event) { |
| 466 | + // Escape check |
| 467 | + if( event.keyCode == 27 ){ |
| 468 | + _this.restoreWindowPlayer(); |
| 469 | + } |
| 470 | + } ); |
| 471 | + }, |
| 472 | + |
| 473 | + /** |
| 474 | + * Resize the player to a target size keeping aspect ratio |
| 475 | + */ |
| 476 | + resizePlayer: function( size, animate, callback ){ |
| 477 | + var _this = this; |
| 478 | + // Update interface container: |
| 479 | + var interfaceCss = { |
| 480 | + 'top' : ( size.top ) ? size.top : '0px', |
| 481 | + 'left' : ( size.left ) ? size.left : '0px', |
| 482 | + 'width' : size.width, |
| 483 | + 'height' : size.height |
| 484 | + }; |
| 485 | + // Set up local pointer to interface: |
| 486 | + var embedPlayer = this.embedPlayer; |
| 487 | + var $interface = embedPlayer.$interface; |
| 488 | + if( animate ){ |
| 489 | + $interface.animate( interfaceCss ); |
| 490 | + // Update player size |
| 491 | + $( embedPlayer ).animate( _this.getAspectPlayerWindowCss( size ), callback ); |
| 492 | + // Update play button pos |
| 493 | + $interface.find('.play-btn-large').animate( _this.getFullscreenPlayButtonCss( size ) ); |
| 494 | + |
| 495 | + if( embedPlayer.isPersistentNativePlayer() ){ |
| 496 | + $( embedPlayer.getPlayerElement() ).animate( _this.getAspectPlayerWindowCss( size ) ); |
| 497 | + } |
| 498 | + } else { |
| 499 | + $interface.css( interfaceCss ); |
| 500 | + // Update player size |
| 501 | + $( embedPlayer ).css( _this.getAspectPlayerWindowCss( size ) ); |
| 502 | + // Update play button pos |
| 503 | + $interface.find('.play-btn-large').css( _this.getFullscreenPlayButtonCss( size ) ); |
| 504 | + |
| 505 | + if( embedPlayer.isPersistentNativePlayer() ){ |
| 506 | + $( embedPlayer.getPlayerElement() ).css( _this.getAspectPlayerWindowCss( size ) ); |
| 507 | + } |
| 508 | + |
| 509 | + if( callback ){ |
| 510 | + callback(); |
| 511 | + } |
| 512 | + } |
| 513 | + }, |
| 514 | + |
| 515 | + /** |
| 516 | + * Restore the window player |
| 517 | + */ |
| 518 | + restoreWindowPlayer: function() { |
| 519 | + var _this = this; |
| 520 | + var embedPlayer = this.embedPlayer; |
| 521 | + |
| 522 | + // Check fullscreen state |
| 523 | + if( this.fullscreenMode == false ){ |
| 524 | + return ; |
| 525 | + } |
| 526 | + // Set fullscreen mode to false |
| 527 | + this.fullscreenMode = false; |
| 528 | + |
| 529 | + var $interface = embedPlayer.$interface; |
| 530 | + var interfaceHeight = ( _this.checkOverlayControls() ) |
| 531 | + ? embedPlayer.getHeight() |
| 532 | + : embedPlayer.getHeight() + _this.getHeight(); |
| 533 | + |
| 534 | + mw.log( 'restoreWindowPlayer:: h:' + interfaceHeight + ' w:' + embedPlayer.getWidth()); |
| 535 | + $('.mw-fullscreen-overlay').fadeOut( 'slow' ); |
| 536 | + |
| 537 | + mw.log( 'restore embedPlayer:: ' + embedPlayer.getWidth() + ' h: ' + embedPlayer.getHeight()); |
| 538 | + // Restore the player: |
| 539 | + embedPlayer.resizePlayer( { |
| 540 | + 'top' : _this.windowOffset.top + 'px', |
| 541 | + 'left' : _this.windowOffset.left + 'px', |
| 542 | + 'width' : embedPlayer.getWidth(), |
| 543 | + 'height' : embedPlayer.getHeight() |
| 544 | + }, true, function(){ |
| 545 | + // Restore non-absolute layout: |
| 546 | + $interface.css({ |
| 547 | + 'position' : _this.windowPositionStyle, |
| 548 | + 'z-index' : _this.windowZindex, |
| 549 | + 'overlow' : 'visible', |
| 550 | + 'top' : '0px', |
| 551 | + 'left' : '0px' |
| 552 | + }); |
| 553 | + |
| 554 | + // Restore absolute layout of parents: |
| 555 | + $j.each( _this.parentsAbsolute, function( na, element ){ |
| 556 | + $( element ).css( 'position', 'absolute' ); |
| 557 | + } ); |
| 558 | + _this.parentsAbsolute = null; |
| 559 | + |
| 560 | + // Restore the body scroll bar |
| 561 | + $('body').css( 'overflow', 'auto' ); |
| 562 | + |
| 563 | + // If native player restore z-index: |
| 564 | + if( embedPlayer.isPersistentNativePlayer() ){ |
| 565 | + $( embedPlayer.getPlayerElement() ).css( { |
| 566 | + 'z-index': 'auto' |
| 567 | + }); |
| 568 | + } |
| 569 | + }); |
| 570 | + |
| 571 | + // Trigger the onCloseFullscreen event: |
| 572 | + $( this.embedPlayer ).trigger( 'onCloseFullScreen' ); |
| 573 | + }, |
| 574 | + |
| 575 | + /** |
| 576 | + * Get minimal width for interface overlay |
| 577 | + */ |
| 578 | + getOverlayWidth: function( ) { |
| 579 | + return ( this.embedPlayer.getPlayerWidth() < 300 )? 300 : this.embedPlayer.getPlayerWidth(); |
| 580 | + }, |
| 581 | + |
| 582 | + /** |
| 583 | + * Get minimal height for interface overlay |
| 584 | + */ |
| 585 | + getOverlayHeight: function( ) { |
| 586 | + return ( this.embedPlayer.getPlayerHeight() < 200 )? 200 : this.embedPlayer.getPlayerHeight(); |
| 587 | + }, |
| 588 | + |
| 589 | + /** |
| 590 | + * addControlBindings |
| 591 | + * Adds control hooks once controls are in the DOM |
| 592 | + */ |
| 593 | + addControlBindings: function() { |
| 594 | + // Set up local pointer to the embedPlayer |
| 595 | + var embedPlayer = this.embedPlayer; |
| 596 | + var _this = this; |
| 597 | + var $interface = embedPlayer.$interface; |
| 598 | + |
| 599 | + // Remove any old interface bindings |
| 600 | + $interface.unbind(); |
| 601 | + |
| 602 | + var bindFirstPlay = false; |
| 603 | + |
| 604 | + // Bind into play.ctrl namespace ( so we can unbind without affecting other play bindings ) |
| 605 | + $(embedPlayer).unbind('play.ctrl').bind('play.ctrl', function() { //Only bind once played |
| 606 | + if(bindFirstPlay) { |
| 607 | + return ; |
| 608 | + } |
| 609 | + bindFirstPlay = true; |
| 610 | + |
| 611 | + var dblClickTime = 300; |
| 612 | + var lastClickTime = 0; |
| 613 | + var didDblClick = false; |
| 614 | + // Remove parent dbl click ( so we can handle play clicks ) |
| 615 | + $( embedPlayer ).unbind("dblclick").click( function() { |
| 616 | + // Don't bind anything if native controls displayed: |
| 617 | + if( embedPlayer.getPlayerElement().controls ) { |
| 618 | + return ; |
| 619 | + } |
| 620 | + var clickTime = new Date().getTime(); |
| 621 | + if( clickTime -lastClickTime < dblClickTime ) { |
| 622 | + embedPlayer.fullscreen(); |
| 623 | + didDblClick = true; |
| 624 | + setTimeout( function(){ didDblClick = false; }, dblClickTime + 10 ); |
| 625 | + } |
| 626 | + lastClickTime = clickTime; |
| 627 | + setTimeout( function(){ |
| 628 | + // check if no click has since the time we called the setTimeout |
| 629 | + if( !didDblClick ){ |
| 630 | + if( embedPlayer.paused ) { |
| 631 | + embedPlayer.play(); |
| 632 | + } else { |
| 633 | + embedPlayer.pause(); |
| 634 | + } |
| 635 | + } |
| 636 | + }, dblClickTime ); |
| 637 | + |
| 638 | + }); |
| 639 | + }); |
| 640 | + |
| 641 | + var bindSpaceUp = function(){ |
| 642 | + $(window).bind('keyup.mwPlayer', function(e) { |
| 643 | + if(e.keyCode == 32) { |
| 644 | + if(embedPlayer.paused) { |
| 645 | + embedPlayer.play(); |
| 646 | + } else { |
| 647 | + embedPlayer.pause(); |
| 648 | + } |
| 649 | + return false; |
| 650 | + } |
| 651 | + }); |
| 652 | + }; |
| 653 | + |
| 654 | + var bindSpaceDown = function() { |
| 655 | + $(window).unbind('keyup.mwPlayer'); |
| 656 | + }; |
| 657 | + // Add hide show bindings for control overlay (if overlay is enabled ) |
| 658 | + if( ! _this.checkOverlayControls() ) { |
| 659 | + $interface |
| 660 | + .show() |
| 661 | + .hover( bindSpaceUp, bindSpaceDown ); |
| 662 | + |
| 663 | + } else { // hide show controls: |
| 664 | + |
| 665 | + // Show controls on click: |
| 666 | + $(embedPlayer).unbind('click.showCtrlBar').bind('click.showCtrlBar', function(){ |
| 667 | + _this.showControlBar(); |
| 668 | + }); |
| 669 | + |
| 670 | + // $interface.css({'background-color': 'red'}); |
| 671 | + // Bind a startTouch to show controls |
| 672 | + $interface.bind( 'touchstart', function() { |
| 673 | + _this.showControlBar(); |
| 674 | + // ( once the user touched the video "don't hide" ) |
| 675 | + } ); |
| 676 | + |
| 677 | + // Add a special absolute overlay for hover ( to keep menu displayed |
| 678 | + |
| 679 | + $interface.hoverIntent({ |
| 680 | + 'sensitivity': 100, |
| 681 | + 'timeout' : 1000, |
| 682 | + 'over' : function(){ |
| 683 | + // Show controls with a set timeout ( avoid fade in fade out on short mouse over ) |
| 684 | + _this.showControlBar(); |
| 685 | + bindSpaceUp(); |
| 686 | + }, |
| 687 | + 'out' : function(){ |
| 688 | + _this.hideControlBar(); |
| 689 | + bindSpaceDown(); |
| 690 | + } |
| 691 | + }); |
| 692 | + |
| 693 | + } |
| 694 | + |
| 695 | + // Add recommend firefox if we have non-native playback: |
| 696 | + if ( _this.checkNativeWarning( ) ) { |
| 697 | + _this.doWarningBindinng( 'EmbedPlayer.ShowNativeWarning', |
| 698 | + gM( 'mwe-embedplayer-for_best_experience', mw.getConfig('EmbedPlayer.FirefoxLink') ) |
| 699 | + ); |
| 700 | + } |
| 701 | + |
| 702 | + // Do png fix for ie6 |
| 703 | + if ( $j.browser.msie && $j.browser.version <= 6 ) { |
| 704 | + $( '#' + embedPlayer.id + ' .play-btn-large' ).pngFix(); |
| 705 | + } |
| 706 | + |
| 707 | + this.doVolumeBinding(); |
| 708 | + |
| 709 | + // Check if we have any custom skin Bindings to run |
| 710 | + if ( this.addSkinControlBindings && typeof( this.addSkinControlBindings ) == 'function' ){ |
| 711 | + this.addSkinControlBindings(); |
| 712 | + } |
| 713 | + |
| 714 | + mw.log('trigger::addControlBindingsEvent'); |
| 715 | + $( embedPlayer ).trigger( 'addControlBindingsEvent'); |
| 716 | + }, |
| 717 | + |
| 718 | + /** |
| 719 | + * Hide the control bar. |
| 720 | + */ |
| 721 | + hideControlBar : function( forceClose ){ |
| 722 | + var animateDuration = 'fast'; |
| 723 | + var _this = this; |
| 724 | + |
| 725 | + if( forceClose ){ |
| 726 | + _this.keepControlBarOnScreen = false; |
| 727 | + } |
| 728 | + |
| 729 | + // Do not hide control bar if overlay menu item is being displayed: |
| 730 | + if( _this.keepControlBarOnScreen ) { |
| 731 | + setTimeout( function(){ |
| 732 | + _this.hideControlBar(); |
| 733 | + }, 200 ); |
| 734 | + return ; |
| 735 | + } |
| 736 | + |
| 737 | + |
| 738 | + // Hide the control bar |
| 739 | + this.embedPlayer.$interface.find( '.control-bar') |
| 740 | + .fadeOut( animateDuration ); |
| 741 | + //mw.log('about to trigger hide control bar') |
| 742 | + // Allow interface items to update: |
| 743 | + $( this.embedPlayer ).trigger('onHideControlBar', {'bottom' : 15} ); |
| 744 | + |
| 745 | + }, |
| 746 | + |
| 747 | + /** |
| 748 | + * Show the control bar |
| 749 | + */ |
| 750 | + showControlBar: function( keepOnScreen ){ |
| 751 | + var animateDuration = 'fast'; |
| 752 | + if(! this.embedPlayer ) |
| 753 | + return ; |
| 754 | + if( keepOnScreen ){ |
| 755 | + this.keepControlBarOnScreen = true; |
| 756 | + } |
| 757 | + |
| 758 | + if( this.embedPlayer.getPlayerElement && ! this.embedPlayer.isPersistentNativePlayer() ){ |
| 759 | + $( this.embedPlayer.getPlayerElement() ).css( 'z-index', '1' ); |
| 760 | + } |
| 761 | + mw.log( 'PlayerControlBuilder:: ShowControlBar' ); |
| 762 | + |
| 763 | + // Show interface controls |
| 764 | + this.embedPlayer.$interface.find( '.control-bar' ) |
| 765 | + .fadeIn( animateDuration ); |
| 766 | + |
| 767 | + // Trigger the screen overlay with layout info: |
| 768 | + $( this.embedPlayer ).trigger( 'onShowControlBar', {'bottom' : this.getHeight() + 15 } ); |
| 769 | + }, |
| 770 | + |
| 771 | + /** |
| 772 | + * Checks if the browser supports overlays and the controlsOverlay is |
| 773 | + * set to true for the player or via config |
| 774 | + */ |
| 775 | + checkOverlayControls: function(){ |
| 776 | + |
| 777 | + //if the player "supports" overlays: |
| 778 | + if( ! this.embedPlayer.supports['overlays'] ){ |
| 779 | + return false; |
| 780 | + } |
| 781 | + |
| 782 | + // If disabled via the player |
| 783 | + if( this.embedPlayer.overlaycontrols === false ){ |
| 784 | + return false; |
| 785 | + } |
| 786 | + |
| 787 | + // If the config is false |
| 788 | + if( mw.getConfig( 'EmbedPlayer.OverlayControls' ) === false){ |
| 789 | + return false; |
| 790 | + } |
| 791 | + // iPad supports overlays but the touch events mean we want the controls displayed all the |
| 792 | + // time for now. |
| 793 | + if( mw.isIpad() ){ |
| 794 | + return false; |
| 795 | + } |
| 796 | + |
| 797 | + |
| 798 | + // Don't hide controls when content "height" is 0px ( audio tags ) |
| 799 | + if( this.embedPlayer.getPlayerHeight() === 0 && |
| 800 | + $(this.embedPlayer).css('height').indexOf('%') === -1 ){ |
| 801 | + return false; |
| 802 | + } |
| 803 | + if( this.embedPlayer.controls === false ){ |
| 804 | + return false; |
| 805 | + } |
| 806 | + |
| 807 | + // Past all tests OverlayControls is true: |
| 808 | + return true; |
| 809 | + }, |
| 810 | + |
| 811 | + /** |
| 812 | + * Check if a warning should be issued to non-native playback systems |
| 813 | + * |
| 814 | + * dependent on mediaElement being setup |
| 815 | + */ |
| 816 | + checkNativeWarning: function( ) { |
| 817 | + if( mw.getConfig( 'EmbedPlayer.ShowNativeWarning' ) === false ){ |
| 818 | + return false; |
| 819 | + } |
| 820 | + |
| 821 | + // If the resolution is too small don't display the warning |
| 822 | + if( this.embedPlayer.getPlayerHeight() < 199 ){ |
| 823 | + return false; |
| 824 | + } |
| 825 | + // See if we have we have ogg support |
| 826 | + var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/ogg' ); |
| 827 | + for ( var i = 0; i < supportingPlayers.length; i++ ) { |
| 828 | + |
| 829 | + if ( supportingPlayers[i].id == 'oggNative' |
| 830 | + && |
| 831 | + // xxx google chrome has broken oggNative playback: |
| 832 | + // http://code.google.com/p/chromium/issues/detail?id=56180 |
| 833 | + ! /chrome/.test(navigator.userAgent.toLowerCase() ) |
| 834 | + ){ |
| 835 | + return false; |
| 836 | + } |
| 837 | + } |
| 838 | + |
| 839 | + // Chrome's webM support is oky though: |
| 840 | + if( /chrome/.test(navigator.userAgent.toLowerCase() ) && |
| 841 | + mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/webm' ).length ){ |
| 842 | + return false; |
| 843 | + } |
| 844 | + |
| 845 | + |
| 846 | + // Check for h264 and or flash/flv source and playback support and don't show warning |
| 847 | + if( |
| 848 | + ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/h264' ).length |
| 849 | + && this.embedPlayer.mediaElement.getSources( 'video/h264' ).length ) |
| 850 | + || |
| 851 | + ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/x-flv' ).length |
| 852 | + && this.embedPlayer.mediaElement.getSources( 'video/x-flv' ).length ) |
| 853 | + ){ |
| 854 | + // No firefox link if a h.264 or flash/flv stream is present |
| 855 | + return false; |
| 856 | + } |
| 857 | + |
| 858 | + // Should issue the native warning |
| 859 | + return true; |
| 860 | + }, |
| 861 | + |
| 862 | + /** |
| 863 | + * Does a native warning check binding to the player on mouse over. |
| 864 | + * @param {string} preferenceId The preference Id |
| 865 | + * @param {object} warningMsg The jQuery object warning message to be displayed. |
| 866 | + * |
| 867 | + */ |
| 868 | + doWarningBindinng: function( preferenceId, warningMsg ) { |
| 869 | + mw.log( 'controlBuilder: doWarningBindinng: ' + preferenceId + ' wm: ' + warningMsg); |
| 870 | + // Set up local pointer to the embedPlayer |
| 871 | + var embedPlayer = this.embedPlayer; |
| 872 | + var _this = this; |
| 873 | + |
| 874 | + // make sure the player is large enough |
| 875 | + if( embedPlayer.getWidth() < 200 ){ |
| 876 | + return false; |
| 877 | + } |
| 878 | + |
| 879 | + $( embedPlayer ).hoverIntent({ |
| 880 | + 'timeout': 2000, |
| 881 | + 'over': function() { |
| 882 | + // don't do the overlay if already playing |
| 883 | + if( embedPlayer.isPlaying() ){ |
| 884 | + return ; |
| 885 | + } |
| 886 | + if ( $( '#warningOverlay_' + embedPlayer.id ).length == 0 ) { |
| 887 | + $( this ).append( |
| 888 | + $('<div />') |
| 889 | + .attr( { |
| 890 | + 'id': "warningOverlay_" + embedPlayer.id |
| 891 | + } ) |
| 892 | + .addClass( 'ui-state-highlight ui-corner-all' ) |
| 893 | + .css({ |
| 894 | + 'position' : 'absolute', |
| 895 | + 'display' : 'none', |
| 896 | + 'background' : '#FFF', |
| 897 | + 'color' : '#111', |
| 898 | + 'top' : '10px', |
| 899 | + 'left' : '10px', |
| 900 | + 'right' : '10px', |
| 901 | + 'padding' : '4px' |
| 902 | + }) |
| 903 | + .html( warningMsg ) |
| 904 | + ); |
| 905 | + |
| 906 | + $targetWarning = $( '#warningOverlay_' + embedPlayer.id ); |
| 907 | + |
| 908 | + $targetWarning.append( |
| 909 | + $('<br />') |
| 910 | + ); |
| 911 | + |
| 912 | + $targetWarning.append( |
| 913 | + $( '<input />' ) |
| 914 | + .attr({ |
| 915 | + 'id' : 'ffwarn_' + embedPlayer.id, |
| 916 | + 'type' : "checkbox", |
| 917 | + 'name' : 'ffwarn_' + embedPlayer.id |
| 918 | + }) |
| 919 | + .click( function() { |
| 920 | + mw.log("WarningBindinng:: set " + preferenceId + ' to hidewarning ' ); |
| 921 | + // Set up a cookie for 30 days: |
| 922 | + $j.cookie( preferenceId, 'hidewarning', { expires: 30 } ); |
| 923 | + // Set the current instance |
| 924 | + mw.setConfig( preferenceId, false ); |
| 925 | + $( '#warningOverlay_' + embedPlayer.id ).fadeOut( 'slow' ); |
| 926 | + // set the local prefrence to false |
| 927 | + _this.addWarningFlag = false; |
| 928 | + } ) |
| 929 | + ); |
| 930 | + $targetWarning.append( |
| 931 | + $('<label />') |
| 932 | + .text( gM( 'mwe-embedplayer-do_not_warn_again' ) ) |
| 933 | + .attr( 'for', 'ffwarn_' + embedPlayer.id ) |
| 934 | + ); |
| 935 | + } |
| 936 | + // Check the global config before showing the warning |
| 937 | + if ( mw.getConfig( preferenceId ) === true && $j.cookie( preferenceId ) != 'hidewarning' ){ |
| 938 | + mw.log("WarningBindinng:: show warning " + mw.getConfig( preferenceId ) + ' cookie: '+ $j.cookie( preferenceId ) + 'typeof:' + typeof $j.cookie( preferenceId )); |
| 939 | + $( '#warningOverlay_' + embedPlayer.id ).fadeIn( 'slow' ); |
| 940 | + }; |
| 941 | + }, |
| 942 | + 'out': function() { |
| 943 | + $( '#warningOverlay_' + embedPlayer.id ).fadeOut( 'slow' ); |
| 944 | + } |
| 945 | + }); |
| 946 | + }, |
| 947 | + |
| 948 | + /** |
| 949 | + * Binds the volume controls |
| 950 | + */ |
| 951 | + doVolumeBinding: function( ) { |
| 952 | + var embedPlayer = this.embedPlayer; |
| 953 | + var _this = this; |
| 954 | + $volumeSlider = embedPlayer.$interface.find( '.volume-slider' ); |
| 955 | + if( $volumeSlider.length == 0 ){ |
| 956 | + return false; |
| 957 | + } |
| 958 | + embedPlayer.$interface.find( '.volume_control' ).unbind().buttonHover().click( function() { |
| 959 | + mw.log( 'Volume control toggle' ); |
| 960 | + embedPlayer.toggleMute(); |
| 961 | + } ); |
| 962 | + |
| 963 | + // Add vertical volume display hover |
| 964 | + if ( this.volume_layout == 'vertical' ) { |
| 965 | + // Default volume binding: |
| 966 | + var hoverOverDelay = false; |
| 967 | + var $targetvol = embedPlayer.$interface.find( '.vol_container' ).hide(); |
| 968 | + embedPlayer.$interface.find( '.volume_control' ).hover( |
| 969 | + function() { |
| 970 | + $targetvol.addClass( 'vol_container_top' ); |
| 971 | + // Set to "below" if playing and embedType != native |
| 972 | + if ( embedPlayer && embedPlayer.isPlaying && embedPlayer.isPlaying() && !embedPlayer.supports['overlays'] ) { |
| 973 | + $targetvol.removeClass( 'vol_container_top' ).addClass( 'vol_container_below' ); |
| 974 | + } |
| 975 | + $targetvol.fadeIn( 'fast' ); |
| 976 | + hoverOverDelay = true; |
| 977 | + }, |
| 978 | + function() { |
| 979 | + hoverOverDelay = false; |
| 980 | + setTimeout( function() { |
| 981 | + if ( !hoverOverDelay ) { |
| 982 | + $targetvol.fadeOut( 'fast' ); |
| 983 | + } |
| 984 | + }, 500 ); |
| 985 | + } |
| 986 | + ); |
| 987 | + } |
| 988 | + |
| 989 | + // Setup volume slider: |
| 990 | + var sliderConf = { |
| 991 | + range: "min", |
| 992 | + value: 80, |
| 993 | + min: 0, |
| 994 | + max: 100, |
| 995 | + slide: function( event, ui ) { |
| 996 | + var percent = ui.value / 100; |
| 997 | + mw.log('PlayerControlBuilder::slide:update volume:' + percent); |
| 998 | + embedPlayer.setVolume( percent ); |
| 999 | + }, |
| 1000 | + change: function( event, ui ) { |
| 1001 | + var percent = ui.value / 100; |
| 1002 | + if ( percent == 0 ) { |
| 1003 | + embedPlayer.$interface.find( '.volume_control span' ).removeClass( 'ui-icon-volume-on' ).addClass( 'ui-icon-volume-off' ); |
| 1004 | + } else { |
| 1005 | + embedPlayer.$interface.find( '.volume_control span' ).removeClass( 'ui-icon-volume-off' ).addClass( 'ui-icon-volume-on' ); |
| 1006 | + } |
| 1007 | + mw.log('PlayerControlBuilder::change:update volume:' + percent); |
| 1008 | + embedPlayer.setVolume( percent ); |
| 1009 | + } |
| 1010 | + }; |
| 1011 | + |
| 1012 | + if ( this.volume_layout == 'vertical' ) { |
| 1013 | + sliderConf[ 'orientation' ] = "vertical"; |
| 1014 | + } |
| 1015 | + |
| 1016 | + $volumeSlider.slider( sliderConf ); |
| 1017 | + }, |
| 1018 | + |
| 1019 | + /** |
| 1020 | + * Get the options menu ul with li menu items |
| 1021 | + */ |
| 1022 | + getOptionsMenu: function( ) { |
| 1023 | + $optionsMenu = $( '<ul />' ); |
| 1024 | + for( var i in this.optionMenuItems ){ |
| 1025 | + |
| 1026 | + // Make sure its supported in the current controlBuilder config: |
| 1027 | + if( ! this.supportedMenuItems[ i ] ) { |
| 1028 | + continue; |
| 1029 | + } |
| 1030 | + |
| 1031 | + $optionsMenu.append( |
| 1032 | + this.optionMenuItems[i]( this ) |
| 1033 | + ); |
| 1034 | + } |
| 1035 | + return $optionsMenu; |
| 1036 | + }, |
| 1037 | + |
| 1038 | + /** |
| 1039 | + * Allow the controlBuilder to do interface actions onDone |
| 1040 | + */ |
| 1041 | + onClipDone: function(){ |
| 1042 | + // Related videos could be shown here |
| 1043 | + }, |
| 1044 | + |
| 1045 | + /** |
| 1046 | + * The ctrl builder updates the interface on seeking |
| 1047 | + */ |
| 1048 | + onSeek: function(){ |
| 1049 | + //mw.log( "controlBuilder:: onSeek" ); |
| 1050 | + // Update the interface: |
| 1051 | + this.setStatus( gM( 'mwe-embedplayer-seeking' ) ); |
| 1052 | + }, |
| 1053 | + |
| 1054 | + /** |
| 1055 | + * Updates the player status that displays short text msgs and the play clock |
| 1056 | + * @param {String} value Status string value to update |
| 1057 | + */ |
| 1058 | + setStatus: function( value ) { |
| 1059 | + // update status: |
| 1060 | + this.embedPlayer.$interface.find( '.time-disp' ).text( value ); |
| 1061 | + }, |
| 1062 | + |
| 1063 | + /** |
| 1064 | + * Option menu items |
| 1065 | + * |
| 1066 | + * @return |
| 1067 | + * 'li' a li line item with click action for that menu item |
| 1068 | + */ |
| 1069 | + optionMenuItems: { |
| 1070 | + // Player select menu item |
| 1071 | + 'playerSelect': function( ctrlObj ){ |
| 1072 | + return $j.getLineItem( |
| 1073 | + gM( 'mwe-embedplayer-choose_player' ), |
| 1074 | + 'gear', |
| 1075 | + function( ) { |
| 1076 | + ctrlObj.displayMenuOverlay( |
| 1077 | + ctrlObj.getPlayerSelect() |
| 1078 | + ); |
| 1079 | + } |
| 1080 | + ); |
| 1081 | + }, |
| 1082 | + |
| 1083 | + // Download the file menu |
| 1084 | + 'download': function( ctrlObj ) { |
| 1085 | + return $j.getLineItem( |
| 1086 | + gM( 'mwe-embedplayer-download' ), |
| 1087 | + 'disk', |
| 1088 | + function( ) { |
| 1089 | + ctrlObj.displayMenuOverlay( gM('mwe-loading_txt' ) ); |
| 1090 | + // Call show download with the target to be populated |
| 1091 | + ctrlObj.showDownload( |
| 1092 | + ctrlObj.embedPlayer.$interface.find( '.overlay-content' ) |
| 1093 | + ); |
| 1094 | + $( ctrlObj.embedPlayer ).trigger( 'showDownloadEvent' ); |
| 1095 | + } |
| 1096 | + ); |
| 1097 | + }, |
| 1098 | + |
| 1099 | + // Share the video menu |
| 1100 | + 'share': function( ctrlObj ) { |
| 1101 | + return $j.getLineItem( |
| 1102 | + gM( 'mwe-embedplayer-share' ), |
| 1103 | + 'mail-closed', |
| 1104 | + function( ) { |
| 1105 | + ctrlObj.displayMenuOverlay( |
| 1106 | + ctrlObj.getShare() |
| 1107 | + ); |
| 1108 | + $( ctrlObj.embedPlayer ).trigger( 'showShareEvent' ); |
| 1109 | + } |
| 1110 | + ); |
| 1111 | + }, |
| 1112 | + |
| 1113 | + 'aboutPlayerLibrary' : function( ctrlObj ){ |
| 1114 | + return $j.getLineItem( |
| 1115 | + gM( 'mwe-embedplayer-about-library' ), |
| 1116 | + 'info', |
| 1117 | + function( ) { |
| 1118 | + ctrlObj.displayMenuOverlay( |
| 1119 | + ctrlObj.aboutPlayerLibrary() |
| 1120 | + ); |
| 1121 | + $( ctrlObj.embedPlayer ).trigger( 'aboutPlayerLibrary' ); |
| 1122 | + } |
| 1123 | + ); |
| 1124 | + } |
| 1125 | + }, |
| 1126 | + |
| 1127 | + /** |
| 1128 | + * Close a menu overlay |
| 1129 | + */ |
| 1130 | + closeMenuOverlay: function(){ |
| 1131 | + var _this = this; |
| 1132 | + var embedPlayer = this.embedPlayer; |
| 1133 | + var $overlay = embedPlayer.$interface.find( '.overlay-win,.ui-widget-overlay,.ui-widget-shadow' ); |
| 1134 | + |
| 1135 | + this.keepControlBarOnScreen = false; |
| 1136 | + //mw.log(' closeMenuOverlay: ' + this.keepControlBarOnScreen); |
| 1137 | + |
| 1138 | + $overlay.fadeOut( "slow", function() { |
| 1139 | + $overlay.remove(); |
| 1140 | + } ); |
| 1141 | + // Show the big play button: |
| 1142 | + embedPlayer.$interface.find( '.play-btn-large' ).fadeIn( 'slow' ); |
| 1143 | + |
| 1144 | + |
| 1145 | + $(embedPlayer).trigger( 'closeMenuOverlay' ); |
| 1146 | + |
| 1147 | + return false; // onclick action return false |
| 1148 | + }, |
| 1149 | + |
| 1150 | + /** |
| 1151 | + * Generic function to display custom HTML overlay |
| 1152 | + * on video. |
| 1153 | + * |
| 1154 | + * @param {String} overlayContent content to be displayed |
| 1155 | + */ |
| 1156 | + displayMenuOverlay: function( overlayContent ) { |
| 1157 | + var _this = this; |
| 1158 | + var embedPlayer = this.embedPlayer; |
| 1159 | + mw.log( 'displayMenuOverlay::' ); |
| 1160 | + // set the overlay display flag to true: |
| 1161 | + this.keepControlBarOnScreen = true; |
| 1162 | + mw.log(" set keepControlBarOnScreen:: " + this.keepControlBarOnScreen); |
| 1163 | + |
| 1164 | + if ( !this.supportedComponets[ 'overlays' ] ) { |
| 1165 | + embedPlayer.stop(); |
| 1166 | + } |
| 1167 | + |
| 1168 | + |
| 1169 | + // Hide the big play button: |
| 1170 | + embedPlayer.$interface.find( '.play-btn-large' ).hide(); |
| 1171 | + |
| 1172 | + // Check if overlay window is already present: |
| 1173 | + if ( embedPlayer.$interface.find( '.overlay-win' ).length != 0 ) { |
| 1174 | + //Update the content |
| 1175 | + embedPlayer.$interface.find( '.overlay-content' ).html( |
| 1176 | + overlayContent |
| 1177 | + ); |
| 1178 | + return ; |
| 1179 | + } |
| 1180 | + |
| 1181 | + // Add an overlay |
| 1182 | + embedPlayer.$interface.append( |
| 1183 | + $('<div />') |
| 1184 | + .addClass( 'ui-widget-overlay' ) |
| 1185 | + .css( { |
| 1186 | + 'height' : '100%', |
| 1187 | + 'width' : '100%', |
| 1188 | + 'z-index' : 2 |
| 1189 | + } ) |
| 1190 | + ); |
| 1191 | + |
| 1192 | + // Setup the close button |
| 1193 | + $closeButton = $('<span />') |
| 1194 | + .addClass( 'ui-icon ui-icon-closethick' ) |
| 1195 | + .css({ |
| 1196 | + 'position': 'absolute', |
| 1197 | + 'cursor' : 'pointer', |
| 1198 | + 'top' : '2px', |
| 1199 | + 'right' : '2px' |
| 1200 | + }) |
| 1201 | + .click( function() { |
| 1202 | + _this.closeMenuOverlay(); |
| 1203 | + } ); |
| 1204 | + |
| 1205 | + var overlayMenuCss = { |
| 1206 | + 'height' : 200, |
| 1207 | + 'width' : 250, |
| 1208 | + 'position' : 'absolute', |
| 1209 | + 'left' : '10px', |
| 1210 | + 'top': '15px', |
| 1211 | + 'overflow' : 'auto', |
| 1212 | + 'padding' : '4px', |
| 1213 | + 'z-index' : 3 |
| 1214 | + }; |
| 1215 | + $overlayMenu = $('<div />') |
| 1216 | + .addClass( 'overlay-win ui-state-default ui-widget-header ui-corner-all' ) |
| 1217 | + .css( overlayMenuCss ) |
| 1218 | + .append( |
| 1219 | + $closeButton, |
| 1220 | + $('<div />') |
| 1221 | + .addClass( 'overlay-content' ) |
| 1222 | + .append( overlayContent ) |
| 1223 | + ); |
| 1224 | + |
| 1225 | + // Clone the overlay menu css: |
| 1226 | + var shadowCss = jQuery.extend( true, {}, overlayMenuCss ); |
| 1227 | + shadowCss['height' ] = 210; |
| 1228 | + shadowCss['width' ] = 260; |
| 1229 | + shadowCss[ 'z-index' ] = 2; |
| 1230 | + $overlayShadow = $( '<div />' ) |
| 1231 | + .addClass('ui-widget-shadow ui-corner-all') |
| 1232 | + .css( shadowCss ); |
| 1233 | + |
| 1234 | + // Append the overlay menu to the player interface |
| 1235 | + embedPlayer.$interface.prepend( |
| 1236 | + $overlayMenu, |
| 1237 | + $overlayShadow |
| 1238 | + ) |
| 1239 | + .find( '.overlay-win' ) |
| 1240 | + .fadeIn( "slow" ); |
| 1241 | + |
| 1242 | + // trigger menu overlay display |
| 1243 | + $(embedPlayer).trigger( 'displayMenuOverlay' ); |
| 1244 | + |
| 1245 | + return false; // onclick action return false |
| 1246 | + }, |
| 1247 | + aboutPlayerLibrary: function(){ |
| 1248 | + return $( '<div />' ) |
| 1249 | + .append( |
| 1250 | + $( '<h3 />' ) |
| 1251 | + .text( |
| 1252 | + gM('mwe-embedplayer-about-library') |
| 1253 | + ) |
| 1254 | + , |
| 1255 | + $( '<span />') |
| 1256 | + .append( |
| 1257 | + gM('mwe-embedplayer-about-library-desc', |
| 1258 | + $('<a />').attr({ |
| 1259 | + 'href' : mw.getConfig( 'EmbedPlayer.LibraryPage' ), |
| 1260 | + 'target' : '_new' |
| 1261 | + }) |
| 1262 | + ) |
| 1263 | + ) |
| 1264 | + ); |
| 1265 | + }, |
| 1266 | + /** |
| 1267 | + * Get the "share" interface |
| 1268 | + * |
| 1269 | + * TODO share should be enabled via <embed> tag usage to be compatible |
| 1270 | + * with sites social networking sites that allow <embed> tags but not js |
| 1271 | + * |
| 1272 | + * @param {Object} $target Target jQuery object to set share html |
| 1273 | + */ |
| 1274 | + getShare: function( ) { |
| 1275 | + var embedPlayer = this.embedPlayer; |
| 1276 | + var embed_code = embedPlayer.getEmbeddingHTML(); |
| 1277 | + var _this = this; |
| 1278 | + |
| 1279 | + var $shareInterface = $('<div />'); |
| 1280 | + |
| 1281 | + $shareList = $( '<ul />' ); |
| 1282 | + |
| 1283 | + $shareList |
| 1284 | + .append( |
| 1285 | + $('<li />') |
| 1286 | + .append( |
| 1287 | + $('<a />') |
| 1288 | + .attr('href', '#') |
| 1289 | + .addClass( 'active' ) |
| 1290 | + .text( |
| 1291 | + gM( 'mwe-embedplayer-embed_site_or_blog' ) |
| 1292 | + ) |
| 1293 | + ) |
| 1294 | + ); |
| 1295 | + |
| 1296 | + $shareInterface.append( |
| 1297 | + $( '<h2 />' ) |
| 1298 | + .text( gM( 'mwe-embedplayer-share_this_video' ) ) |
| 1299 | + .append( |
| 1300 | + $shareList |
| 1301 | + ) |
| 1302 | + ); |
| 1303 | + |
| 1304 | + $shareInterface.append( |
| 1305 | + |
| 1306 | + $( '<textarea />' ) |
| 1307 | + .attr( 'rows', 4 ) |
| 1308 | + .html( embed_code ) |
| 1309 | + .click( function() { |
| 1310 | + $( this ).select(); |
| 1311 | + }), |
| 1312 | + |
| 1313 | + $('<br />'), |
| 1314 | + $('<br />'), |
| 1315 | + |
| 1316 | + $('<button />') |
| 1317 | + .addClass( 'ui-state-default ui-corner-all copycode' ) |
| 1318 | + .text( gM( 'mwe-embedplayer-copy-code' ) ) |
| 1319 | + .click(function() { |
| 1320 | + $shareInterface.find( 'textarea' ).focus().select(); |
| 1321 | + // Copy the text if supported: |
| 1322 | + if ( document.selection ) { |
| 1323 | + CopiedTxt = document.selection.createRange(); |
| 1324 | + CopiedTxt.execCommand( "Copy" ); |
| 1325 | + } |
| 1326 | + } ) |
| 1327 | + |
| 1328 | + ); |
| 1329 | + return $shareInterface; |
| 1330 | + }, |
| 1331 | + |
| 1332 | + /** |
| 1333 | + * Shows the Player Select interface |
| 1334 | + * |
| 1335 | + * @param {Object} $target jQuery target for output |
| 1336 | + */ |
| 1337 | + getPlayerSelect: function( ) { |
| 1338 | + mw.log('ControlBuilder::getPlayerSelect: source:' + |
| 1339 | + this.embedPlayer.mediaElement.selectedSource.getSrc() + |
| 1340 | + ' player: ' + this.embedPlayer.selectedPlayer.id ); |
| 1341 | + |
| 1342 | + var embedPlayer = this.embedPlayer; |
| 1343 | + |
| 1344 | + var _this = this; |
| 1345 | + |
| 1346 | + $playerSelect = $('<div />') |
| 1347 | + .append( |
| 1348 | + $( '<h2 />' ) |
| 1349 | + .text( gM( 'mwe-embedplayer-choose_player' ) ) |
| 1350 | + ); |
| 1351 | + |
| 1352 | + $j.each( embedPlayer.mediaElement.getPlayableSources(), function( sourceId, source ) { |
| 1353 | + |
| 1354 | + var isPlayable = (typeof mw.EmbedTypes.getMediaPlayers().defaultPlayer( source.getMIMEType() ) == 'object' ); |
| 1355 | + var is_selected = ( source.getSrc() == embedPlayer.mediaElement.selectedSource.getSrc() ); |
| 1356 | + |
| 1357 | + $playerSelect.append( |
| 1358 | + $( '<h3 />' ) |
| 1359 | + .text( source.getTitle() ) |
| 1360 | + ); |
| 1361 | + |
| 1362 | + if ( isPlayable ) { |
| 1363 | + $playerList = $('<ul />'); |
| 1364 | + // output the player select code: |
| 1365 | + |
| 1366 | + var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() ); |
| 1367 | + |
| 1368 | + for ( var i = 0; i < supportingPlayers.length ; i++ ) { |
| 1369 | + |
| 1370 | + // Add link to select the player if not already selected ) |
| 1371 | + if( embedPlayer.selectedPlayer.id == supportingPlayers[i].id && is_selected ) { |
| 1372 | + // Active player ( no link ) |
| 1373 | + $playerLine = $( '<span />' ) |
| 1374 | + .append( |
| 1375 | + $('<a />') |
| 1376 | + .attr({ |
| 1377 | + 'href' : '#' |
| 1378 | + }) |
| 1379 | + .addClass( 'active') |
| 1380 | + .text( |
| 1381 | + supportingPlayers[i].getName() |
| 1382 | + ) |
| 1383 | + ); |
| 1384 | + //.addClass( 'ui-state-highlight ui-corner-all' ); removed by ran |
| 1385 | + } else { |
| 1386 | + // Non active player add link to select: |
| 1387 | + $playerLine = $( '<a />') |
| 1388 | + .attr({ |
| 1389 | + 'href' : '#', |
| 1390 | + 'rel' : 'sel_source', |
| 1391 | + 'id' : 'sc_' + sourceId + '_' + supportingPlayers[i].id |
| 1392 | + }) |
| 1393 | + .addClass( 'ui-corner-all') |
| 1394 | + .text( supportingPlayers[i].getName() ) |
| 1395 | + .click( function() { |
| 1396 | + var iparts = $( this ).attr( 'id' ).replace(/sc_/ , '' ).split( '_' ); |
| 1397 | + var sourceId = iparts[0]; |
| 1398 | + var player_id = iparts[1]; |
| 1399 | + mw.log( 'source id: ' + sourceId + ' player id: ' + player_id ); |
| 1400 | + |
| 1401 | + embedPlayer.controlBuilder.closeMenuOverlay(); |
| 1402 | + |
| 1403 | + // Close fullscreen if we are in fullscreen mode |
| 1404 | + if( _this.fullscreenMode ){ |
| 1405 | + _this.restoreWindowPlayer(); |
| 1406 | + } |
| 1407 | + |
| 1408 | + embedPlayer.mediaElement.selectSource( sourceId ); |
| 1409 | + var playableSources = embedPlayer.mediaElement.getPlayableSources(); |
| 1410 | + |
| 1411 | + mw.EmbedTypes.getMediaPlayers().setPlayerPreference( |
| 1412 | + player_id, |
| 1413 | + playableSources[ sourceId ].getMIMEType() |
| 1414 | + ); |
| 1415 | + |
| 1416 | + // Issue a stop |
| 1417 | + embedPlayer.stop(); |
| 1418 | + |
| 1419 | + // Don't follow the # link: |
| 1420 | + return false; |
| 1421 | + } ) |
| 1422 | + .hover( |
| 1423 | + function(){ |
| 1424 | + $( this ).addClass('active'); |
| 1425 | + }, |
| 1426 | + function(){ |
| 1427 | + $( this ).removeClass('active'); |
| 1428 | + } |
| 1429 | + ); |
| 1430 | + } |
| 1431 | + |
| 1432 | + // Add the player line to the player list: |
| 1433 | + $playerList.append( |
| 1434 | + $( '<li />' ).append( |
| 1435 | + $playerLine |
| 1436 | + ) |
| 1437 | + ); |
| 1438 | + } |
| 1439 | + |
| 1440 | + // Append the player list: |
| 1441 | + $playerSelect.append( $playerList ); |
| 1442 | + |
| 1443 | + } else { |
| 1444 | + // No player available: |
| 1445 | + $playerSelect.append( gM( 'mwe-embedplayer-no-player', source.getTitle() ) ); |
| 1446 | + } |
| 1447 | + } ); |
| 1448 | + |
| 1449 | + // Return the player select elements |
| 1450 | + return $playerSelect; |
| 1451 | + }, |
| 1452 | + |
| 1453 | + /** |
| 1454 | + * Loads sources and calls showDownloadWithSources |
| 1455 | + * @param {Object} $target jQuery target to output to |
| 1456 | + */ |
| 1457 | + showDownload: function( $target ) { |
| 1458 | + var _this = this; |
| 1459 | + var embedPlayer = this.embedPlayer; |
| 1460 | + |
| 1461 | + // Load additional text sources via apiTitleKey: |
| 1462 | + // TODO we should move this to timedText bindings |
| 1463 | + if( embedPlayer.apiTitleKey ) { |
| 1464 | + // Load text interface ( if not already loaded ) |
| 1465 | + mw.load( 'TimedText', function() { |
| 1466 | + embedPlayer.timedText.setupTextSources(function(){ |
| 1467 | + _this.showDownloadWithSources( $target ); |
| 1468 | + }); |
| 1469 | + }); |
| 1470 | + } else { |
| 1471 | + _this.showDownloadWithSources( $target ); |
| 1472 | + } |
| 1473 | + }, |
| 1474 | + |
| 1475 | + /** |
| 1476 | + * Shows the download interface with sources loaded |
| 1477 | + * @param {Object} $target jQuery target to output to |
| 1478 | + */ |
| 1479 | + showDownloadWithSources : function( $target ) { |
| 1480 | + var _this = this; |
| 1481 | + mw.log( 'showDownloadWithSources::' + $target.length ); |
| 1482 | + var embedPlayer = this.embedPlayer; |
| 1483 | + // Empty the target: |
| 1484 | + $target.empty(); |
| 1485 | + |
| 1486 | + var $mediaList = $( '<ul />' ); |
| 1487 | + var $textList = $( '<ul />' ); |
| 1488 | + $j.each( embedPlayer.mediaElement.getSources(), function( index, source ) { |
| 1489 | + if( source.getSrc() ) { |
| 1490 | + mw.log("showDownloadWithSources:: Add src: " + source.getTitle() ); |
| 1491 | + var $dl_line = $( '<li />').append( |
| 1492 | + $('<a />') |
| 1493 | + .attr( 'href', source.getSrc() ) |
| 1494 | + .text( source.getTitle() ) |
| 1495 | + ); |
| 1496 | + // Add link to correct "bucket" |
| 1497 | + |
| 1498 | + //Add link to time segment: |
| 1499 | + if ( source.getSrc().indexOf( '?t=' ) !== -1 ) { |
| 1500 | + $target.append( $dl_line ); |
| 1501 | + } else if ( this.getMIMEType().indexOf('text') === 0 ) { |
| 1502 | + // Add link to text list |
| 1503 | + $textList.append( $dl_line ); |
| 1504 | + } else { |
| 1505 | + // Add link to media list |
| 1506 | + $mediaList.append( $dl_line ); |
| 1507 | + } |
| 1508 | + |
| 1509 | + } |
| 1510 | + } ); |
| 1511 | + if( $mediaList.find('li').length != 0 ) { |
| 1512 | + $target.append( |
| 1513 | + $('<h2 />') |
| 1514 | + .text( gM( 'mwe-embedplayer-download_full' ) ), |
| 1515 | + $mediaList |
| 1516 | + ); |
| 1517 | + } |
| 1518 | + |
| 1519 | + if( $textList.find('li').length != 0 ) { |
| 1520 | + $target.append( |
| 1521 | + $('<h2 />') |
| 1522 | + .html( gM( 'mwe-embedplayer-download_text' ) ), |
| 1523 | + $textList |
| 1524 | + ); |
| 1525 | + } |
| 1526 | + }, |
| 1527 | + getSwichSourceMenu: function(){ |
| 1528 | + // for each source with "native playback" |
| 1529 | + $sourceMenu = $j('<ul />'); |
| 1530 | + $j.each( this.embedPlayer.mediaElement.getPlayableSources(), function( sourceId, source ) { |
| 1531 | + //var isSelected = ( source.getSrc() == this.embedPlayer.mediaElement.selectedSource.getSrc() ); |
| 1532 | + // Output the player select code: |
| 1533 | + var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() ); |
| 1534 | + for ( var i = 0; i < supportingPlayers.length ; i++ ) { |
| 1535 | + if( supportingPlayers[i].library == 'Native' ){ |
| 1536 | + $sourceMenu.append( |
| 1537 | + $.getLineItem( source.shorttitle, 'video', function(){ |
| 1538 | + mw.log("Selected source"); |
| 1539 | + }) |
| 1540 | + ) |
| 1541 | + } |
| 1542 | + } |
| 1543 | + }); |
| 1544 | + return $sourceMenu; |
| 1545 | + }, |
| 1546 | + |
| 1547 | + /** |
| 1548 | + * Get component |
| 1549 | + * |
| 1550 | + * @param {String} component_id Component key to grab html output |
| 1551 | + */ |
| 1552 | + getComponent: function( component_id ) { |
| 1553 | + if ( this.components[ component_id ] ) { |
| 1554 | + return this.components[ component_id ].o( this ); |
| 1555 | + } else { |
| 1556 | + return false; |
| 1557 | + } |
| 1558 | + }, |
| 1559 | + |
| 1560 | + /** |
| 1561 | + * Get a component height |
| 1562 | + * |
| 1563 | + * @param {String} component_id Component key to grab height |
| 1564 | + * @return height or false if not set |
| 1565 | + */ |
| 1566 | + getComponentHeight: function( component_id ) { |
| 1567 | + if ( this.components[ component_id ] |
| 1568 | + && this.components[ component_id ].h ) |
| 1569 | + { |
| 1570 | + return this.components[ component_id ].h; |
| 1571 | + } |
| 1572 | + return 0; |
| 1573 | + }, |
| 1574 | + |
| 1575 | + /** |
| 1576 | + * Get a component width |
| 1577 | + * @param {String} component_id Component key to grab width |
| 1578 | + * @return width or false if not set |
| 1579 | + */ |
| 1580 | + getComponentWidth: function( component_id ){ |
| 1581 | + if ( this.components[ component_id ] |
| 1582 | + && this.components[ component_id ].w ) |
| 1583 | + { |
| 1584 | + return this.components[ component_id ].w; |
| 1585 | + } |
| 1586 | + return 0; |
| 1587 | + }, |
| 1588 | + |
| 1589 | + /** |
| 1590 | + * Components Object |
| 1591 | + * Take in the embedPlayer and return some html for the given component. |
| 1592 | + * |
| 1593 | + * components can be overwritten by skin javascript |
| 1594 | + * |
| 1595 | + * Component JSON structure is as follows: |
| 1596 | + * 'o' Function to return a binded jQuery object ( accepts the ctrlObject as a parameter ) |
| 1597 | + * 'w' The width of the component |
| 1598 | + * 'h' The height of the component ( if height is undefined the height of the control bar is used ) |
| 1599 | + */ |
| 1600 | + components: { |
| 1601 | + /** |
| 1602 | + * The large play button in center of the player |
| 1603 | + */ |
| 1604 | + 'playButtonLarge': { |
| 1605 | + 'w' : 70, |
| 1606 | + 'h' : 53, |
| 1607 | + 'o' : function( ctrlObj ) { |
| 1608 | + return $( '<div/>' ) |
| 1609 | + .attr( { |
| 1610 | + 'title' : gM( 'mwe-embedplayer-play_clip' ), |
| 1611 | + 'class' : "play-btn-large" |
| 1612 | + } ) |
| 1613 | + // Get dynamic position for big play button |
| 1614 | + .css( { |
| 1615 | + 'left' : ( ( ctrlObj.embedPlayer.getPlayerWidth() - this.w ) / 2 ), |
| 1616 | + 'top' : ( ( ctrlObj.embedPlayer.getPlayerHeight() - this.h ) / 2 ) |
| 1617 | + } ) |
| 1618 | + // Add play hook: |
| 1619 | + .click( function() { |
| 1620 | + ctrlObj.embedPlayer.play(); |
| 1621 | + return false; // Event Stop Propagation |
| 1622 | + } ); |
| 1623 | + } |
| 1624 | + }, |
| 1625 | + |
| 1626 | + /** |
| 1627 | + * The Attribution button ( by default this is kaltura-icon |
| 1628 | + */ |
| 1629 | + 'attributionButton' : { |
| 1630 | + 'w' : 24, |
| 1631 | + 'o' : function( ctrlObj ){ |
| 1632 | + var buttonConfig = mw.getConfig( 'EmbedPlayer.AttributionButton'); |
| 1633 | + // Check for source ( by configuration convention this is a 16x16 image |
| 1634 | + if( buttonConfig.iconurl ){ |
| 1635 | + var $icon = $('<img />') |
| 1636 | + .css({'width': '16px', 'height': '16px', 'margin': '-8px 5px 0px 0px'}) |
| 1637 | + .attr('src', buttonConfig.iconurl ) |
| 1638 | + } else { |
| 1639 | + var $icon = $('<span />') |
| 1640 | + .addClass( 'ui-icon' ); |
| 1641 | + if( buttonConfig['class'] ){ |
| 1642 | + $icon.addClass( buttonConfig['class'] ); |
| 1643 | + } |
| 1644 | + } |
| 1645 | + |
| 1646 | + return $('<a />') |
| 1647 | + .attr({ |
| 1648 | + 'href': buttonConfig.href, |
| 1649 | + 'title' : buttonConfig.title, |
| 1650 | + 'target' : '_new' |
| 1651 | + }) |
| 1652 | + .addClass( 'attributionButton' ) |
| 1653 | + .append( |
| 1654 | + $( '<div />' ) |
| 1655 | + .addClass( 'rButton' ) |
| 1656 | + .css({ |
| 1657 | + 'top' : '9px', |
| 1658 | + 'left' : '2px' |
| 1659 | + }) |
| 1660 | + .append( |
| 1661 | + $icon |
| 1662 | + ) |
| 1663 | + ); |
| 1664 | + } |
| 1665 | + }, |
| 1666 | + |
| 1667 | + /** |
| 1668 | + * The options button, invokes display of the options menu |
| 1669 | + */ |
| 1670 | + 'options': { |
| 1671 | + 'w': 50, |
| 1672 | + 'o': function( ctrlObj ) { |
| 1673 | + return $( '<div />' ) |
| 1674 | + .attr( 'title', gM( 'mwe-embedplayer-player_options' ) ) |
| 1675 | + .addClass( 'ui-state-default ui-corner-all ui-icon_link rButton options-btn' ) |
| 1676 | + .append( |
| 1677 | + $('<span />') |
| 1678 | + .addClass( 'ui-icon ui-icon-wrench' ) |
| 1679 | + ) |
| 1680 | + .buttonHover() |
| 1681 | + // Options binding: |
| 1682 | + .menu( { |
| 1683 | + 'content' : ctrlObj.getOptionsMenu(), |
| 1684 | + 'zindex' : mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) + 1, |
| 1685 | + 'positionOpts': { |
| 1686 | + 'directionV' : 'up', |
| 1687 | + 'offsetY' : 30, |
| 1688 | + 'directionH' : 'left', |
| 1689 | + 'offsetX' : -28 |
| 1690 | + } |
| 1691 | + } ); |
| 1692 | + } |
| 1693 | + }, |
| 1694 | + |
| 1695 | + /** |
| 1696 | + * The fullscreen button for displaying the video fullscreen |
| 1697 | + */ |
| 1698 | + 'fullscreen': { |
| 1699 | + 'w': 24, |
| 1700 | + 'o': function( ctrlObj ) { |
| 1701 | + |
| 1702 | + // Setup "dobuleclick" fullscreen binding to embedPlayer |
| 1703 | + $( ctrlObj.embedPlayer ).unbind("dblclick").bind("dblclick", function(){ |
| 1704 | + ctrlObj.embedPlayer.fullscreen(); |
| 1705 | + }); |
| 1706 | + |
| 1707 | + return $( '<div />' ) |
| 1708 | + .attr( 'title', gM( 'mwe-embedplayer-player_fullscreen' ) ) |
| 1709 | + .addClass( "ui-state-default ui-corner-all ui-icon_link rButton fullscreen-btn" ) |
| 1710 | + .append( |
| 1711 | + $( '<span />' ) |
| 1712 | + .addClass( "ui-icon ui-icon-arrow-4-diag" ) |
| 1713 | + ) |
| 1714 | + // Fullscreen binding: |
| 1715 | + .buttonHover().click( function() { |
| 1716 | + ctrlObj.embedPlayer.fullscreen(); |
| 1717 | + } ); |
| 1718 | + } |
| 1719 | + }, |
| 1720 | + |
| 1721 | + |
| 1722 | + /** |
| 1723 | + * The pause / play button |
| 1724 | + */ |
| 1725 | + 'pause': { |
| 1726 | + 'w': 24, |
| 1727 | + 'o': function( ctrlObj ) { |
| 1728 | + return $( '<div />' ) |
| 1729 | + .attr( 'title', gM( 'mwe-embedplayer-play_clip' ) ) |
| 1730 | + .addClass ( "ui-state-default ui-corner-all ui-icon_link lButton play-btn" ) |
| 1731 | + .append( |
| 1732 | + $( '<span />' ) |
| 1733 | + .addClass( "ui-icon ui-icon-play" ) |
| 1734 | + ) |
| 1735 | + // Play / pause binding |
| 1736 | + .buttonHover() |
| 1737 | + .click( function() { |
| 1738 | + ctrlObj.embedPlayer.play(); |
| 1739 | + }); |
| 1740 | + } |
| 1741 | + }, |
| 1742 | + |
| 1743 | + |
| 1744 | + /** |
| 1745 | + * The volume control interface html |
| 1746 | + */ |
| 1747 | + 'volumeControl': { |
| 1748 | + 'w' : 36, |
| 1749 | + 'o' : function( ctrlObj ) { |
| 1750 | + mw.log( 'PlayerControlBuilder::Set up volume control for: ' + ctrlObj.embedPlayer.id ); |
| 1751 | + $volumeOut = $( '<span />' ); |
| 1752 | + if ( ctrlObj.volume_layout == 'horizontal' ) { |
| 1753 | + $volumeOut.append( |
| 1754 | + $( '<div />' ) |
| 1755 | + .addClass( "ui-slider ui-slider-horizontal rButton volume-slider" ) |
| 1756 | + ); |
| 1757 | + } |
| 1758 | + |
| 1759 | + // Add the volume control icon |
| 1760 | + $volumeOut.append( |
| 1761 | + $('<div />') |
| 1762 | + .attr( 'title', gM( 'mwe-embedplayer-volume_control' ) ) |
| 1763 | + .addClass( "ui-state-default ui-corner-all ui-icon_link rButton volume_control" ) |
| 1764 | + .append( |
| 1765 | + $( '<span />' ) |
| 1766 | + .addClass( "ui-icon ui-icon-volume-on" ) |
| 1767 | + ) |
| 1768 | + ); |
| 1769 | + if ( ctrlObj.volume_layout == 'vertical' ) { |
| 1770 | + $volumeOut.find('.volume_control').append( |
| 1771 | + $( '<div />' ) |
| 1772 | + .css( { |
| 1773 | + 'position' : 'absolute', |
| 1774 | + 'left' : '0px' |
| 1775 | + }) |
| 1776 | + .hide() |
| 1777 | + .addClass( "vol_container ui-corner-all" ) |
| 1778 | + .append( |
| 1779 | + $( '<div />' ) |
| 1780 | + .addClass ( "volume-slider" ) |
| 1781 | + ) |
| 1782 | + ); |
| 1783 | + } |
| 1784 | + //Return the inner html |
| 1785 | + return $volumeOut.html(); |
| 1786 | + } |
| 1787 | + }, |
| 1788 | + |
| 1789 | + 'sourceSwitch' : { |
| 1790 | + 'w' : 50, |
| 1791 | + 'o' : function( ctrlObj ){ |
| 1792 | + // Stream switching widget ( display the current selected stream text ) |
| 1793 | + return $( '<div />' ) |
| 1794 | + .addClass('ui-widget source-switch') |
| 1795 | + .append( |
| 1796 | + ctrlObj.embedPlayer.mediaElement.selectedSource.shorttitle |
| 1797 | + ).menu( { |
| 1798 | + 'content' : ctrlObj.getSwichSourceMenu(), |
| 1799 | + 'zindex' : mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) + 2, |
| 1800 | + 'width' : 75, |
| 1801 | + 'positionOpts' : { |
| 1802 | + 'posY' : 'top', |
| 1803 | + 'directionV' : 'up', |
| 1804 | + 'offsetY' : 23 |
| 1805 | + }, |
| 1806 | + 'createMenuCallback' : function(){ |
| 1807 | + ctrlObj.showControlBar( true ); |
| 1808 | + }, |
| 1809 | + 'closeMenuCallback' : function(){ |
| 1810 | + ctrlObj.hideControlBar( true ); |
| 1811 | + } |
| 1812 | + } ); |
| 1813 | + } |
| 1814 | + }, |
| 1815 | + |
| 1816 | + /* |
| 1817 | + * The time display area |
| 1818 | + */ |
| 1819 | + 'timeDisplay': { |
| 1820 | + 'w' : 50, |
| 1821 | + 'o' : function( ctrlObj ) { |
| 1822 | + return $( '<div />' ) |
| 1823 | + .addClass( "ui-widget time-disp" ) |
| 1824 | + .append( |
| 1825 | + ctrlObj.embedPlayer.getTimeRange() |
| 1826 | + ); |
| 1827 | + } |
| 1828 | + }, |
| 1829 | + |
| 1830 | + /** |
| 1831 | + * The playhead component |
| 1832 | + */ |
| 1833 | + 'playHead': { |
| 1834 | + 'w':0, // special case (takes up remaining space) |
| 1835 | + 'o':function( ctrlObj ) { |
| 1836 | + |
| 1837 | + var sliderConfig = { |
| 1838 | + range: "min", |
| 1839 | + value: 0, |
| 1840 | + min: 0, |
| 1841 | + max: 1000, |
| 1842 | + start: function( event, ui ) { |
| 1843 | + var id = ( embedPlayer.pc != null ) ? embedPlayer.pc.pp.id:embedPlayer.id; |
| 1844 | + embedPlayer.userSlide = true; |
| 1845 | + $( id + ' .play-btn-large' ).fadeOut( 'fast' ); |
| 1846 | + // If playlist always start at 0 |
| 1847 | + embedPlayer.start_time_sec = ( embedPlayer.instanceOf == 'mvPlayList' ) ? 0: |
| 1848 | + mw.npt2seconds( embedPlayer.getTimeRange().split( '/' )[0] ); |
| 1849 | + }, |
| 1850 | + slide: function( event, ui ) { |
| 1851 | + var perc = ui.value / 1000; |
| 1852 | + embedPlayer.jump_time = mw.seconds2npt( parseFloat( parseFloat( embedPlayer.getDuration() ) * perc ) + embedPlayer.start_time_sec ); |
| 1853 | + // mw.log('perc:' + perc + ' * ' + embedPlayer.getDuration() + ' jt:'+ this.jump_time); |
| 1854 | + if ( _this.longTimeDisp ) { |
| 1855 | + ctrlObj.setStatus( gM( 'mwe-embedplayer-seek_to', embedPlayer.jump_time ) ); |
| 1856 | + } else { |
| 1857 | + ctrlObj.setStatus( embedPlayer.jump_time ); |
| 1858 | + } |
| 1859 | + // Update the thumbnail / frame |
| 1860 | + if ( embedPlayer.isPlaying == false ) { |
| 1861 | + embedPlayer.updateThumbPerc( perc ); |
| 1862 | + } |
| 1863 | + }, |
| 1864 | + change:function( event, ui ) { |
| 1865 | + // Only run the onChange event if done by a user slide |
| 1866 | + // (otherwise it runs times it should not) |
| 1867 | + if ( embedPlayer.userSlide ) { |
| 1868 | + embedPlayer.userSlide = false; |
| 1869 | + embedPlayer.seeking = true; |
| 1870 | + |
| 1871 | + var perc = ui.value / 1000; |
| 1872 | + // Set seek time (in case we have to do a url seek) |
| 1873 | + embedPlayer.seek_time_sec = mw.npt2seconds( embedPlayer.jump_time, true ); |
| 1874 | + mw.log( 'do jump to: ' + embedPlayer.jump_time + ' perc:' + perc + ' sts:' + embedPlayer.seek_time_sec ); |
| 1875 | + ctrlObj.setStatus( gM( 'mwe-embedplayer-seeking' ) ); |
| 1876 | + embedPlayer.doSeek( perc ); |
| 1877 | + } |
| 1878 | + } |
| 1879 | + }; |
| 1880 | + |
| 1881 | + // Set up the disable playhead function: |
| 1882 | + // TODO this will move into the disableSeekBar binding in the new theme framework |
| 1883 | + ctrlObj.disableSeekBar = function(){ |
| 1884 | + ctrlObj.embedPlayer.$interface.find( ".play_head" ).slider( "option", "disabled", true ); |
| 1885 | + } |
| 1886 | + ctrlObj.enableSeekBar = function(){ |
| 1887 | + ctrlObj.embedPlayer.$interface.find( ".play_head" ).slider( "option", "disabled", false); |
| 1888 | + } |
| 1889 | + |
| 1890 | + |
| 1891 | + var embedPlayer = ctrlObj.embedPlayer; |
| 1892 | + var _this = this; |
| 1893 | + var $playHead = $( '<div />' ) |
| 1894 | + .addClass ( "play_head" ) |
| 1895 | + .css({ |
| 1896 | + "position" : 'absolute', |
| 1897 | + "left" : '33px', |
| 1898 | + "right" : ( ( embedPlayer.getPlayerWidth() - ctrlObj.available_width ) - 10) + 'px' |
| 1899 | + }) |
| 1900 | + // Playhead binding |
| 1901 | + .slider( sliderConfig ); |
| 1902 | + |
| 1903 | + // Up the z-index of the default status indicator: |
| 1904 | + $playHead.find( '.ui-slider-handle' ).css( 'z-index', 4 ); |
| 1905 | + $playHead.find( '.ui-slider-range' ).addClass( 'ui-corner-all' ).css( 'z-index', 2 ); |
| 1906 | + |
| 1907 | + // Add buffer html: |
| 1908 | + $playHead.append( |
| 1909 | + $('<div />') |
| 1910 | + .addClass( "ui-slider-range ui-slider-range-min ui-widget-header") |
| 1911 | + .addClass( "ui-state-highlight ui-corner-all mw_buffer") |
| 1912 | + ); |
| 1913 | + |
| 1914 | + return $playHead; |
| 1915 | + } |
| 1916 | + } |
| 1917 | + } |
| 1918 | +}; |
| 1919 | + |
| 1920 | + |
| 1921 | +} )( window.mediaWiki, window.jQuery ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 1922 | + text/plain |
Added: svn:eol-style |
2 | 1923 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.style.EmbedPlayer.css |
— | — | @@ -0,0 +1,35 @@ |
| 2 | +.player_select_list { |
| 3 | + color:white; |
| 4 | + font-size:10pt; |
| 5 | +/* display:none;*/ |
| 6 | +} |
| 7 | +.player_select_list a:visited { |
| 8 | + color:white; |
| 9 | +} |
| 10 | +.mv_playhead { |
| 11 | + position:absolute; |
| 12 | + top:0; |
| 13 | + left:0; |
| 14 | + width:17px; |
| 15 | + height:21px; |
| 16 | + /*http://art.gnome.org/themes/gtk2*/ |
| 17 | +} |
| 18 | +.mv_status { |
| 19 | + font-family:"Times New Roman", Times, serif; |
| 20 | + font-size:14px; |
| 21 | + float:left; |
| 22 | +} |
| 23 | +.set_ogg_player_pref{ |
| 24 | + text-align:left; |
| 25 | +} |
| 26 | + |
| 27 | +.large_play_button { |
| 28 | + display:block; |
| 29 | + width: 130px; |
| 30 | + height: 96px; |
| 31 | + margin: auto; |
| 32 | +/* margin: -202px 0 0 154px;*/ |
| 33 | + position: absolute; |
| 34 | + z-index: 3; |
| 35 | + cursor: pointer; |
| 36 | +} |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.style.EmbedPlayer.css |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 37 | + text/plain |
Added: svn:eol-style |
2 | 38 | + native |
Added: svn:executable |
3 | 39 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/images/player_big_play_button.png |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/images/player_big_play_button.png |
___________________________________________________________________ |
Added: svn:mime-type |
4 | 40 | + application/octet-stream |
Added: svn:executable |
5 | 41 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/mw.style.PlayerSkinMvpcf.css |
— | — | @@ -0,0 +1,169 @@ |
| 2 | +/** |
| 3 | + * reference player skin |
| 4 | + */ |
| 5 | + |
| 6 | + |
| 7 | +/*.ui-state-default */ |
| 8 | +.mv-player a:link {color: #2060c1; text-decoration: underline;} |
| 9 | +.mv-player a:visited {color: #2060c1; text-decoration: underline;} |
| 10 | +/*a:visited {color: #75a5e4; text-decoration: underline;}*/ /*Not sure if you want this*/ |
| 11 | +.mv-player a:hover {color: #75a5e4; text-decoration: underline;} |
| 12 | +.mv-player img, .mv-player img a, .mv-player img a:hover {border: 0;} |
| 13 | + |
| 14 | + |
| 15 | +.mv-player .video { |
| 16 | + display: block; |
| 17 | + position: relative; |
| 18 | + font-size: 1px; |
| 19 | + height: 305px; |
| 20 | +} |
| 21 | +.mv-player .control-bar { |
| 22 | + height: 29px; |
| 23 | + z-index: 2; |
| 24 | +} |
| 25 | +.mv-player .controlInnerSmall { |
| 26 | +/* width: 430px;*/ |
| 27 | + height: 29px; |
| 28 | + float: left; |
| 29 | + display: inline; |
| 30 | +} |
| 31 | + |
| 32 | +.mv-player .lButton { |
| 33 | + cursor:pointer; |
| 34 | + float:left; |
| 35 | + list-style:none outside none; |
| 36 | + margin:2px; |
| 37 | + padding:4px 0; |
| 38 | + width: 24px; |
| 39 | + height:16px; |
| 40 | + position:relative; |
| 41 | +} |
| 42 | +.mv-player .rButton { |
| 43 | + cursor:pointer; |
| 44 | + float:right; |
| 45 | + list-style:none outside none; |
| 46 | + margin:2px; |
| 47 | + padding:4px 0; |
| 48 | + width: 23px; |
| 49 | + height:16px; |
| 50 | + position:relative; |
| 51 | +} |
| 52 | + |
| 53 | +.mv-player .volume_icon { |
| 54 | + float: right; |
| 55 | + display: inline; |
| 56 | + width: 22px; |
| 57 | + height: 29px; |
| 58 | + padding: 0 0 0 0; |
| 59 | + |
| 60 | +} |
| 61 | + |
| 62 | +.mv-player .vol_container{ |
| 63 | + z-index:99; |
| 64 | + width:23px; |
| 65 | + height:75px; |
| 66 | + width:23px; |
| 67 | + background: #CCC; |
| 68 | +} |
| 69 | +.mv-player .vol_container_below{ |
| 70 | + top:30px; |
| 71 | +} |
| 72 | +.mv-player .vol_container_top{ |
| 73 | + top:-77px; |
| 74 | +} |
| 75 | +.mv-player .vol_container .volume-slider{ |
| 76 | + margin-top:5px; |
| 77 | + height:65px; |
| 78 | + width:10px; |
| 79 | + margin-left: auto ; |
| 80 | + margin-right: auto ; |
| 81 | +} |
| 82 | +.mv-player .vol_container .ui-slider-handle{ |
| 83 | + cursor : pointer; |
| 84 | + width:10px; |
| 85 | + height:10px; |
| 86 | + position:absolute; |
| 87 | + left:-1px; |
| 88 | +} |
| 89 | + |
| 90 | +.mv-player .time-disp { |
| 91 | + line-height: 32px; |
| 92 | + height: 29px; |
| 93 | + overflow: visible; |
| 94 | + font-size: 10.2px; |
| 95 | + width: 85px; |
| 96 | + float: right; |
| 97 | + display: inline; |
| 98 | + border:none; |
| 99 | +} |
| 100 | + |
| 101 | +.mv-player .play_head{ |
| 102 | + float: left; |
| 103 | + display: inline; |
| 104 | + height: 10px; |
| 105 | + margin-left:8px; |
| 106 | + margin-top:10px; |
| 107 | + position:relative; |
| 108 | +} |
| 109 | + |
| 110 | +.mv-player .play_head .ui-slider-handle{ |
| 111 | + width:10px; |
| 112 | + height:15px; |
| 113 | + margin-left:-5px; |
| 114 | + margin-top: -0px; |
| 115 | + z-index: 2; |
| 116 | +} |
| 117 | + |
| 118 | +.mv-player .inOutSlider .ui-slider-handle{ |
| 119 | + width:8px; |
| 120 | + cusror: move; |
| 121 | +} |
| 122 | + |
| 123 | +.mv-player .overlay-win textarea { |
| 124 | + background:none repeat scroll 0 0 transparent; |
| 125 | + border: 2px solid #333; |
| 126 | + color: #fff; |
| 127 | + font: 11px arial,sans-serif; |
| 128 | + height:15px; |
| 129 | + overflow:hidden; |
| 130 | + padding-left:2px; |
| 131 | + width:97%; |
| 132 | +} |
| 133 | + |
| 134 | +.mv-player .overlay-win div.ui-state-highlight { |
| 135 | + background:none repeat scroll 0 0 transparent; |
| 136 | + border-color:#554926; |
| 137 | + color:#FFE96E; |
| 138 | + float:left; |
| 139 | + padding:2px 5px; |
| 140 | +} |
| 141 | + |
| 142 | +.mv-player .videoOptionsComplete div.ui-state-highlight a { |
| 143 | + color:#eee; |
| 144 | + font-weight:bold; |
| 145 | +} |
| 146 | + |
| 147 | +.mv-player .overlay-win h2{ |
| 148 | + font-size: 115%; |
| 149 | +} |
| 150 | + |
| 151 | +.mv-player .overlay-win{ |
| 152 | + font-family : arial,sans-serif; |
| 153 | + font-size : 85%; |
| 154 | +} |
| 155 | +.mv-player .overlay-win a{ |
| 156 | + text-decoration: none; |
| 157 | +} |
| 158 | + |
| 159 | +.mv-player .overlay-win ul{ |
| 160 | + padding-left: 15px; |
| 161 | +} |
| 162 | + |
| 163 | +.mv-player a:hover {} |
| 164 | + |
| 165 | +.mv-player .overlay-win ul li span { font-weight:bold; color:#fff;} |
| 166 | + |
| 167 | +.mv-player .overlay-win h2 { font-size:16px;} |
| 168 | +.mv-player .overlay-win h3 { font-size:14px;} |
| 169 | + |
| 170 | +.active { font-size: 12px; } |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/mw.style.PlayerSkinMvpcf.css |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 171 | + text/plain |
Added: svn:eol-style |
2 | 172 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/mw.PlayerSkinMvpcf.js |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +/* |
| 3 | +mvpcf skin config |
| 4 | +*/ |
| 5 | +mw.PlayerSkinMvpcf = { |
| 6 | + playerClass : 'mv-player' |
| 7 | +}; |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/mw.PlayerSkinMvpcf.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 8 | + text/plain |
Added: svn:eol-style |
2 | 9 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerJava.js |
— | — | @@ -0,0 +1,248 @@ |
| 2 | +/** |
| 3 | +* List of domains and hosted location of cortado. Lets clients avoid the security warning for cross domain cortado |
| 4 | +*/ |
| 5 | +( function( mw, $ ) { |
| 6 | + |
| 7 | +window.cortadoDomainLocations = { |
| 8 | + 'upload.wikimedia.org' : 'http://upload.wikimedia.org/jars/cortado.jar' |
| 9 | +}; |
| 10 | + |
| 11 | +// Set the default location for CortadoApplet |
| 12 | +mw.setDefaultConfig( 'relativeCortadoAppletPath', |
| 13 | + mw.getMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/cortado/cortado-ovtk-stripped-0.6.0.jar' |
| 14 | +); |
| 15 | + |
| 16 | +mw.EmbedPlayerJava = { |
| 17 | + |
| 18 | + // Instance name: |
| 19 | + instanceOf: 'Java', |
| 20 | + |
| 21 | + // Supported feature set of the cortado applet: |
| 22 | + supports: { |
| 23 | + 'playHead' : true, |
| 24 | + 'pause' : true, |
| 25 | + 'stop' : true, |
| 26 | + 'fullscreen' : false, |
| 27 | + 'timeDisplay' : true, |
| 28 | + 'volumeControl' : false |
| 29 | + }, |
| 30 | + |
| 31 | + /** |
| 32 | + * Output the the embed html |
| 33 | + */ |
| 34 | + doEmbedHTML: function () { |
| 35 | + var _this = this; |
| 36 | + mw.log( "java play url:" + this.getSrc( this.seek_time_sec ) ); |
| 37 | + |
| 38 | + mw.log('Applet location: ' + this.getAppletLocation() ); |
| 39 | + mw.log('Play media: ' + this.getSrc() ); |
| 40 | + |
| 41 | + // load directly in the page.. |
| 42 | + // ( media must be on the same server or applet must be signed ) |
| 43 | + var appletCode = '' + |
| 44 | + '<applet id="' + this.pid + '" code="com.fluendo.player.Cortado.class" ' + |
| 45 | + 'archive="' + this.getAppletLocation() + '" width="' + parseInt( this.getWidth() ) + '" ' + |
| 46 | + 'height="' + parseInt( this.getHeight() ) + '"> ' + "\n" + |
| 47 | + '<param name="url" value="' + this.getSrc() + '" /> ' + "\n" + |
| 48 | + '<param name="local" value="false"/>' + "\n" + |
| 49 | + '<param name="keepaspect" value="true" />' + "\n" + |
| 50 | + '<param name="video" value="true" />' + "\n" + |
| 51 | + '<param name="showStatus" value="hide" />' + "\n" + |
| 52 | + '<param name="audio" value="true" />' + "\n" + |
| 53 | + '<param name="seekable" value="true" />' + "\n"; |
| 54 | + |
| 55 | + // Add the duration attribute if set: |
| 56 | + if( this.getDuration() ){ |
| 57 | + appletCode += '<param name="duration" value="' + parseFloat( this.getDuration() ) + '" />' + "\n"; |
| 58 | + } |
| 59 | + |
| 60 | + appletCode += '<param name="BufferSize" value="4096" />' + |
| 61 | + '<param name="BufferHigh" value="25">' + |
| 62 | + '<param name="BufferLow" value="5">' + |
| 63 | + '</applet>'; |
| 64 | + |
| 65 | + $( this ).html( appletCode ); |
| 66 | + |
| 67 | + // Wrap it in an iframe to avoid hanging the event thread in FF 2/3 and similar |
| 68 | + // NOTE: This breaks reference to the applet so disabled for now: |
| 69 | + /*if ( $j.browser.mozilla ) { |
| 70 | + var iframe = document.createElement( 'iframe' ); |
| 71 | + iframe.setAttribute( 'width', this.getWidth() ); |
| 72 | + iframe.setAttribute( 'height', this.getHeight() ); |
| 73 | + iframe.setAttribute( 'scrolling', 'no' ); |
| 74 | + iframe.setAttribute( 'frameborder', 0 ); |
| 75 | + iframe.setAttribute( 'marginWidth', 0 ); |
| 76 | + iframe.setAttribute( 'marginHeight', 0 ); |
| 77 | + iframe.setAttribute( 'id', 'cframe_' + this.id ) |
| 78 | + |
| 79 | + // Append the iframe to the embed object: |
| 80 | + $( this ).html( iframe ); |
| 81 | + |
| 82 | + // Write out the iframe content: |
| 83 | + var newDoc = iframe.contentDocument; |
| 84 | + newDoc.open(); |
| 85 | + newDoc.write( '<html><body>' + appletCode + '</body></html>' ); |
| 86 | + // spurious error in some versions of FF, no workaround known |
| 87 | + newDoc.close(); |
| 88 | + } else { |
| 89 | + $( this ).html( appletCode ); |
| 90 | + //} |
| 91 | + */ |
| 92 | + |
| 93 | + // Start the monitor: |
| 94 | + _this.monitor(); |
| 95 | + }, |
| 96 | + |
| 97 | + /** |
| 98 | + * Get the applet location |
| 99 | + */ |
| 100 | + getAppletLocation: function() { |
| 101 | + var mediaSrc = this.getSrc(); |
| 102 | + var appletLoc = false; |
| 103 | + if ( |
| 104 | + !mw.isLocalDomain( mediaSrc ) |
| 105 | + || |
| 106 | + !mw.isLocalDomain( mw.getMwEmbedPath() |
| 107 | + || |
| 108 | + mw.getConfig( 'relativeCortadoAppletPath' ) === false ) |
| 109 | + ){ |
| 110 | + if ( window.cortadoDomainLocations[ mw.parseUri( mediaSrc ).host ] ) { |
| 111 | + appletLoc = window.cortadoDomainLocations[ mw.parseUri( mediaSrc ).host ]; |
| 112 | + } else { |
| 113 | + appletLoc = 'http://theora.org/cortado.jar'; |
| 114 | + } |
| 115 | + } else { |
| 116 | + // Get the local relative cortado applet location: |
| 117 | + appletLoc = mw.getConfig( 'relativeCortadoAppletPath' ); |
| 118 | + } |
| 119 | + return appletLoc; |
| 120 | + }, |
| 121 | + |
| 122 | + /** |
| 123 | + * Get the embed player time |
| 124 | + */ |
| 125 | + getPlayerElementTime: function() { |
| 126 | + this.getPlayerElement(); |
| 127 | + var currentTime = 0; |
| 128 | + if ( this.playerElement ) { |
| 129 | + try { |
| 130 | + // java reads ogg media time.. so no need to add the start or seek offset: |
| 131 | + //mw.log(' ct: ' + this.playerElement.getPlayPosition() + ' ' + this.supportsURLTimeEncoding()); |
| 132 | + |
| 133 | + currentTime = this.playerElement.currentTime; |
| 134 | + // ( java cortado has -1 time ~sometimes~ ) |
| 135 | + /*if ( this.currentTime < 0 ) { |
| 136 | + mw.log( 'pp:' + this.currentTime ); |
| 137 | + // Probably reached clip ( should fire ondone event instead ) |
| 138 | + this.onClipDone(); |
| 139 | + }*/ |
| 140 | + } catch ( e ) { |
| 141 | + mw.log( 'could not get time from jPlayer: ' + e ); |
| 142 | + } |
| 143 | + }else{ |
| 144 | + mw.log(" could not find playerElement " ); |
| 145 | + } |
| 146 | + return currentTime; |
| 147 | + }, |
| 148 | + |
| 149 | + /** |
| 150 | + * Seek in the ogg stream |
| 151 | + * ( Cortado seek does not seem to work very well ) |
| 152 | + * @param {Float} percentage Percentage to seek into the stream |
| 153 | + */ |
| 154 | + doSeek: function( percentage ) { |
| 155 | + mw.log( 'java:seek:p: ' + percentage + ' : ' + this.supportsURLTimeEncoding() + ' dur: ' + this.getDuration() + ' sts:' + this.seek_time_sec ); |
| 156 | + this.getPlayerElement(); |
| 157 | + |
| 158 | + if ( this.supportsURLTimeEncoding() ) { |
| 159 | + this.parent_doSeek( percentage ); |
| 160 | + } else if ( this.playerElement ) { |
| 161 | + // do a (generally broken) local seek: |
| 162 | + mw.log( "Cortado seek is not very accurate :: doSeek::" + ( percentage * parseFloat( this.getDuration() ) ) ); |
| 163 | + this.playerElement.currentTime = ( percentage * parseFloat( this.getDuration() ) ); |
| 164 | + } else { |
| 165 | + this.doPlayThenSeek( percentage ); |
| 166 | + } |
| 167 | + |
| 168 | + // Run the onSeeking interface update |
| 169 | + this.controlBuilder.onSeek(); |
| 170 | + }, |
| 171 | + |
| 172 | + /** |
| 173 | + * Issue a play request then seek to a percentage point in the stream |
| 174 | + * @param {Float} percentage Percentage to seek into the stream |
| 175 | + */ |
| 176 | + doPlayThenSeek: function( percentage ) { |
| 177 | + mw.log( 'doPlayThenSeek' ); |
| 178 | + var _this = this; |
| 179 | + this.play(); |
| 180 | + var rfsCount = 0; |
| 181 | + var readyForSeek = function() { |
| 182 | + _this.getPlayerElement(); |
| 183 | + // if we have .jre ~in theory~ we can seek (but probably not) |
| 184 | + if ( _this.playerElement ) { |
| 185 | + _this.doSeek( perc ); |
| 186 | + } else { |
| 187 | + // try to get player for 10 seconds: |
| 188 | + if ( rfsCount < 200 ) { |
| 189 | + setTimeout( readyForSeek, 50 ); |
| 190 | + rfsCount++; |
| 191 | + } else { |
| 192 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 193 | + } |
| 194 | + } |
| 195 | + }; |
| 196 | + readyForSeek(); |
| 197 | + }, |
| 198 | + |
| 199 | + /** |
| 200 | + * Update the playerElement instance with a pointer to the embed object |
| 201 | + */ |
| 202 | + getPlayerElement: function() { |
| 203 | + if( !$( '#' + this.pid ).length ) { |
| 204 | + return false; |
| 205 | + } |
| 206 | + //mw.log( 'getPlayerElement::' + this.pid ); |
| 207 | + this.playerElement = $( '#' + this.pid ).get( 0 ); |
| 208 | + //this.playerElement = document.applets[ 0 ]; |
| 209 | + // NOTE we are currently not using the iframe embed method: |
| 210 | + //if ( $j.browser.mozilla ) { |
| 211 | + // this.playerElement = $('#cframe_' + this.id).contents().find( '#' + this.pid ); |
| 212 | + //} else { |
| 213 | + // this.playerElement = $( '#' + this.pid ).get( 0 ); |
| 214 | + //} |
| 215 | + return this.playerElement; |
| 216 | + }, |
| 217 | + |
| 218 | + /** |
| 219 | + * Issue the doPlay request to the playerElement |
| 220 | + * calls parent_play to update interface |
| 221 | + */ |
| 222 | + play: function() { |
| 223 | + this.getPlayerElement(); |
| 224 | + this.parent_play(); |
| 225 | + if ( this.playerElement ) { |
| 226 | + try{ |
| 227 | + this.playerElement.play(); |
| 228 | + }catch( e ){ |
| 229 | + mw.log("EmbedPlayerJava::Could not issue play request"); |
| 230 | + } |
| 231 | + } |
| 232 | + }, |
| 233 | + |
| 234 | + /** |
| 235 | + * Pause playback |
| 236 | + * calls parent_pause to update interface |
| 237 | + */ |
| 238 | + pause: function() { |
| 239 | + this.getPlayerElement(); |
| 240 | + // Update the interface |
| 241 | + this.parent_pause(); |
| 242 | + // Call the pause function if it exists: |
| 243 | + if ( this.playerElement ) { |
| 244 | + this.playerElement.pause(); |
| 245 | + } |
| 246 | + } |
| 247 | +}; |
| 248 | + |
| 249 | +} )( window.mediaWiki, window.jQuery ); |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerJava.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 250 | + text/plain |
Added: svn:eol-style |
2 | 251 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaPlayers.js |
— | — | @@ -0,0 +1,184 @@ |
| 2 | +/** |
| 3 | + * mediaPlayers is a collection of mediaPlayer objects supported by the client. |
| 4 | + * |
| 5 | + * @constructor |
| 6 | + */ |
| 7 | + |
| 8 | +function mediaPlayers() |
| 9 | +{ |
| 10 | + this.init(); |
| 11 | +} |
| 12 | + |
| 13 | +mediaPlayers.prototype = |
| 14 | +{ |
| 15 | + // The list of players supported |
| 16 | + players : null, |
| 17 | + |
| 18 | + // Store per mime-type prefrences for players |
| 19 | + preference : { }, |
| 20 | + |
| 21 | + // Stores the default set of players for a given mime type |
| 22 | + defaultPlayers : { }, |
| 23 | + |
| 24 | + /** |
| 25 | + * Initializartion function sets the default order for players for a given |
| 26 | + * mime type |
| 27 | + */ |
| 28 | + init: function() { |
| 29 | + this.players = new Array(); |
| 30 | + this.loadPreferences(); |
| 31 | + |
| 32 | + // set up default players order for each library type |
| 33 | + this.defaultPlayers['video/x-flv'] = ['Kplayer', 'Vlc']; |
| 34 | + this.defaultPlayers['video/h264'] = ['Native', 'Kplayer', 'Vlc']; |
| 35 | + |
| 36 | + this.defaultPlayers['video/ogg'] = ['Native', 'Vlc', 'Java', 'Generic']; |
| 37 | + this.defaultPlayers['video/webm'] = ['Native', 'Vlc']; |
| 38 | + this.defaultPlayers['application/ogg'] = ['Native', 'Vlc', 'Java', 'Generic']; |
| 39 | + this.defaultPlayers['audio/ogg'] = ['Native', 'Vlc', 'Java' ]; |
| 40 | + this.defaultPlayers['video/mp4'] = ['Vlc']; |
| 41 | + this.defaultPlayers['video/mpeg'] = ['Vlc']; |
| 42 | + this.defaultPlayers['video/x-msvideo'] = ['Vlc']; |
| 43 | + |
| 44 | + this.defaultPlayers['text/html'] = ['Html']; |
| 45 | + this.defaultPlayers['image/jpeg'] = ['Html']; |
| 46 | + this.defaultPlayers['image/png'] = ['Html']; |
| 47 | + this.defaultPlayers['image/svg'] = ['Html']; |
| 48 | + |
| 49 | + }, |
| 50 | + |
| 51 | + /** |
| 52 | + * Adds a Player to the player list |
| 53 | + * |
| 54 | + * @param {Object} |
| 55 | + * player Player object to be added |
| 56 | + */ |
| 57 | + addPlayer: function( player ) { |
| 58 | + for ( var i = 0; i < this.players.length; i++ ) { |
| 59 | + if ( this.players[i].id == player.id ) { |
| 60 | + // Player already found |
| 61 | + return ; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + |
| 66 | + // Add the player: |
| 67 | + this.players.push( player ); |
| 68 | + }, |
| 69 | + |
| 70 | + /** |
| 71 | + * Checks if a player is supported by id |
| 72 | + */ |
| 73 | + isSupportedPlayer: function( playerId ){ |
| 74 | + for( var i=0; i < this.players.length; i++ ){ |
| 75 | + if( this.players[i].id == playerId ){ |
| 76 | + return true; |
| 77 | + } |
| 78 | + } |
| 79 | + return false; |
| 80 | + }, |
| 81 | + |
| 82 | + /** |
| 83 | + * get players that support a given mimeType |
| 84 | + * |
| 85 | + * @param {String} |
| 86 | + * mimeType Mime type of player set |
| 87 | + * @return {Array} Array of players that support a the requested mime type |
| 88 | + */ |
| 89 | + getMIMETypePlayers: function( mimeType ) { |
| 90 | + var mimePlayers = new Array(); |
| 91 | + var _this = this; |
| 92 | + if ( this.defaultPlayers[mimeType] ) { |
| 93 | + $j.each( this.defaultPlayers[ mimeType ], function( d, lib ) { |
| 94 | + var library = _this.defaultPlayers[ mimeType ][ d ]; |
| 95 | + for ( var i = 0; i < _this.players.length; i++ ) { |
| 96 | + if ( _this.players[i].library == library && _this.players[i].supportsMIMEType( mimeType ) ) { |
| 97 | + mimePlayers.push( _this.players[i] ); |
| 98 | + } |
| 99 | + } |
| 100 | + } ); |
| 101 | + } |
| 102 | + return mimePlayers; |
| 103 | + }, |
| 104 | + |
| 105 | + /** |
| 106 | + * Default player for a given mime type |
| 107 | + * |
| 108 | + * @param {String} |
| 109 | + * mimeType Mime type of the requested player |
| 110 | + * @return Player for mime type null if no player found |
| 111 | + */ |
| 112 | + defaultPlayer : function( mimeType ) { |
| 113 | + // mw.log( "get defaultPlayer for " + mimeType ); |
| 114 | + var mimePlayers = this.getMIMETypePlayers( mimeType ); |
| 115 | + if ( mimePlayers.length > 0 ) |
| 116 | + { |
| 117 | + // Check for prior preference for this mime type |
| 118 | + for ( var i = 0; i < mimePlayers.length; i++ ) { |
| 119 | + if ( mimePlayers[i].id == this.preference[mimeType] ) |
| 120 | + return mimePlayers[i]; |
| 121 | + } |
| 122 | + // Otherwise just return the first compatible player |
| 123 | + // (it will be chosen according to the defaultPlayers list |
| 124 | + return mimePlayers[0]; |
| 125 | + } |
| 126 | + // mw.log( 'No default player found for ' + mimeType ); |
| 127 | + return null; |
| 128 | + }, |
| 129 | + |
| 130 | + /** |
| 131 | + * Sets the format preference. |
| 132 | + * |
| 133 | + * @param {String} |
| 134 | + * mimeFormat Prefered format |
| 135 | + */ |
| 136 | + setFormatPreference : function ( mimeFormat ) { |
| 137 | + this.preference['formatPreference'] = mimeFormat; |
| 138 | + $.cookie( 'EmbedPlayer.Preference', this.preference); |
| 139 | + }, |
| 140 | + |
| 141 | + /** |
| 142 | + * Sets the player preference |
| 143 | + * |
| 144 | + * @param {String} |
| 145 | + * playerId Prefered player id |
| 146 | + * @param {String} |
| 147 | + * mimeType Mime type for the associated player stream |
| 148 | + */ |
| 149 | + setPlayerPreference : function( playerId, mimeType ) { |
| 150 | + var selectedPlayer = null; |
| 151 | + for ( var i = 0; i < this.players.length; i++ ) { |
| 152 | + if ( this.players[i].id == playerId ) { |
| 153 | + selectedPlayer = this.players[i]; |
| 154 | + mw.log( 'EmbedPlayer::setPlayerPreference: choosing ' + playerId + ' for ' + mimeType ); |
| 155 | + this.preference[ mimeType ] = playerId; |
| 156 | + $.cookie( 'EmbedPlayer.Preference', this.preference ); |
| 157 | + break; |
| 158 | + } |
| 159 | + } |
| 160 | + // Update All the player instances on the page |
| 161 | + if ( selectedPlayer ) { |
| 162 | + $('.mwEmbedPlayer').each(function(inx, playerTarget ){ |
| 163 | + var embedPlayer = $( playerTarget ).get( 0 ); |
| 164 | + if ( embedPlayer.mediaElement.selectedSource |
| 165 | + && ( embedPlayer.mediaElement.selectedSource.mimeType == mimeType ) ) |
| 166 | + { |
| 167 | + embedPlayer.selectPlayer( selectedPlayer ); |
| 168 | + } |
| 169 | + }); |
| 170 | + } |
| 171 | + }, |
| 172 | + |
| 173 | + /** |
| 174 | + * Loads the user preference settings from a cookie |
| 175 | + */ |
| 176 | + loadPreferences : function ( ) { |
| 177 | + this.preference = { }; |
| 178 | + // See if we have a cookie set to a clientSupported type: |
| 179 | + if( $.cookie( 'EmbedPlayer.Preference' ) ) { |
| 180 | + this.preference = $.cookie( 'EmbedPlayer.Preference' ); |
| 181 | + } |
| 182 | + } |
| 183 | +}; |
| 184 | + |
| 185 | + |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaPlayers.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 186 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaSource.js |
— | — | @@ -0,0 +1,318 @@ |
| 2 | +/** |
| 3 | + * mediaSource class represents a source for a media element. |
| 4 | + * |
| 5 | + * @param {Element} |
| 6 | + * element: MIME type of the source. |
| 7 | + * @constructor |
| 8 | + */ |
| 9 | + |
| 10 | +function mediaSource( element ) { |
| 11 | + this.init( element ); |
| 12 | +} |
| 13 | + |
| 14 | +mediaSource.prototype = { |
| 15 | + // True if the source has been marked as the default. |
| 16 | + markedDefault: false, |
| 17 | + |
| 18 | + // True if the source supports url specification of offset and duration |
| 19 | + URLTimeEncoding:false, |
| 20 | + |
| 21 | + // Start offset of the requested segment |
| 22 | + startOffset: 0, |
| 23 | + |
| 24 | + // Duration of the requested segment (0 if not known) |
| 25 | + duration:0, |
| 26 | + |
| 27 | + /** |
| 28 | + * MediaSource constructor: |
| 29 | + */ |
| 30 | + init : function( element ) { |
| 31 | + // mw.log('EmbedPlayer::adding mediaSource: ' + element); |
| 32 | + this.src = $( element ).attr( 'src' ); |
| 33 | + |
| 34 | + // Set default URLTimeEncoding if we have a time url: |
| 35 | + // not ideal way to discover if content is on an oggz_chop server. |
| 36 | + // should check some other way. |
| 37 | + var pUrl = mw.parseUri ( this.src ); |
| 38 | + if ( typeof pUrl[ 'queryKey' ][ 't' ] != 'undefined' ) { |
| 39 | + this.URLTimeEncoding = true; |
| 40 | + } |
| 41 | + |
| 42 | + var sourceAttr = mw.getConfig( 'EmbedPlayer.SourceAttributes' ); |
| 43 | + for ( var i = 0; i < sourceAttr.length; i++ ) { // array loop: |
| 44 | + var attr = sourceAttr[ i ]; |
| 45 | + var attrValue = $( element ).attr( attr ); |
| 46 | + if ( attrValue ) { |
| 47 | + // strip data- from the attribute name |
| 48 | + if( attr.indexOf('data-') === 0){ |
| 49 | + attr = attr.substr(5); |
| 50 | + } |
| 51 | + this[ attr ] = attrValue; |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + // Set the content type: |
| 56 | + if ( $( element ).attr( 'type' ) ) { |
| 57 | + this.mimeType = $( element ).attr( 'type' ); |
| 58 | + }else if ( $( element ).attr( 'content-type' ) ) { |
| 59 | + this.mimeType = $( element ).attr( 'content-type' ); |
| 60 | + }else if( $( element ).get(0).tagName.toLowerCase() == 'audio' ){ |
| 61 | + // If the element is an "audio" tag set audio format |
| 62 | + this.mimeType = 'audio/ogg'; |
| 63 | + } else { |
| 64 | + this.mimeType = this.detectType( this.src ); |
| 65 | + } |
| 66 | + |
| 67 | + // Conform the mime type to ogg |
| 68 | + if( this.mimeType == 'video/theora') { |
| 69 | + this.mimeType = 'video/ogg'; |
| 70 | + } |
| 71 | + |
| 72 | + if( this.mimeType == 'audio/vorbis') { |
| 73 | + this.mimeType = 'audio/ogg'; |
| 74 | + } |
| 75 | + |
| 76 | + // Check for parent elements ( supplies categories in "track" ) |
| 77 | + if( $( element ).parent().attr('category') ) { |
| 78 | + this.category = $( element ).parent().attr('category'); |
| 79 | + } |
| 80 | + |
| 81 | + if( $( element ).attr( 'default' ) ){ |
| 82 | + this.markedDefault = true; |
| 83 | + } |
| 84 | + |
| 85 | + // Get the url duration ( if applicable ) |
| 86 | + this.getURLDuration(); |
| 87 | + }, |
| 88 | + |
| 89 | + /** |
| 90 | + * Update Source title via Element |
| 91 | + * |
| 92 | + * @param {Element} |
| 93 | + * element Source element to update attributes from |
| 94 | + */ |
| 95 | + updateSource: function( element ) { |
| 96 | + // for now just update the title: |
| 97 | + if ( $( element ).attr( "title" ) ) { |
| 98 | + this.title = $( element ).attr( "title" ); |
| 99 | + } |
| 100 | + }, |
| 101 | + |
| 102 | + /** |
| 103 | + * Updates the src time and start & end |
| 104 | + * |
| 105 | + * @param {String} |
| 106 | + * start_time: in NPT format |
| 107 | + * @param {String} |
| 108 | + * end_time: in NPT format |
| 109 | + */ |
| 110 | + updateSrcTime: function ( start_npt, end_npt ) { |
| 111 | + // mw.log("f:updateSrcTime: "+ start_npt+'/'+ end_npt + ' from org: ' + |
| 112 | + // this.start_npt+ '/'+this.end_npt); |
| 113 | + // mw.log("pre uri:" + this.src); |
| 114 | + // if we have time we can use: |
| 115 | + if ( this.URLTimeEncoding ) { |
| 116 | + // make sure its a valid start time / end time (else set default) |
| 117 | + if ( !mw.npt2seconds( start_npt ) ) { |
| 118 | + start_npt = this.start_npt; |
| 119 | + } |
| 120 | + |
| 121 | + if ( !mw.npt2seconds( end_npt ) ) { |
| 122 | + end_npt = this.end_npt; |
| 123 | + } |
| 124 | + |
| 125 | + this.src = mw.replaceUrlParams( this.src, { |
| 126 | + 't': start_npt + '/' + end_npt |
| 127 | + }); |
| 128 | + |
| 129 | + // update the duration |
| 130 | + this.getURLDuration(); |
| 131 | + } |
| 132 | + }, |
| 133 | + |
| 134 | + /** |
| 135 | + * Sets the duration and sets the end time if unset |
| 136 | + * |
| 137 | + * @param {Float} |
| 138 | + * duration: in seconds |
| 139 | + */ |
| 140 | + setDuration: function ( duration ) { |
| 141 | + this.duration = duration; |
| 142 | + if ( !this.end_npt ) { |
| 143 | + this.end_npt = mw.seconds2npt( this.startOffset + duration ); |
| 144 | + } |
| 145 | + }, |
| 146 | + |
| 147 | + /** |
| 148 | + * MIME type accessors function. |
| 149 | + * |
| 150 | + * @return {String} the MIME type of the source. |
| 151 | + */ |
| 152 | + getMIMEType: function() { |
| 153 | + if( this.mimeType ) { |
| 154 | + return this.mimeType; |
| 155 | + } |
| 156 | + this.mimeType = this.detectType( this.src ); |
| 157 | + return this.mimeType; |
| 158 | + }, |
| 159 | + |
| 160 | + /** |
| 161 | + * URI function. |
| 162 | + * |
| 163 | + * @param {Number} |
| 164 | + * serverSeekTime Int: Used to adjust the URI for url based |
| 165 | + * seeks) |
| 166 | + * @return {String} the URI of the source. |
| 167 | + */ |
| 168 | + getSrc: function( serverSeekTime ) { |
| 169 | + if ( !serverSeekTime || !this.URLTimeEncoding ) { |
| 170 | + return this.src; |
| 171 | + } |
| 172 | + var endvar = ''; |
| 173 | + if ( this.end_npt ) { |
| 174 | + endvar = '/' + this.end_npt; |
| 175 | + } |
| 176 | + return mw.replaceUrlParams( this.src, |
| 177 | + { |
| 178 | + 't': mw.seconds2npt( serverSeekTime ) + endvar |
| 179 | + } |
| 180 | + ); |
| 181 | + }, |
| 182 | + |
| 183 | + /** |
| 184 | + * Title accessor function. |
| 185 | + * |
| 186 | + * @return {String} Title of the source. |
| 187 | + */ |
| 188 | + getTitle : function() { |
| 189 | + if( this.title ){ |
| 190 | + return this.title; |
| 191 | + } |
| 192 | + // Text tracks use "label" instead of "title" |
| 193 | + if( this.label ){ |
| 194 | + return this.label; |
| 195 | + } |
| 196 | + |
| 197 | + // Return a Title based on mime type: |
| 198 | + switch( this.getMIMEType() ) { |
| 199 | + case 'video/h264' : |
| 200 | + return gM( 'mwe-embedplayer-video-h264' ); |
| 201 | + break; |
| 202 | + case 'video/x-flv' : |
| 203 | + return gM( 'mwe-embedplayer-video-flv' ); |
| 204 | + break; |
| 205 | + case 'video/webm' : |
| 206 | + return gM( 'mwe-embedplayer-video-webm'); |
| 207 | + break; |
| 208 | + case 'video/ogg' : |
| 209 | + return gM( 'mwe-embedplayer-video-ogg' ); |
| 210 | + break; |
| 211 | + case 'audio/ogg' : |
| 212 | + return gM( 'mwe-embedplayer-video-audio' ); |
| 213 | + break; |
| 214 | + case 'video/mpeg' : |
| 215 | + return 'MPEG video'; // FIXME: i18n |
| 216 | + break; |
| 217 | + case 'video/x-msvideo' : |
| 218 | + return 'AVI video'; // FIXME: i18n |
| 219 | + break; |
| 220 | + } |
| 221 | + |
| 222 | + // Return tilte based on file name: |
| 223 | + var urlParts = mw.parseUri( this.getSrc() ); |
| 224 | + if( urlParts.file ){ |
| 225 | + return urlParts.file; |
| 226 | + } |
| 227 | + |
| 228 | + // Return the mime type string if not known type. |
| 229 | + return this.mimeType; |
| 230 | + }, |
| 231 | + |
| 232 | + /** |
| 233 | + * |
| 234 | + * Get Duration of the media in milliseconds from the source url. |
| 235 | + * |
| 236 | + * Supports media_url?t=ntp_start/ntp_end url request format |
| 237 | + */ |
| 238 | + getURLDuration : function() { |
| 239 | + // check if we have a URLTimeEncoding: |
| 240 | + if ( this.URLTimeEncoding ) { |
| 241 | + var annoURL = mw.parseUri( this.src ); |
| 242 | + if ( annoURL.queryKey.t ) { |
| 243 | + var times = annoURL.queryKey.t.split( '/' ); |
| 244 | + this.start_npt = times[0]; |
| 245 | + this.end_npt = times[1]; |
| 246 | + this.startOffset = mw.npt2seconds( this.start_npt ); |
| 247 | + this.duration = mw.npt2seconds( this.end_npt ) - this.startOffset; |
| 248 | + } else { |
| 249 | + // look for this info as attributes |
| 250 | + if ( this.startOffset ) { |
| 251 | + this.start_npt = mw.seconds2npt( this.startOffset ); |
| 252 | + } |
| 253 | + if ( this.duration ) { |
| 254 | + this.end_npt = mw.seconds2npt( parseInt( this.duration ) + parseInt( this.startOffset ) ); |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + }, |
| 259 | + |
| 260 | + /** |
| 261 | + * Attempts to detect the type of a media file based on the URI. |
| 262 | + * |
| 263 | + * @param {String} |
| 264 | + * uri URI of the media file. |
| 265 | + * @return {String} The guessed MIME type of the file. |
| 266 | + */ |
| 267 | + detectType: function( uri ) { |
| 268 | + // NOTE: if media is on the same server as the javascript |
| 269 | + // we can issue a HEAD request and read the mime type of the media... |
| 270 | + // ( this will detect media mime type independently of the url name ) |
| 271 | + // http://www.jibbering.com/2002/4/httprequest.html |
| 272 | + var urlParts = mw.parseUri( uri ); |
| 273 | + // Get the extension from the url or from the relative name: |
| 274 | + var ext = ( urlParts.file )? /[^.]+$/.exec( urlParts.file ) : /[^.]+$/.exec( uri ); |
| 275 | + switch( ext.toString().toLowerCase() ) { |
| 276 | + case 'smil': |
| 277 | + case 'sml': |
| 278 | + return 'application/smil'; |
| 279 | + break; |
| 280 | + case 'm4v': |
| 281 | + case 'mp4': |
| 282 | + return 'video/h264'; |
| 283 | + break; |
| 284 | + case 'webm': |
| 285 | + return 'video/webm'; |
| 286 | + break; |
| 287 | + case 'srt': |
| 288 | + return 'text/x-srt'; |
| 289 | + break; |
| 290 | + case 'flv': |
| 291 | + return 'video/x-flv'; |
| 292 | + break; |
| 293 | + case 'ogg': |
| 294 | + case 'ogv': |
| 295 | + return 'video/ogg'; |
| 296 | + break; |
| 297 | + case 'oga': |
| 298 | + return 'audio/ogg'; |
| 299 | + break; |
| 300 | + case 'anx': |
| 301 | + return 'video/ogg'; |
| 302 | + break; |
| 303 | + case 'xml': |
| 304 | + return 'text/xml'; |
| 305 | + break; |
| 306 | + case 'avi': |
| 307 | + return 'video/x-msvideo'; |
| 308 | + break; |
| 309 | + case 'mpg': |
| 310 | + return 'video/mpeg'; |
| 311 | + break; |
| 312 | + case 'mpeg': |
| 313 | + return 'video/mpeg'; |
| 314 | + break; |
| 315 | + } |
| 316 | + mw.log( "Error: could not detect type of media src: " + uri ); |
| 317 | + } |
| 318 | +}; |
| 319 | + |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/MediaSource.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 320 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerVlc.js |
— | — | @@ -0,0 +1,369 @@ |
| 2 | +/* |
| 3 | +* VLC embed based on: http://people.videolan.org/~damienf/plugin-0.8.6.html |
| 4 | +* javascript api: http://www.videolan.org/doc/play-howto/en/ch04.html |
| 5 | +* assume version > 0.8.5.1 |
| 6 | +*/ |
| 7 | +( function( mw, $ ) { |
| 8 | + |
| 9 | +mw.EmbedPlayerVlc = { |
| 10 | + |
| 11 | + //Instance Name: |
| 12 | + instanceOf : 'Vlc', |
| 13 | + |
| 14 | + //What the vlc player / plug-in supports: |
| 15 | + supports : { |
| 16 | + 'playHead':true, |
| 17 | + 'pause':true, |
| 18 | + 'stop':true, |
| 19 | + 'fullscreen':true, |
| 20 | + 'timeDisplay':true, |
| 21 | + 'volumeControl':true, |
| 22 | + |
| 23 | + 'playlist_driver':true, // if the object supports playlist functions |
| 24 | + 'overlay':false |
| 25 | + }, |
| 26 | + |
| 27 | + // The previous state of the player instance |
| 28 | + prevState : 0, |
| 29 | + |
| 30 | + // Counter for waiting for vlc embed to be ready |
| 31 | + waitForVlcCount:0, |
| 32 | + |
| 33 | + // Store the current play time for vlc |
| 34 | + vlcCurrentTime: 0, |
| 35 | + |
| 36 | + /** |
| 37 | + * Get embed HTML |
| 38 | + */ |
| 39 | + doEmbedHTML: function() { |
| 40 | + var _this = this; |
| 41 | + $( this ).html( |
| 42 | + '<object classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921" ' + |
| 43 | + 'codebase="http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab#Version=0,8,6,0" ' + |
| 44 | + 'id="' + this.pid + '" events="True" height="' + this.getPlayerHeight() + '" width="' + this.getPlayerWidth() + '"' + |
| 45 | + '>' + |
| 46 | + '<param name="MRL" value="">' + |
| 47 | + '<param name="ShowDisplay" value="True">' + |
| 48 | + '<param name="AutoLoop" value="False">' + |
| 49 | + '<param name="AutoPlay" value="False">' + |
| 50 | + '<param name="Volume" value="50">' + |
| 51 | + '<param name="StartTime" value="0">' + |
| 52 | + '<embed pluginspage="http://www.videolan.org" type="application/x-vlc-plugin" ' + |
| 53 | + 'progid="VideoLAN.VLCPlugin.2" name="' + this.pid + '" ' + |
| 54 | + 'height="' + this.getHeight() + '" width="' + this.getWidth() + '" ' + |
| 55 | + // set the style too 'just to be sure' |
| 56 | + 'style="width:' + this.getWidth() + 'px;height:' + this.getHeight() + 'px;" ' + |
| 57 | + '>' + |
| 58 | + '</object>' |
| 59 | + ) |
| 60 | + /* |
| 61 | + $( this ).html( |
| 62 | + '<embed type="application/x-vlc-plugin" pluginspage="http://www.videolan.org" version="VideoLAN.VLCPlugin.2" '+ |
| 63 | + 'width="' + this.width +'" ' + |
| 64 | + 'height="' + this.height + '" ' + |
| 65 | + 'id="' + this.pid + '"> ' + |
| 66 | + '</embed>' |
| 67 | + );*/ |
| 68 | + |
| 69 | + |
| 70 | + // give VLC 150ms to initialize before we start playback |
| 71 | + // @@todo should be able to do this as an ready event |
| 72 | + this.waitForVlcCount = 0; |
| 73 | + setTimeout( function() { |
| 74 | + _this.postEmbedJS(); |
| 75 | + }, 150 ); |
| 76 | + }, |
| 77 | + |
| 78 | + /** |
| 79 | + * Javascript to run post vlc embedding |
| 80 | + * Inserts the requested src to the embed instance |
| 81 | + */ |
| 82 | + postEmbedJS: function() { |
| 83 | + var _this = this; |
| 84 | + // load a pointer to the vlc into the object (this.playerElement) |
| 85 | + this.getPlayerElement(); |
| 86 | + if ( this.playerElement && this.playerElement.playlist) { |
| 87 | + // manipulate the dom object to make sure vlc has the correct size: |
| 88 | + this.playerElement.style.width = this.getWidth(); |
| 89 | + this.playerElement.style.height = this.getHeight(); |
| 90 | + this.playerElement.playlist.items.clear(); |
| 91 | + |
| 92 | + // VLC likes absolute urls: |
| 93 | + var src = mw.absoluteUrl( this.getSrc() ) ; |
| 94 | + |
| 95 | + // @@todo if client supports seeking no need to send seek_offset to URI |
| 96 | + mw.log( 'vlc play::' + src ); |
| 97 | + var itemId = this.playerElement.playlist.add( src ); |
| 98 | + if ( itemId != -1 ) { |
| 99 | + // Play |
| 100 | + this.playerElement.playlist.playItem( itemId ); |
| 101 | + } else { |
| 102 | + mw.log( "error:cannot play at the moment !" ); |
| 103 | + } |
| 104 | + setTimeout( function() { |
| 105 | + _this.monitor(); |
| 106 | + }, 100 ); |
| 107 | + } else { |
| 108 | + mw.log( 'postEmbedJS: vlc not ready' ); |
| 109 | + this.waitForVlcCount++; |
| 110 | + if ( this.waitForVlcCount < 10 ) { |
| 111 | + setTimeout( function() { |
| 112 | + _this.postEmbedJS(); |
| 113 | + }, 100 ); |
| 114 | + } else { |
| 115 | + mw.log( 'vlc never ready' ); |
| 116 | + } |
| 117 | + } |
| 118 | + }, |
| 119 | + |
| 120 | + /** |
| 121 | + * Handles seek requests based on temporal media source type support |
| 122 | + * |
| 123 | + * @param {Float} percent Seek to this percent of the stream |
| 124 | + */ |
| 125 | + doSeek : function( percent ) { |
| 126 | + this.getPlayerElement(); |
| 127 | + if ( this.supportsURLTimeEncoding() ) { |
| 128 | + this.parent_doSeek( percent ); |
| 129 | + } else if ( this.playerElement ) { |
| 130 | + this.seeking = true; |
| 131 | + mw.log( "do vlc http seek to: " + percent ) |
| 132 | + if ( ( this.playerElement.input.state == 3 ) && ( this.playerElement.input.position != percent ) ) |
| 133 | + { |
| 134 | + this.playerElement.input.position = percent; |
| 135 | + this.controlBuilder.setStatus( 'seeking...' ); |
| 136 | + } |
| 137 | + } else { |
| 138 | + this.doPlayThenSeek( percent ); |
| 139 | + } |
| 140 | + this.parent_monitor(); |
| 141 | + }, |
| 142 | + |
| 143 | + /** |
| 144 | + * Issues a play request then seeks to a given time |
| 145 | + * |
| 146 | + * @param {Float} percent Seek to this percent of the stream after playing |
| 147 | + */ |
| 148 | + doPlayThenSeek:function( percent ) { |
| 149 | + mw.log( 'doPlayThenSeekHack' ); |
| 150 | + var _this = this; |
| 151 | + this.play(); |
| 152 | + var rfsCount = 0; |
| 153 | + var readyForSeek = function() { |
| 154 | + _this.getPlayerElement(); |
| 155 | + var newState = _this.playerElement.input.state; |
| 156 | + // if playing we are ready to do the |
| 157 | + if ( newState == 3 ) { |
| 158 | + _this.doSeek( percent ); |
| 159 | + } else { |
| 160 | + // try to get player for 10 seconds: |
| 161 | + if ( rfsCount < 200 ) { |
| 162 | + setTimeout( readyForSeek, 50 ); |
| 163 | + rfsCount++; |
| 164 | + } else { |
| 165 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | + readyForSeek(); |
| 170 | + }, |
| 171 | + |
| 172 | + /** |
| 173 | + * Updates the status time and player state |
| 174 | + */ |
| 175 | + monitor: function() { |
| 176 | + this.getPlayerElement(); |
| 177 | + if ( !this.playerElement ) |
| 178 | + return ; |
| 179 | + try{ |
| 180 | + //mw.log( 'state:' + this.playerElement.input.state); |
| 181 | + //mw.log('time: ' + this.playerElement.input.time); |
| 182 | + //mw.log('pos: ' + this.playerElement.input.position); |
| 183 | + if ( this.playerElement.log.messages.count > 0 ) { |
| 184 | + // there is one or more messages in the log |
| 185 | + var iter = this.playerElement.log.messages.iterator(); |
| 186 | + while ( iter.hasNext ) { |
| 187 | + var msg = iter.next(); |
| 188 | + var msgtype = msg.type.toString(); |
| 189 | + if ( ( msg.severity == 1 ) && ( msgtype == "input" ) ) |
| 190 | + { |
| 191 | + mw.log( msg.message ); |
| 192 | + } |
| 193 | + } |
| 194 | + // clear the log once finished to avoid clogging |
| 195 | + this.playerElement.log.messages.clear(); |
| 196 | + } |
| 197 | + |
| 198 | + var newState = this.playerElement.input.state; |
| 199 | + if ( this.prevState != newState ) { |
| 200 | + if ( newState == 0 ) |
| 201 | + { |
| 202 | + // current media has stopped |
| 203 | + this.onStop(); |
| 204 | + } |
| 205 | + else if ( newState == 1 ) |
| 206 | + { |
| 207 | + // current media is opening/connecting |
| 208 | + this.onOpen(); |
| 209 | + } |
| 210 | + else if ( newState == 2 ) |
| 211 | + { |
| 212 | + // current media is buffering data |
| 213 | + this.onBuffer(); |
| 214 | + } |
| 215 | + else if ( newState == 3 ) |
| 216 | + { |
| 217 | + // current media is now playing |
| 218 | + this.onPlay(); |
| 219 | + } |
| 220 | + else if ( this.playerElement.input.state == 4 ) { |
| 221 | + // current media is now paused |
| 222 | + this.onPause(); |
| 223 | + } |
| 224 | + this.prevState = newState; |
| 225 | + } else if ( newState == 3 ) { |
| 226 | + // current media is playing |
| 227 | + this.onPlaying(); |
| 228 | + } |
| 229 | + } catch( e ){ |
| 230 | + mw.log("EmbedPlayerVlc::Monitor error"); |
| 231 | + } |
| 232 | + // update the status and check timmer via universal parent monitor |
| 233 | + this.parent_monitor(); |
| 234 | + }, |
| 235 | + |
| 236 | + /** |
| 237 | + * Events: |
| 238 | + * NOTE : should be localized: |
| 239 | + */ |
| 240 | + onOpen: function() { |
| 241 | + this.controlBuilder.setStatus( "Opening..." ); |
| 242 | + }, |
| 243 | + onBuffer: function() { |
| 244 | + this.controlBuilder.setStatus( "Buffering..." ); |
| 245 | + }, |
| 246 | + onPlay: function() { |
| 247 | + this.onPlaying(); |
| 248 | + }, |
| 249 | + onPlaying: function() { |
| 250 | + this.seeking = false; |
| 251 | + // for now trust the duration from url over vlc input.length |
| 252 | + if ( !this.getDuration() && this.playerElement.input.length > 0 ) |
| 253 | + { |
| 254 | + // mw.log('setting duration to ' + this.playerElement.input.length /1000); |
| 255 | + this.duration = this.playerElement.input.length / 1000; |
| 256 | + } |
| 257 | + this.vlcCurrentTime = this.playerElement.input.time / 1000; |
| 258 | + }, |
| 259 | + |
| 260 | + /** |
| 261 | + * Get the embed player time |
| 262 | + */ |
| 263 | + getPlayerElementTime: function(){ |
| 264 | + return this.vlcCurrentTime; |
| 265 | + }, |
| 266 | + |
| 267 | + onPause: function() { |
| 268 | + this.parent_pause(); // update the inteface if paused via native control |
| 269 | + }, |
| 270 | + onStop: function() { |
| 271 | + mw.log( 'vlc:onStop:' ); |
| 272 | + if ( !this.seeking ) |
| 273 | + this.onClipDone(); |
| 274 | + }, |
| 275 | + |
| 276 | + /** |
| 277 | + * Handles play requests |
| 278 | + */ |
| 279 | + play : function() { |
| 280 | + mw.log( 'f:vlcPlay' ); |
| 281 | + // Update the interface |
| 282 | + this.parent_play(); |
| 283 | + if ( this.getPlayerElement() ) { |
| 284 | + // plugin is already being present send play call: |
| 285 | + // clear the message log and enable error logging |
| 286 | + if ( this.playerElement.log ) { |
| 287 | + this.playerElement.log.messages.clear(); |
| 288 | + } |
| 289 | + if ( this.playerElement.playlist && typeof this.playerElement.playlist.play == 'function') |
| 290 | + this.playerElement.playlist.play(); |
| 291 | + |
| 292 | + if( typeof this.playerElement.play == 'function' ) |
| 293 | + this.playerElement.play(); |
| 294 | + |
| 295 | + this.paused = false; |
| 296 | + |
| 297 | + // re-start the monitor: |
| 298 | + this.monitor(); |
| 299 | + } |
| 300 | + }, |
| 301 | + |
| 302 | + /** |
| 303 | + * Passes the Pause request to the plugin. |
| 304 | + * calls parent "pause" to update interface |
| 305 | + */ |
| 306 | + pause : function() { |
| 307 | + this.parent_pause(); // update the interface if paused via native control |
| 308 | + if ( this.getPlayerElement() ) { |
| 309 | + try{ |
| 310 | + this.playerElement.playlist.togglePause(); |
| 311 | + } catch( e ){ |
| 312 | + mw.log("EmbedPlayerVlc could not pause video " + e); |
| 313 | + } |
| 314 | + } |
| 315 | + }, |
| 316 | + |
| 317 | + /** |
| 318 | + * Mutes the video |
| 319 | + * calls parent "toggleMute" to update interface |
| 320 | + */ |
| 321 | + toggleMute:function() { |
| 322 | + this.parent_toggleMute(); |
| 323 | + if ( this.getPlayerElement() ) |
| 324 | + this.playerElement.audio.toggleMute(); |
| 325 | + }, |
| 326 | + |
| 327 | + /** |
| 328 | + * Update the player volume |
| 329 | + * @pram {Float} percent Percent of total volume |
| 330 | + */ |
| 331 | + setPlayerElementVolume: function ( percent ) { |
| 332 | + if ( this.getPlayerElement() ) { |
| 333 | + this.playerElement.audio.volume = percent * 100; |
| 334 | + } |
| 335 | + }, |
| 336 | + |
| 337 | + /** |
| 338 | + * Gets the current volume |
| 339 | + * @return {Float} percent percent of total volume |
| 340 | + */ |
| 341 | + getVolumen:function() { |
| 342 | + if ( this.getPlayerElement() ) |
| 343 | + return this.playerElement.audio.volume / 100; |
| 344 | + }, |
| 345 | + |
| 346 | + /** |
| 347 | + * Passes fullscreen request to plugin |
| 348 | + */ |
| 349 | + fullscreen : function() { |
| 350 | + if ( this.playerElement ) { |
| 351 | + if ( this.playerElement.video ){ |
| 352 | + try{ |
| 353 | + this.playerElement.video.toggleFullscreen(); |
| 354 | + } catch ( e ){ |
| 355 | + mw.log("VlcEmbed toggle fullscreen : possible error: " + e); |
| 356 | + } |
| 357 | + } |
| 358 | + } |
| 359 | + }, |
| 360 | + |
| 361 | + /** |
| 362 | + * Get the embed vlc object |
| 363 | + */ |
| 364 | + getPlayerElement : function() { |
| 365 | + this.playerElement = $( '#' + this.pid ).get(0); |
| 366 | + return this.playerElement; |
| 367 | + } |
| 368 | +}; |
| 369 | + |
| 370 | +} )( window.mediaWiki, window.jQuery ); |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerVlc.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 371 | + text/plain |
Added: svn:eol-style |
2 | 372 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/iframeApi/mw.IFramePlayerApiClient.js |
— | — | @@ -0,0 +1,204 @@ |
| 2 | +/** |
| 3 | +* iFrame api mapping support |
| 4 | +* |
| 5 | +* Client side ( binds a given iFrames to expose the player api ) |
| 6 | +*/ |
| 7 | + |
| 8 | +( function( mw ) { |
| 9 | + |
| 10 | +mw.IFramePlayerApiClient = function( iframe, playerProxy ){ |
| 11 | + return this.init( iframe , playerProxy ); |
| 12 | +} |
| 13 | +mw.IFramePlayerApiClient.prototype = { |
| 14 | + // flag to register if the iframe is in fullscreen mode |
| 15 | + inFullScreenMode: null, |
| 16 | + |
| 17 | + 'exportedMethods': [ |
| 18 | + 'play', |
| 19 | + 'pause' |
| 20 | + ], |
| 21 | + // Local store of the previous sate of player proxy |
| 22 | + '_prevPlayerProxy': {}, |
| 23 | + |
| 24 | + // Stores the current playerProxy ( can be updated by user js ) |
| 25 | + 'init': function( iframe , playerProxy, options ){ |
| 26 | + this.iframe = iframe; |
| 27 | + this.playerProxy = playerProxy; |
| 28 | + // Set the iframe server |
| 29 | + var srcParts = mw.parseUri( mw.absoluteUrl( $(this.iframe).attr('src') ) ); |
| 30 | + this.iframeServer = srcParts.protocol + '://' + srcParts.authority; |
| 31 | + |
| 32 | + this.addPlayerSendApi(); |
| 33 | + this.addPlayerReciveApi(); |
| 34 | + |
| 35 | + this.addIframeFullscreenBinding(); |
| 36 | + }, |
| 37 | + 'addPlayerSendApi': function(){ |
| 38 | + var _this = this; |
| 39 | + |
| 40 | + // Allow modules to extend the list of iframeExported bindings |
| 41 | + $( mw ).trigger( 'AddIframePlayerMethods', [ this.exportedMethods ]); |
| 42 | + |
| 43 | + $j.each( this.exportedMethods, function(na, method){ |
| 44 | + _this.playerProxy[ method ] = function(){ |
| 45 | + _this.postMessage( { |
| 46 | + 'method' : method, |
| 47 | + 'args' : $j.makeArray( arguments ) |
| 48 | + } ); |
| 49 | + }; |
| 50 | + }); |
| 51 | + }, |
| 52 | + 'addPlayerReciveApi': function(){ |
| 53 | + var _this = this; |
| 54 | + $j.receiveMessage( function( event ){ |
| 55 | + _this.hanldeReciveMsg( event ); |
| 56 | + }, this.iframeServer); |
| 57 | + }, |
| 58 | + 'addIframeFullscreenBinding': function(){ |
| 59 | + var _this = this; |
| 60 | + parentsAbsoluteList = []; |
| 61 | + var fullscreenMode = false; |
| 62 | + var orgSize = { |
| 63 | + 'width' : $( _this.iframe ).width(), |
| 64 | + 'height' : $( _this.iframe ).height(), |
| 65 | + 'position' : null |
| 66 | + }; |
| 67 | + |
| 68 | + // Bind orientation change to resize player ( if fullscreen ) |
| 69 | + $j(window).bind( 'orientationchange', function(e){ |
| 70 | + if( _this.inFullScreenMode ){ |
| 71 | + doFullscreen(); |
| 72 | + } |
| 73 | + }); |
| 74 | + |
| 75 | + var doFullscreen = function(){ |
| 76 | + _this.inFullScreenMode = true; |
| 77 | + // Make the iframe fullscreen |
| 78 | + $( _this.iframe ).css({ |
| 79 | + 'z-index': mw.getConfig( 'EmbedPlayer.FullScreenZIndex' ) + 1, |
| 80 | + 'position': 'absolute', |
| 81 | + 'top' : 0, |
| 82 | + 'left' : 0, |
| 83 | + 'width' : $(window).width(), |
| 84 | + 'height' : $(window).height() |
| 85 | + }); |
| 86 | + |
| 87 | + // Remove absolute css of the interface parents |
| 88 | + $( _this.iframe ).parents().each( function() { |
| 89 | + //mw.log(' parent : ' + $( this ).attr('id' ) + ' class: ' + $( this ).attr('class') + ' pos: ' + $( this ).css( 'position' ) ); |
| 90 | + if( $( this ).css( 'position' ) == 'absolute' ) { |
| 91 | + parentsAbsoluteList.push( $( this ) ); |
| 92 | + $( this ).css( 'position', null ); |
| 93 | + } |
| 94 | + } ); |
| 95 | + } |
| 96 | + var restoreWindowMode = function(){ |
| 97 | + _this.inFullScreenMode = false; |
| 98 | + $( _this.iframe ).css( orgSize ); |
| 99 | + // restore any parent absolute pos: |
| 100 | + $(parentsAbsoluteList).each( function() { |
| 101 | + $( this ).css( 'position', 'absolute' ); |
| 102 | + } ); |
| 103 | + }; |
| 104 | + |
| 105 | + $( this.playerProxy ).bind( 'onOpenFullScreen', doFullscreen); |
| 106 | + $( this.playerProxy ).bind( 'onCloseFullScreen', restoreWindowMode); |
| 107 | + |
| 108 | + }, |
| 109 | + /** |
| 110 | + * Handle received events |
| 111 | + */ |
| 112 | + 'hanldeReciveMsg': function( event ){ |
| 113 | + var _this = this; |
| 114 | + |
| 115 | + // Decode the message |
| 116 | + var msgObject = JSON.parse( event.data ); |
| 117 | + var playerAttributes = mw.getConfig( 'EmbedPlayer.Attributes' ); |
| 118 | + |
| 119 | + // Before we update local attributes check that the object has not been updated by user js |
| 120 | + for( var attrName in playerAttributes ){ |
| 121 | + if( attrName != 'id' ){ |
| 122 | + if( _this._prevPlayerProxy[ attrName ] != _this.playerProxy[ attrName ] ){ |
| 123 | + //mw.log( "IFramePlayerApiClient:: User js update:" + attrName + ' set to: ' + this.playerProxy[ attrName ] + ' != old: ' + _this._prevPlayerProxy[ attrName ] ); |
| 124 | + // Send the updated attribute back to the iframe: |
| 125 | + _this.postMessage({ |
| 126 | + 'attrName' : attrName, |
| 127 | + 'attrValue' : _this.playerProxy[ attrName ] |
| 128 | + }); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + // Update any attributes |
| 133 | + if( msgObject.attributes ){ |
| 134 | + for( var i in msgObject.attributes ){ |
| 135 | + if( i != 'id' && i != 'class' && i != 'style' ){ |
| 136 | + try{ |
| 137 | + this.playerProxy[ i ] = msgObject.attributes[i]; |
| 138 | + this._prevPlayerProxy[i] = msgObject.attributes[i]; |
| 139 | + } catch( e ){ |
| 140 | + mw.log("Error could not set:" + i ); |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + // Trigger any binding events |
| 146 | + if( typeof msgObject.triggerName != 'undefined' && msgObject.triggerArgs != 'undefined') { |
| 147 | + //mw.log('IFramePlayerApiClient:: trigger: ' + msgObject.triggerName ); |
| 148 | + $( _this.playerProxy ).trigger( msgObject.triggerName, msgObject.triggerArgs ); |
| 149 | + } |
| 150 | + }, |
| 151 | + 'postMessage': function( msgObject ){ |
| 152 | + /*mw.log( "IFramePlayerApiClient:: postMessage(): " + JSON.stringify( msgObject ) + |
| 153 | + ' iframe: ' + this.iframe + ' cw:' + this.iframe.contentWindow + |
| 154 | + ' src: ' + mw.absoluteUrl( $( this.iframe ).attr('src') ) );*/ |
| 155 | + $j.postMessage( |
| 156 | + JSON.stringify( msgObject ), |
| 157 | + mw.absoluteUrl( $( this.iframe ).attr('src') ), |
| 158 | + this.iframe.contentWindow |
| 159 | + ); |
| 160 | + } |
| 161 | +}; |
| 162 | + |
| 163 | +//Add the jQuery binding |
| 164 | +( function( $ ) { |
| 165 | + $.fn.iFramePlayer = function( readyCallback ){ |
| 166 | + if( ! this.selector ){ |
| 167 | + this.selector = $( this ).get(0); |
| 168 | + } |
| 169 | + // Append '_ifp' ( iframe player ) to id of real iframe so that 'id', and 'src' attributes don't conflict |
| 170 | + var originalIframeId = ( $( this.selector ).attr( 'id' ) )? $( this.selector ).attr( 'id' ) : Math.floor( 9999999 * Math.random() ); |
| 171 | + |
| 172 | + var iframePlayerId = originalIframeId + '_ifp' ; |
| 173 | + |
| 174 | + // Append the div element proxy after the iframe |
| 175 | + $( this.selector ) |
| 176 | + .attr('id', iframePlayerId) |
| 177 | + .after( |
| 178 | + $('<div />') |
| 179 | + .attr( 'id', originalIframeId ) |
| 180 | + ); |
| 181 | + |
| 182 | + var playerProxy = $( '#' + originalIframeId ).get(0); |
| 183 | + var iframe = $('#' + iframePlayerId).get(0); |
| 184 | + if(!iframe){ |
| 185 | + mw.log("Error invalide iFramePlayer request"); |
| 186 | + return false; |
| 187 | + } |
| 188 | + if( !iframe['playerApi'] ){ |
| 189 | + iframe['playerApi'] = new mw.IFramePlayerApiClient( iframe, playerProxy ); |
| 190 | + } |
| 191 | + |
| 192 | + // Allow modules to extend the 'iframe' based player |
| 193 | + $( mw ).trigger( 'newIframePlayerClientSide', [ playerProxy ]); |
| 194 | + |
| 195 | + // Bind the iFrame player ready callback |
| 196 | + if( readyCallback ){ |
| 197 | + $( playerProxy ).bind( 'playerReady', readyCallback ) |
| 198 | + }; |
| 199 | + |
| 200 | + // Return the player proxy for chaining player events / attributes |
| 201 | + return $( playerProxy ); |
| 202 | + }; |
| 203 | +} )( jQuery ); |
| 204 | + |
| 205 | +} )( window.mw ); |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/iframeApi/mw.IFramePlayerApiClient.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 206 | + text/plain |
Added: svn:eol-style |
2 | 207 | + native |
Added: svn:executable |
3 | 208 | + * |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/iframeApi/mw.IFramePlayerApiServer.js |
— | — | @@ -0,0 +1,227 @@ |
| 2 | +/** |
| 3 | +* iFrame api mapping support |
| 4 | +* |
| 5 | +* enables player api to be accesses cross domain as |
| 6 | +* if the video element was in page dom |
| 7 | +* |
| 8 | +* native support in: |
| 9 | +* * Internet Explorer 8.0+ |
| 10 | +* * Firefox 3.0+ |
| 11 | +* * Safari 4.0+ |
| 12 | +* * Google Chrome 1.0+ |
| 13 | +* * Opera 9.5+ |
| 14 | +* |
| 15 | +* fallback iframe cross domain hack will target IE6/7 |
| 16 | +*/ |
| 17 | + |
| 18 | +( function( mw ) { |
| 19 | + |
| 20 | + |
| 21 | +// Bind apiServer to EmbedPlayerNewPlayer: |
| 22 | +$( mw ).bind( 'EmbedPlayerNewPlayer', function( event, embedPlayer ) { |
| 23 | + // Check if the iFrame player api is enabled and we have a parent iframe url: |
| 24 | + if ( mw.getConfig('EmbedPlayer.EnableIframeApi') |
| 25 | + && |
| 26 | + mw.getConfig( 'EmbedPlayer.IframeParentUrl' ) |
| 27 | + ){ |
| 28 | + embedPlayer['iFrameServer'] = new mw.IFramePlayerApiServer( embedPlayer ); |
| 29 | + } |
| 30 | +}); |
| 31 | + |
| 32 | +mw.IFramePlayerApiServer = function( embedPlayer ){ |
| 33 | + this.init( embedPlayer ); |
| 34 | +} |
| 35 | + |
| 36 | +mw.IFramePlayerApiServer.prototype = { |
| 37 | + // Exported bindings / events. ( all the native html5 events are added in 'init' ) |
| 38 | + 'exportedBindings': [ |
| 39 | + 'playerReady', |
| 40 | + 'monitorEvent', |
| 41 | + 'onOpenFullScreen', |
| 42 | + 'onCloseFullScreen' |
| 43 | + ], |
| 44 | + |
| 45 | + 'init': function( embedPlayer ){ |
| 46 | + this.embedPlayer = embedPlayer; |
| 47 | + |
| 48 | + // Add the list of native events to the exportedBindings |
| 49 | + for( var i =0 ; i < mw.EmbedPlayerNative.nativeEvents.length; i++ ){ |
| 50 | + var bindName = mw.EmbedPlayerNative.nativeEvents[i]; |
| 51 | + // The progress event fires too often for the iframe proxy ( instead use mwEmbed monitorEvent ) |
| 52 | + if( bindName != 'progress' ) { |
| 53 | + this.exportedBindings.push( bindName ); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + // Allow modules to extend the list of iframeExported bindings |
| 58 | + $( mw ).trigger( 'AddIframePlayerBindings', [ this.exportedBindings ]); |
| 59 | + |
| 60 | + this.addIframeListener(); |
| 61 | + this.addIframeSender(); |
| 62 | + $( mw ).trigger( 'newIframePlayerServerSide', [embedPlayer]); |
| 63 | + }, |
| 64 | + |
| 65 | + /** |
| 66 | + * Listens to requested methods and triggers their action |
| 67 | + */ |
| 68 | + 'addIframeListener': function(){ |
| 69 | + var _this = this; |
| 70 | + mw.log('IFramePlayerApiServer::_addIframeListener'); |
| 71 | + $j.receiveMessage( function( event ) { |
| 72 | + _this.hanldeMsg( event ); |
| 73 | + }, this.getParentUrl() ); |
| 74 | + }, |
| 75 | + getParentUrl: function(){ |
| 76 | + var purl = mw.getConfig( 'EmbedPlayer.IframeParentUrl' ); |
| 77 | + if(!purl){ |
| 78 | + mw.log("Error: iFramePlayerApiServer:: could not parse parent url. \n" + |
| 79 | + "Player events will be dissabled"); |
| 80 | + } |
| 81 | + return purl; |
| 82 | + }, |
| 83 | + /** |
| 84 | + * Add iframe sender bindings: |
| 85 | + */ |
| 86 | + 'addIframeSender': function(){ |
| 87 | + var _this = this; |
| 88 | + // Get the parent page URL as it was passed in, for browsers that don't support |
| 89 | + // window.postMessage (this URL could be hard-coded). |
| 90 | + |
| 91 | + // Set the initial attributes once player is "ready" |
| 92 | + $( this.embedPlayer ).bind( 'playerReady', function(){ |
| 93 | + _this.sendPlayerAttributes(); |
| 94 | + }); |
| 95 | + // On monitor event package the attributes for cross domain delivery: |
| 96 | + $( this.embedPlayer ).bind( 'monitorEvent', function(){ |
| 97 | + _this.sendPlayerAttributes(); |
| 98 | + }) |
| 99 | + |
| 100 | + $j.each( this.exportedBindings, function( inx, bindName ){ |
| 101 | + $( _this.embedPlayer ).bind( bindName, function( event ){ |
| 102 | + var argSet = $j.makeArray( arguments ); |
| 103 | + // remove the event from the arg set |
| 104 | + argSet.shift(); |
| 105 | + // protect against a jQuery event getting past as an arguments: |
| 106 | + if( argSet[0] && argSet[0].originalEvent ){ |
| 107 | + argSet.shift(); |
| 108 | + } |
| 109 | + //mw.log("IFramePlayerApiServer::postMessage:: " + bindName + ' arg count:' + argSet.length ); |
| 110 | + _this.postMessage({ |
| 111 | + 'triggerName' : bindName, |
| 112 | + 'triggerArgs' : argSet |
| 113 | + }) |
| 114 | + }); |
| 115 | + }); |
| 116 | + }, |
| 117 | + |
| 118 | + /** |
| 119 | + * Send all the player attributes to the host |
| 120 | + */ |
| 121 | + 'sendPlayerAttributes': function(){ |
| 122 | + var _this = this; |
| 123 | + |
| 124 | + var playerAttributes = mw.getConfig( 'EmbedPlayer.Attributes' ); |
| 125 | + var attrSet = { }; |
| 126 | + for( var i in playerAttributes ){ |
| 127 | + if( i != 'id' ){ |
| 128 | + if( typeof this.embedPlayer[ i ] != 'undefined' ){ |
| 129 | + attrSet[i] = this.embedPlayer[ i ]; |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + //mw.log( "IframePlayerApiServer:: sendPlayerAttributes: " + JSON.stringify( attrSet ) ); |
| 134 | + _this.postMessage( { |
| 135 | + 'attributes' : attrSet |
| 136 | + } ) |
| 137 | + }, |
| 138 | + |
| 139 | + 'postMessage': function( msgObject ){ |
| 140 | + try { |
| 141 | + var messageString = JSON.stringify( msgObject ); |
| 142 | + } catch ( e ){ |
| 143 | + mw.log("Error: could not JSON object: " + msgObject + ' ' + e); |
| 144 | + return ; |
| 145 | + } |
| 146 | + // By default postMessage sends the message to the parent frame: |
| 147 | + $j.postMessage( |
| 148 | + messageString, |
| 149 | + this.getParentUrl(), |
| 150 | + window.parent |
| 151 | + ); |
| 152 | + }, |
| 153 | + |
| 154 | + /** |
| 155 | + * Handle a message event and pass it off to the embedPlayer |
| 156 | + * |
| 157 | + * @param {string} event |
| 158 | + */ |
| 159 | + 'hanldeMsg': function( event ){ |
| 160 | + mw.log( 'IFramePlayerApiServer:: hanldeMsg: ' + event.data ); |
| 161 | + // Check if the server should even be enabled |
| 162 | + if( !mw.getConfig( 'EmbedPlayer.EnableIframeApi' )){ |
| 163 | + mw.log( 'Error: Loading iFrame playerApi but config EmbedPlayer.EnableIframeApi is false'); |
| 164 | + return false; |
| 165 | + } |
| 166 | + |
| 167 | + if( !this.eventDomainCheck( event.origin ) ){ |
| 168 | + mw.log( 'Error: ' + event.origin + ' domain origin not allowed to send player events'); |
| 169 | + return false; |
| 170 | + } |
| 171 | + // Decode the message |
| 172 | + var msgObject = JSON.parse( event.data ); |
| 173 | + |
| 174 | + // Call a method: |
| 175 | + if( msgObject.method && this.embedPlayer[ msgObject.method ] ){ |
| 176 | + this.embedPlayer[ msgObject.method ].apply( this.embedPlayer, $j.makeArray( msgObject.args ) ); |
| 177 | + } |
| 178 | + // Update a attribute |
| 179 | + if( typeof msgObject.attrName != 'undefined' && typeof msgObject.attrValue != 'undefined' ){ |
| 180 | + try{ |
| 181 | + $( this.embedPlayer ).attr( msgObject.attrName, msgObject.attrValue) |
| 182 | + } catch(e){ |
| 183 | + // possible error cant set attribute msgObject.attrName |
| 184 | + } |
| 185 | + } |
| 186 | + }, |
| 187 | + |
| 188 | + /** |
| 189 | + * Check an origin domain against the configuration value: 'EmbedPLayer.IFramePlayer.DomainWhiteList' |
| 190 | + * Returns true if the origin domain is allowed to communicate with the embedPlayer |
| 191 | + * otherwise returns false. |
| 192 | + * |
| 193 | + * @parma {string} origin |
| 194 | + * The origin domain to be checked |
| 195 | + */ |
| 196 | + 'eventDomainCheck': function( origin ){ |
| 197 | + if( mw.getConfig( 'EmbedPLayer.IFramePlayer.DomainWhiteList' ) ){ |
| 198 | + // NOTE this is very similar to the apiProxy function: |
| 199 | + var domainWhiteList = mw.getConfig('EmbedPLayer.IFramePlayer.DomainWhiteList'); |
| 200 | + if( domainWhiteList == '*' ){ |
| 201 | + // The default very permissive state |
| 202 | + return true; |
| 203 | + } |
| 204 | + // @@FIXME we should also check protocol to avoid |
| 205 | + // http vs https |
| 206 | + var originDomain = mw.parseUri( origin ).host; |
| 207 | + |
| 208 | + // Check the domains: |
| 209 | + for ( var i =0; i < domainWhiteList.length; i++ ) { |
| 210 | + whiteDomain = domainWhiteList[i]; |
| 211 | + // Check if domain check is a RegEx: |
| 212 | + if( typeof whiteDomain == 'object' ){ |
| 213 | + if( originDomain.match( whiteDomain ) ) { |
| 214 | + return true; |
| 215 | + } |
| 216 | + } else { |
| 217 | + if( originDomain == whiteDomain ){ |
| 218 | + return true; |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + // If no passing domain was found return false |
| 224 | + return false; |
| 225 | + } |
| 226 | +}; |
| 227 | + |
| 228 | +} )( window.mw ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/iframeApi/mw.IFramePlayerApiServer.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 229 | + text/plain |
Added: svn:eol-style |
2 | 230 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerNative.js |
— | — | @@ -0,0 +1,697 @@ |
| 2 | +/** |
| 3 | +* Native embed library: |
| 4 | +* |
| 5 | +* Enables embedPlayer support for native html5 browser playback system |
| 6 | +*/ |
| 7 | +( function( mw, $ ) { |
| 8 | + |
| 9 | +mw.EmbedPlayerNative = { |
| 10 | + |
| 11 | + //Instance Name |
| 12 | + instanceOf: 'Native', |
| 13 | + |
| 14 | + // Flag to only load the video ( not play it ) |
| 15 | + onlyLoadFlag:false, |
| 16 | + |
| 17 | + //Callback fired once video is "loaded" |
| 18 | + onLoadedCallback: null, |
| 19 | + |
| 20 | + // The previous "currentTime" to sniff seek actions |
| 21 | + // NOTE the bug where onSeeked does not seem fire consistently may no longer be applicable |
| 22 | + prevCurrentTime: -1, |
| 23 | + |
| 24 | + // Store the progress event ( updated during monitor ) |
| 25 | + progressEventData: null, |
| 26 | + |
| 27 | + // If the media loaded event has been fired |
| 28 | + mediaLoadedFlag: null, |
| 29 | + |
| 30 | + // All the native events per: |
| 31 | + // http://www.w3.org/TR/html5/video.html#mediaevents |
| 32 | + nativeEvents : [ |
| 33 | + 'loadstart', |
| 34 | + 'progress', |
| 35 | + 'suspend', |
| 36 | + 'abort', |
| 37 | + 'error', |
| 38 | + 'emptied', |
| 39 | + 'stalled', |
| 40 | + 'play', |
| 41 | + 'pause', |
| 42 | + 'loadedmetadata', |
| 43 | + 'loadeddata', |
| 44 | + 'waiting', |
| 45 | + 'playing', |
| 46 | + 'canplay', |
| 47 | + 'canplaythough', |
| 48 | + 'seeking', |
| 49 | + 'seeked', |
| 50 | + 'timeupdate', |
| 51 | + 'ended', |
| 52 | + 'ratechange', |
| 53 | + 'durationchange', |
| 54 | + 'volumechange' |
| 55 | + ], |
| 56 | + |
| 57 | + // Native player supported feature set |
| 58 | + supports: { |
| 59 | + 'playHead' : true, |
| 60 | + 'sourceSwitch': true, |
| 61 | + 'pause' : true, |
| 62 | + 'fullscreen' : true, |
| 63 | + 'timeDisplay' : true, |
| 64 | + 'volumeControl' : true, |
| 65 | + 'overlays' : true |
| 66 | + }, |
| 67 | + |
| 68 | + /** |
| 69 | + * Updates the supported features given the "type of player" |
| 70 | + */ |
| 71 | + updateFeatureSupport: function(){ |
| 72 | + // The native controls function checks for overly support |
| 73 | + // especially the special case of iPad in-dom or not support |
| 74 | + if( this.useNativePlayerControls() ) { |
| 75 | + this.supports.overlays = false; |
| 76 | + this.supports.volumeControl = false; |
| 77 | + } |
| 78 | + // iOS does not support volume control ( only iPad can have controls ) |
| 79 | + if( mw.isIpad() ){ |
| 80 | + this.supports.volumeControl = false; |
| 81 | + } |
| 82 | + this.parent_updateFeatureSupport(); |
| 83 | + }, |
| 84 | + |
| 85 | + /** |
| 86 | + * Return the embed code |
| 87 | + */ |
| 88 | + doEmbedHTML : function () { |
| 89 | + var _this = this; |
| 90 | + |
| 91 | + // Reset some play state flags: |
| 92 | + _this.bufferStartFlag = false; |
| 93 | + _this.bufferEndFlag = false; |
| 94 | + |
| 95 | + mw.log( "EmbedPlayerNative:: play url:" + this.getSrc( this.currentTime ) + ' startOffset: ' + this.start_ntp + ' end: ' + this.end_ntp ); |
| 96 | + |
| 97 | + // Check if using native controls and already the "pid" is already in the DOM |
| 98 | + if( ( this.useNativePlayerControls() |
| 99 | + || |
| 100 | + this.isPersistentNativePlayer() |
| 101 | + ) |
| 102 | + && $( '#' + this.pid ).length |
| 103 | + && typeof $( '#' + this.pid ).get(0).play != 'undefined' ) { |
| 104 | + |
| 105 | + // Update the player source: |
| 106 | + $( '#' + this.pid ).attr( 'src', this.getSrc( this.currentTime ) ); |
| 107 | + $( '#' + this.pid ).get(0).load(); |
| 108 | + |
| 109 | + _this.postEmbedJS(); |
| 110 | + return ; |
| 111 | + } |
| 112 | + |
| 113 | + $( this ).html( |
| 114 | + _this.getNativePlayerHtml() |
| 115 | + ); |
| 116 | + |
| 117 | + // Directly run postEmbedJS ( if playerElement is not available it will retry ) |
| 118 | + _this.postEmbedJS(); |
| 119 | + }, |
| 120 | + |
| 121 | + /** |
| 122 | + * Get the native player embed code. |
| 123 | + * |
| 124 | + * @param {object} playerAttribtues Attributes to be override in function call |
| 125 | + * @return {object} cssSet css to apply to the player |
| 126 | + */ |
| 127 | + getNativePlayerHtml: function( playerAttribtues, cssSet ){ |
| 128 | + if( !playerAttribtues) { |
| 129 | + playerAttribtues = {}; |
| 130 | + } |
| 131 | + // Update required attributes |
| 132 | + if( !playerAttribtues['id'] ) playerAttribtues['id'] = this.pid; |
| 133 | + if( !playerAttribtues['src'] ) playerAttribtues['src'] = this.getSrc( this.currentTime); |
| 134 | + |
| 135 | + // If autoplay pass along to attribute ( needed for iPad / iPod no js autoplay support |
| 136 | + if( this.autoplay ) { |
| 137 | + playerAttribtues['autoplay'] = 'true'; |
| 138 | + } |
| 139 | + |
| 140 | + if( !cssSet ){ |
| 141 | + cssSet = {}; |
| 142 | + } |
| 143 | + // Set default width height to 100% of parent container |
| 144 | + if( !cssSet['width'] ) cssSet['width'] = '100%'; |
| 145 | + if( !cssSet['height'] ) cssSet['height'] = '100%'; |
| 146 | + |
| 147 | + // Also need to set the loop param directly for iPad / iPod |
| 148 | + if( this.loop ) { |
| 149 | + playerAttribtues['loop'] = 'true'; |
| 150 | + } |
| 151 | + |
| 152 | + var tagName = ( this.isAudio() ) ? 'audio' : 'video'; |
| 153 | + |
| 154 | + return $( '<' + tagName + ' />' ) |
| 155 | + // Add the special nativeEmbedPlayer to avoid any rewrites of of this video tag. |
| 156 | + .addClass( 'nativeEmbedPlayerPid' ) |
| 157 | + .attr( playerAttribtues ) |
| 158 | + .css( cssSet ) |
| 159 | + }, |
| 160 | + |
| 161 | + /** |
| 162 | + * Post element javascript, binds event listeners and starts monitor |
| 163 | + */ |
| 164 | + postEmbedJS: function() { |
| 165 | + var _this = this; |
| 166 | + mw.log( "f:native:postEmbedJS:" ); |
| 167 | + |
| 168 | + // Setup local pointer: |
| 169 | + var vid = this.getPlayerElement(); |
| 170 | + // Apply media element bindings: |
| 171 | + this.applyMediaElementBindings(); |
| 172 | + |
| 173 | + // Check for load flag |
| 174 | + if ( this.onlyLoadFlag ) { |
| 175 | + vid.pause(); |
| 176 | + vid.load(); |
| 177 | + } else { |
| 178 | + // Issue play request |
| 179 | + vid.play(); |
| 180 | + } |
| 181 | + |
| 182 | + setTimeout( function() { |
| 183 | + _this.monitor(); |
| 184 | + }, 100 ); |
| 185 | + }, |
| 186 | + |
| 187 | + /** |
| 188 | + * Apply media element bindings |
| 189 | + */ |
| 190 | + applyMediaElementBindings: function(){ |
| 191 | + var _this = this; |
| 192 | + var vid = this.getPlayerElement(); |
| 193 | + if( ! vid ){ |
| 194 | + mw.log( " Error: applyMediaElementBindings without player elemnet"); |
| 195 | + return ; |
| 196 | + } |
| 197 | + $j.each( _this.nativeEvents, function( inx, eventName ){ |
| 198 | + $( vid ).bind( eventName , function(){ |
| 199 | + if( _this._propagateEvents ){ |
| 200 | + var argArray = $j.makeArray( arguments ); |
| 201 | + // Check if there is local handler: |
| 202 | + if( _this['on' + eventName ] ){ |
| 203 | + _this['on' + eventName ].apply( _this, argArray); |
| 204 | + } else { |
| 205 | + // No local handler directly propagate the event to the abstract object: |
| 206 | + $( _this ).trigger( eventName, argArray ); |
| 207 | + } |
| 208 | + } |
| 209 | + }) |
| 210 | + }); |
| 211 | + }, |
| 212 | + |
| 213 | + // basic monitor function to update buffer |
| 214 | + monitor: function(){ |
| 215 | + var _this = this; |
| 216 | + var vid = _this.getPlayerElement(); |
| 217 | + |
| 218 | + // Update duration |
| 219 | + if( vid && vid.duration ){ |
| 220 | + this.duration = vid.duration; |
| 221 | + } |
| 222 | + // Update the bufferedPercent |
| 223 | + if( vid && vid.buffered && vid.buffered.end && vid.duration ) { |
| 224 | + this.bufferedPercent = ( vid.buffered.end(0) / vid.duration ); |
| 225 | + } |
| 226 | + _this.parent_monitor(); |
| 227 | + }, |
| 228 | + |
| 229 | + |
| 230 | + /** |
| 231 | + * Issue a seeking request. |
| 232 | + * |
| 233 | + * @param {Float} percentage |
| 234 | + */ |
| 235 | + doSeek: function( percentage ) { |
| 236 | + mw.log( 'Native::doSeek p: ' + percentage + ' : ' + this.supportsURLTimeEncoding() + ' dur: ' + this.getDuration() + ' sts:' + this.seek_time_sec ); |
| 237 | + this.seeking = true; |
| 238 | + |
| 239 | + // Run the onSeeking interface update |
| 240 | + this.controlBuilder.onSeek(); |
| 241 | + |
| 242 | + // @@todo check if the clip is loaded here (if so we can do a local seek) |
| 243 | + if ( this.supportsURLTimeEncoding() ) { |
| 244 | + // Make sure we could not do a local seek instead: |
| 245 | + if ( percentage < this.bufferedPercent && this.playerElement.duration && !this.didSeekJump ) { |
| 246 | + mw.log( "do local seek " + percentage + ' is already buffered < ' + this.bufferedPercent ); |
| 247 | + this.doNativeSeek( percentage ); |
| 248 | + } else { |
| 249 | + // We support URLTimeEncoding call parent seek: |
| 250 | + this.parent_doSeek( percentage ); |
| 251 | + } |
| 252 | + } else if ( this.playerElement && this.playerElement.duration ) { |
| 253 | + // (could also check bufferedPercent > percentage seek (and issue oggz_chop request or not) |
| 254 | + this.doNativeSeek( percentage ); |
| 255 | + } else { |
| 256 | + // try to do a play then seek: |
| 257 | + this.doPlayThenSeek( percentage ) |
| 258 | + } |
| 259 | + }, |
| 260 | + |
| 261 | + /** |
| 262 | + * Do a native seek by updating the currentTime |
| 263 | + * @param {float} percentage |
| 264 | + * Percent to seek to of full time |
| 265 | + */ |
| 266 | + doNativeSeek: function( percentage ) { |
| 267 | + var _this = this; |
| 268 | + mw.log( 'native::doNativeSeek::' + percentage ); |
| 269 | + this.seeking = true; |
| 270 | + this.seek_time_sec = 0; |
| 271 | + this.setCurrentTime( ( percentage * this.duration ) , function(){ |
| 272 | + _this.seeking = false; |
| 273 | + _this.monitor(); |
| 274 | + }) |
| 275 | + }, |
| 276 | + |
| 277 | + /** |
| 278 | + * Seek in a existing stream |
| 279 | + * |
| 280 | + * @param {Float} percentage |
| 281 | + * Percentage of the stream to seek to between 0 and 1 |
| 282 | + */ |
| 283 | + doPlayThenSeek: function( percentage ) { |
| 284 | + mw.log( 'native::doPlayThenSeek::' ); |
| 285 | + var _this = this; |
| 286 | + this.play(); |
| 287 | + var retryCount = 0; |
| 288 | + var readyForSeek = function() { |
| 289 | + _this.getPlayerElement(); |
| 290 | + // If we have duration then we are ready to do the seek |
| 291 | + if ( _this.playerElement && _this.playerElement.duration ) { |
| 292 | + _this.doNativeSeek( percentage ); |
| 293 | + } else { |
| 294 | + // Try to get player for 40 seconds: |
| 295 | + // (it would be nice if the onmetadata type callbacks where fired consistently) |
| 296 | + if ( retryCount < 800 ) { |
| 297 | + setTimeout( readyForSeek, 50 ); |
| 298 | + retryCount++; |
| 299 | + } else { |
| 300 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 301 | + } |
| 302 | + } |
| 303 | + } |
| 304 | + readyForSeek(); |
| 305 | + }, |
| 306 | + |
| 307 | + /** |
| 308 | + * Set the current time with a callback |
| 309 | + * |
| 310 | + * @param {Float} position |
| 311 | + * Seconds to set the time to |
| 312 | + * @param {Function} callback |
| 313 | + * Function called once time has been set. |
| 314 | + */ |
| 315 | + setCurrentTime: function( time , callback, callbackCount ) { |
| 316 | + var _this = this; |
| 317 | + if( !callbackCount ) |
| 318 | + callbackCount = 0; |
| 319 | + this.getPlayerElement(); |
| 320 | + if( _this.playerElement.readyState >= 1 ){ |
| 321 | + if( _this.playerElement.currentTime == time ){ |
| 322 | + callback(); |
| 323 | + return; |
| 324 | + } |
| 325 | + var once = function( event ) { |
| 326 | + if( callback ){ |
| 327 | + callback(); |
| 328 | + } |
| 329 | + _this.playerElement.removeEventListener( 'seeked', once, false ); |
| 330 | + }; |
| 331 | + // Assume we will get to add the Listener before the seek is done |
| 332 | + _this.playerElement.addEventListener( 'seeked', once, false ); |
| 333 | + try { |
| 334 | + _this.playerElement.currentTime = time; |
| 335 | + } catch (e) { |
| 336 | + mw.log("Could not seek to this point. Unbuffered point."); |
| 337 | + callback(); |
| 338 | + return; |
| 339 | + } |
| 340 | + } else { |
| 341 | + if( callbackCount >= 300 ){ |
| 342 | + mw.log("Error with seek request, media never in ready state"); |
| 343 | + return ; |
| 344 | + } |
| 345 | + setTimeout( function(){ |
| 346 | + _this.setCurrentTime( time, callback , callbackCount++); |
| 347 | + }, 10 ); |
| 348 | + } |
| 349 | + }, |
| 350 | + |
| 351 | + /** |
| 352 | + * Get the embed player time |
| 353 | + */ |
| 354 | + getPlayerElementTime: function() { |
| 355 | + var _this = this; |
| 356 | + // Make sure we have .vid obj |
| 357 | + this.getPlayerElement(); |
| 358 | + |
| 359 | + if ( !this.playerElement ) { |
| 360 | + mw.log( 'mwEmbedPlayer::getPlayerElementTime: ' + this.id + ' not in dom ( stop monitor)' ); |
| 361 | + return false; |
| 362 | + } |
| 363 | + // Return the playerElement currentTime |
| 364 | + return this.playerElement.currentTime; |
| 365 | + }, |
| 366 | + |
| 367 | + // Update the poster src ( updates the native object if in dom ) |
| 368 | + updatePosterSrc: function( src ){ |
| 369 | + if( this.getPlayerElement() ){ |
| 370 | + this.getPlayerElement().poster = src; |
| 371 | + } |
| 372 | + // Also update the embedPlayer poster |
| 373 | + this.parent_updatePosterSrc( src ); |
| 374 | + }, |
| 375 | + |
| 376 | + /** |
| 377 | + * switchPlaySrc switches the player source working around a few bugs in browsers |
| 378 | + * |
| 379 | + * @param {string} |
| 380 | + * src Video url Source to switch to. |
| 381 | + * @param {function} |
| 382 | + * switchCallback Function to call once the source has been switched |
| 383 | + * @param {function} |
| 384 | + * doneCallback Function to call once the clip has completed playback |
| 385 | + */ |
| 386 | + switchPlaySrc: function( src, switchCallback, doneCallback ){ |
| 387 | + var _this = this; |
| 388 | + mw.log( 'EmbedPlayerNative:: switchPlaySrc:' + src + ' native time: ' + this.getPlayerElement().currentTime ); |
| 389 | + // Update some parent embedPlayer vars: |
| 390 | + this.duration = 0; |
| 391 | + this.currentTime = 0; |
| 392 | + this.previousTime = 0; |
| 393 | + var vid = this.getPlayerElement(); |
| 394 | + if ( vid ) { |
| 395 | + try { |
| 396 | + // issue a play request on the source |
| 397 | + vid.play(); |
| 398 | + setTimeout(function(){ |
| 399 | + // Remove all native player bindings |
| 400 | + $(vid).unbind(); |
| 401 | + vid.pause(); |
| 402 | + var orginalControlsState = vid.controls; |
| 403 | + // Hide controls ( to not display native play button while switching sources ) |
| 404 | + vid.removeAttribute('controls'); |
| 405 | + |
| 406 | + // Local scope update source and play function to work around google chrome bug |
| 407 | + var updateSrcAndPlay = function() { |
| 408 | + var vid = _this.getPlayerElement(); |
| 409 | + if (!vid){ |
| 410 | + mw.log( 'Error: switchPlaySrc no vid'); |
| 411 | + return ; |
| 412 | + } |
| 413 | + vid.src = src; |
| 414 | + // Give iOS 50ms to figure out the src got updated ( iPad OS 4.0 ) |
| 415 | + setTimeout(function() { |
| 416 | + var vid = _this.getPlayerElement(); |
| 417 | + if (!vid){ |
| 418 | + mw.log( 'Error: switchPlaySrc no vid'); |
| 419 | + return ; |
| 420 | + } |
| 421 | + vid.load(); |
| 422 | + vid.play(); |
| 423 | + // Wait another 100ms then bind the end event and any custom events |
| 424 | + // for the switchCallback |
| 425 | + setTimeout(function() { |
| 426 | + var vid = _this.getPlayerElement(); |
| 427 | + // restore controls |
| 428 | + vid.controls = orginalControlsState; |
| 429 | + // add the end binding: |
| 430 | + $(vid).bind('ended', function( event ) { |
| 431 | + if(typeof doneCallback == 'function' ){ |
| 432 | + doneCallback(); |
| 433 | + } |
| 434 | + return false; |
| 435 | + }); |
| 436 | + if (typeof switchCallback == 'function') { |
| 437 | + switchCallback(vid); |
| 438 | + } |
| 439 | + }, 100); |
| 440 | + }, 100); |
| 441 | + }; |
| 442 | + if (navigator.userAgent.toLowerCase().indexOf('chrome') != -1) { |
| 443 | + // Null the src and wait 50ms ( helps unload video without crashing |
| 444 | + // google chrome 7.x ) |
| 445 | + vid.src = ''; |
| 446 | + setTimeout(updateSrcAndPlay, 100); |
| 447 | + } else { |
| 448 | + updateSrcAndPlay(); |
| 449 | + } |
| 450 | + }, 100 ); |
| 451 | + } catch (e) { |
| 452 | + mw.log("Error: Error in swiching source playback"); |
| 453 | + } |
| 454 | + } |
| 455 | + }, |
| 456 | + |
| 457 | + /** |
| 458 | + * Pause the video playback |
| 459 | + * calls parent_pause to update the interface |
| 460 | + */ |
| 461 | + pause: function( ) { |
| 462 | + this.getPlayerElement(); |
| 463 | + this.parent_pause(); // update interface |
| 464 | + if ( this.playerElement ) { // update player |
| 465 | + if( !this.playerElement.paused ){ |
| 466 | + this.playerElement.pause(); |
| 467 | + } |
| 468 | + } |
| 469 | + }, |
| 470 | + |
| 471 | + /** |
| 472 | + * Play back the video stream |
| 473 | + * calls parent_play to update the interface |
| 474 | + */ |
| 475 | + play: function( ) { |
| 476 | + this.getPlayerElement(); |
| 477 | + this.parent_play(); // update interface |
| 478 | + if ( this.playerElement && this.playerElement.play ) { |
| 479 | + // issue a play request if the media is paused: |
| 480 | + if( this.playerElement.paused ){ |
| 481 | + this.playerElement.play(); |
| 482 | + } |
| 483 | + // re-start the monitor: |
| 484 | + this.monitor(); |
| 485 | + } |
| 486 | + }, |
| 487 | + /** |
| 488 | + * Stop the player ( end all listeners ) |
| 489 | + */ |
| 490 | + stop:function(){ |
| 491 | + if( this.playerElement ){ |
| 492 | + $( this.playerElement ).unbind(); |
| 493 | + } |
| 494 | + this.parent_stop(); |
| 495 | + }, |
| 496 | + |
| 497 | + /** |
| 498 | + * Toggle the Mute |
| 499 | + * calls parent_toggleMute to update the interface |
| 500 | + */ |
| 501 | + toggleMute: function() { |
| 502 | + this.parent_toggleMute(); |
| 503 | + this.getPlayerElement(); |
| 504 | + if ( this.playerElement ) |
| 505 | + this.playerElement.muted = this.muted; |
| 506 | + }, |
| 507 | + |
| 508 | + /** |
| 509 | + * Update Volume |
| 510 | + * |
| 511 | + * @param {Float} percentage Value between 0 and 1 to set audio volume |
| 512 | + */ |
| 513 | + setPlayerElementVolume : function( percentage ) { |
| 514 | + if ( this.getPlayerElement() ) { |
| 515 | + // Disable mute if positive volume |
| 516 | + if( percentage != 0 ) { |
| 517 | + this.playerElement.muted = false; |
| 518 | + } |
| 519 | + this.playerElement.volume = percentage; |
| 520 | + } |
| 521 | + }, |
| 522 | + |
| 523 | + /** |
| 524 | + * get Volume |
| 525 | + * |
| 526 | + * @return {Float} |
| 527 | + * Audio volume between 0 and 1. |
| 528 | + */ |
| 529 | + getPlayerElementVolume: function() { |
| 530 | + if ( this.getPlayerElement() ) { |
| 531 | + return this.playerElement.volume; |
| 532 | + } |
| 533 | + }, |
| 534 | + /** |
| 535 | + * get the native muted state |
| 536 | + */ |
| 537 | + getPlayerElementMuted: function(){ |
| 538 | + if ( this.getPlayerElement() ) { |
| 539 | + return this.playerElement.muted; |
| 540 | + } |
| 541 | + }, |
| 542 | + |
| 543 | + /** |
| 544 | + * Get the native media duration |
| 545 | + */ |
| 546 | + getNativeDuration: function() { |
| 547 | + if ( this.playerElement ) { |
| 548 | + return this.playerElement.duration; |
| 549 | + } |
| 550 | + }, |
| 551 | + |
| 552 | + /** |
| 553 | + * load the video stream with a callback fired once the video is "loaded" |
| 554 | + * |
| 555 | + * @parma {Function} callbcak Function called once video is loaded |
| 556 | + */ |
| 557 | + load: function( callback ) { |
| 558 | + this.getPlayerElement(); |
| 559 | + if ( !this.playerElement ) { |
| 560 | + // No vid loaded |
| 561 | + mw.log( 'native::load() ... doEmbed' ); |
| 562 | + this.onlyLoadFlag = true; |
| 563 | + this.doEmbedHTML(); |
| 564 | + this.onLoadedCallback = callback; |
| 565 | + } else { |
| 566 | + // Should not happen offten |
| 567 | + this.playerElement.load(); |
| 568 | + if( callback) |
| 569 | + callback(); |
| 570 | + } |
| 571 | + }, |
| 572 | + |
| 573 | + /** |
| 574 | + * Get /update the playerElement value |
| 575 | + */ |
| 576 | + getPlayerElement: function () { |
| 577 | + this.playerElement = $( '#' + this.pid ).get( 0 ); |
| 578 | + return this.playerElement; |
| 579 | + }, |
| 580 | + |
| 581 | + /** |
| 582 | + * Bindings for the Video Element Events |
| 583 | + */ |
| 584 | + |
| 585 | + /** |
| 586 | + * Local method for seeking event |
| 587 | + * fired when "seeking" |
| 588 | + */ |
| 589 | + onseeking: function() { |
| 590 | + mw.log( "native:onSeeking"); |
| 591 | + // Trigger the html5 seeking event |
| 592 | + //( if not already set from interface ) |
| 593 | + if( !this.seeking ) { |
| 594 | + this.seeking = true; |
| 595 | + |
| 596 | + // Run the onSeeking interface update |
| 597 | + this.controlBuilder.onSeek(); |
| 598 | + |
| 599 | + // Trigger the html5 "seeking" trigger |
| 600 | + mw.log("native:seeking:trigger:: " + this.seeking); |
| 601 | + $( this ).trigger( 'seeking' ); |
| 602 | + } |
| 603 | + }, |
| 604 | + |
| 605 | + /** |
| 606 | + * Local method for seeked event |
| 607 | + * fired when done seeking |
| 608 | + */ |
| 609 | + onseeked: function() { |
| 610 | + mw.log("native:onSeeked"); |
| 611 | + // Trigger the html5 action on the parent |
| 612 | + if( this.seeking ){ |
| 613 | + this.seeking = false; |
| 614 | + $( this ).trigger( 'seeked' ); |
| 615 | + } |
| 616 | + this.seeking = false; |
| 617 | + }, |
| 618 | + |
| 619 | + /** |
| 620 | + * Handle the native paused event |
| 621 | + */ |
| 622 | + onpause: function(){ |
| 623 | + mw.log( "EmbedPlayer:native: OnPaused:: " + this._propagateEvents ); |
| 624 | + if( this._propagateEvents ){ |
| 625 | + this.parent_pause(); |
| 626 | + } |
| 627 | + }, |
| 628 | + |
| 629 | + /** |
| 630 | + * Handle the native play event |
| 631 | + */ |
| 632 | + onplay: function(){ |
| 633 | + mw.log("EmbedPlayer:native:: OnPlay::" + this._propagateEvents ); |
| 634 | + // Update the interface ( if paused ) |
| 635 | + if( this._propagateEvents ){ |
| 636 | + this.parent_play(); |
| 637 | + } |
| 638 | + }, |
| 639 | + |
| 640 | + /** |
| 641 | + * Local method for metadata ready |
| 642 | + * fired when metadata becomes available |
| 643 | + * |
| 644 | + * Used to update the media duration to |
| 645 | + * accurately reflect the src duration |
| 646 | + */ |
| 647 | + onloadedmetadata: function() { |
| 648 | + this.getPlayerElement(); |
| 649 | + if ( this.playerElement && ! isNaN( this.playerElement.duration ) ) { |
| 650 | + mw.log( 'f:onloadedmetadata metadata ready Update duration:' + this.playerElement.duration + ' old dur: ' + this.getDuration() ); |
| 651 | + this.duration = this.playerElement.duration; |
| 652 | + } |
| 653 | + |
| 654 | + //Fire "onLoaded" flags if set |
| 655 | + if( typeof this.onLoadedCallback == 'function' ) { |
| 656 | + this.onLoadedCallback(); |
| 657 | + } |
| 658 | + |
| 659 | + // Trigger "media loaded" |
| 660 | + if( ! this.mediaLoadedFlag ){ |
| 661 | + $( this ).trigger( 'mediaLoaded' ); |
| 662 | + this.mediaLoadedFlag = true; |
| 663 | + } |
| 664 | + }, |
| 665 | + |
| 666 | + /** |
| 667 | + * Local method for progress event |
| 668 | + * fired as the video is downloaded / buffered |
| 669 | + * |
| 670 | + * Used to update the bufferedPercent |
| 671 | + * |
| 672 | + * Note: this way of updating buffer was only supported in Firefox 3.x and |
| 673 | + * not supported in Firefox 4.x |
| 674 | + */ |
| 675 | + onprogress: function( event ) { |
| 676 | + var e = event.originalEvent; |
| 677 | + if( e && e.loaded && e.total ) { |
| 678 | + this.bufferedPercent = e.loaded / e.total; |
| 679 | + this.progressEventData = e.loaded; |
| 680 | + } |
| 681 | + }, |
| 682 | + |
| 683 | + /** |
| 684 | + * Local method for progress event |
| 685 | + * fired as the video is downloaded / buffered |
| 686 | + * |
| 687 | + * Used to update the bufferedPercent |
| 688 | + */ |
| 689 | + onended: function() { |
| 690 | + var _this = this; |
| 691 | + mw.log( 'EmbedPlayer:native: onended:' + this.playerElement.currentTime + ' real dur:' + this.getDuration() + ' ended ' + this._propagateEvents ); |
| 692 | + if( this._propagateEvents ){ |
| 693 | + this.onClipDone(); |
| 694 | + } |
| 695 | + } |
| 696 | +}; |
| 697 | + |
| 698 | +} )( window.mediaWiki, window.jQuery ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerNative.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 699 | + text/plain |
Added: svn:eol-style |
2 | 700 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerKplayer.js |
— | — | @@ -0,0 +1,1252 @@ |
| 2 | +/* |
| 3 | + * The "kaltura player" embedPlayer interface for fallback h.264 and flv video format support |
| 4 | + */ |
| 5 | +// Called from the kdp.swf |
| 6 | +( function( mw, $ ) { |
| 7 | + |
| 8 | +function jsInterfaceReadyFunc() { |
| 9 | + return true; |
| 10 | +} |
| 11 | + |
| 12 | +mw.EmbedPlayerKplayer = { |
| 13 | + |
| 14 | + // Instance name: |
| 15 | + instanceOf : 'Kplayer', |
| 16 | + |
| 17 | + // List of supported features: |
| 18 | + supports : { |
| 19 | + 'playHead' : true, |
| 20 | + 'pause' : true, |
| 21 | + 'stop' : true, |
| 22 | + 'timeDisplay' : true, |
| 23 | + 'volumeControl' : true, |
| 24 | + 'overlays' : true, |
| 25 | + 'fullscreen' : true |
| 26 | + }, |
| 27 | + |
| 28 | + // Stores the current time as set from flash |
| 29 | + flashCurrentTime : 0, |
| 30 | + |
| 31 | + /* |
| 32 | + * Write the Embed html to the target |
| 33 | + */ |
| 34 | + doEmbedHTML : function() { |
| 35 | + var _this = this; |
| 36 | + |
| 37 | + mw.log("kPlayer:: embed src::" + _this.getSrc()); |
| 38 | + var flashvars = {}; |
| 39 | + flashvars.autoPlay = "true"; |
| 40 | + var playerPath = mw.getMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/kaltura-player'; |
| 41 | + flashvars.entryId = mw.absoluteUrl(_this.getSrc()); |
| 42 | + |
| 43 | + // Use a relative url if the protocal is file:// |
| 44 | + if (mw.parseUri(document.URL).protocol == 'file') { |
| 45 | + playerPath = mw.getRelativeMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/kaltura-player'; |
| 46 | + flashvars.entryId = _this.getSrc(); |
| 47 | + } |
| 48 | + |
| 49 | + flashvars.debugMode = "true"; |
| 50 | + flashvars.fileSystemMode = "true"; |
| 51 | + flashvars.widgetId = "_7463"; |
| 52 | + flashvars.partnerId = "7463"; |
| 53 | + flashvars.pluginDomain = "kdp3/plugins/"; |
| 54 | + flashvars.kml = "local"; |
| 55 | + flashvars.kmlPath = playerPath + '/config.xml'; |
| 56 | + flashvars.sourceType = "url"; |
| 57 | + |
| 58 | + // flashvars.host = "www.kaltura.com"; |
| 59 | + flashvars.externalInterfaceDisabled = 'false'; |
| 60 | + flashvars.skinPath = playerPath + '/skin.swf'; |
| 61 | + |
| 62 | + flashvars["full.skinPath"] = playerPath + '/LightDoodleskin.swf'; |
| 63 | + |
| 64 | + var params = {}; |
| 65 | + params.quality = "best"; |
| 66 | + params.wmode = "opaque"; |
| 67 | + params.allowfullscreen = "true"; |
| 68 | + params.allowscriptaccess = "always"; |
| 69 | + |
| 70 | + var attributes = {}; |
| 71 | + attributes.id = this.pid; |
| 72 | + attributes.name = this.pid; |
| 73 | + |
| 74 | + mw.log(" about to add the pid container"); |
| 75 | + $(this).html($('<div />').attr('id', this.pid + '_container')); |
| 76 | + // Call swm dom loaded function: |
| 77 | + swfobject.callDomLoadFunctions(); |
| 78 | + // Do the flash embedding with embedSWF |
| 79 | + swfobject.embedSWF(playerPath + "/kdp3.swf", this.pid + '_container', |
| 80 | + '100%', '100%', "10.0.0", playerPath + "/expressInstall.swf", |
| 81 | + flashvars, params, attributes); |
| 82 | + |
| 83 | + // Direct object embed |
| 84 | + /* |
| 85 | + * $( this ).html( '<object |
| 86 | + * classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="780" |
| 87 | + * height="420">'+ '<param name="movie" value="myContent.swf" />'+ '<!--[if |
| 88 | + * !IE]>-->'+ '<object type="application/x-shockwave-flash" |
| 89 | + * data="myContent.swf" width="780" height="420">'+ '<!--<![endif]-->'+ '<p> |
| 90 | + * error with flash embed</p>' '<!--[if !IE]>-->'+ '</object>'+ '<!--<![endif]-->'+ '</object>' ) |
| 91 | + */ |
| 92 | + |
| 93 | + setTimeout(function() { |
| 94 | + _this.postEmbedJS(); |
| 95 | + }, 100); |
| 96 | + |
| 97 | + // Flash player loses its bindings once it changes sizes:: |
| 98 | + $(_this).bind('onOpenFullScreen', function() { |
| 99 | + _this.postEmbedJS(); |
| 100 | + }); |
| 101 | + $(_this).bind('onCloseFullScreen', function() { |
| 102 | + _this.postEmbedJS(); |
| 103 | + }); |
| 104 | + }, |
| 105 | + |
| 106 | + /** |
| 107 | + * javascript run post player embedding |
| 108 | + */ |
| 109 | + postEmbedJS : function() { |
| 110 | + var _this = this; |
| 111 | + this.getPlayerElement(); |
| 112 | + |
| 113 | + var bindEventMap = { |
| 114 | + 'doPause' : 'onPause', |
| 115 | + 'doPlay' : 'onPlay', |
| 116 | + 'durationChange' : 'onDurationChange', |
| 117 | + 'playerPlayEnd' : 'onClipDone', |
| 118 | + 'playerUpdatePlayhead' : 'onUpdatePlayhead', |
| 119 | + 'bytesTotalChange' : 'onBytesTotalChange', |
| 120 | + 'bytesDownloadedChange' : 'onBytesDownloadedChange' |
| 121 | + }; |
| 122 | + |
| 123 | + if (this.playerElement && this.playerElement.addJsListener) { |
| 124 | + $j.each( bindEventMap, function( bindName, localMethod ) { |
| 125 | + _this.bindPlayerFunction(bindName, localMethod); |
| 126 | + } ); |
| 127 | + // Start the monitor |
| 128 | + this.monitor(); |
| 129 | + } else { |
| 130 | + // Keep trying to get the player element |
| 131 | + // mw.log('insert media: not defined:' + typeof |
| 132 | + // this.playerElement.insertMedia ); |
| 133 | + setTimeout(function() { |
| 134 | + _this.postEmbedJS(); |
| 135 | + }, 250); |
| 136 | + } |
| 137 | + }, |
| 138 | + |
| 139 | + /** |
| 140 | + * Bind a Player Function, |
| 141 | + * |
| 142 | + * Does some tricker to bind to "this" player instance: |
| 143 | + * |
| 144 | + * @param {String} |
| 145 | + * flash binding name |
| 146 | + * @param {String} |
| 147 | + * function callback name |
| 148 | + */ |
| 149 | + bindPlayerFunction : function(bindName, methodName) { |
| 150 | + // The kaltura kdp can only call a global function by given name |
| 151 | + var gKdpCallbackName = methodName + '_cb_' + this.id; |
| 152 | + |
| 153 | + // Create an anonymous function with local player scope |
| 154 | + var createGlobalCB = function(cName, embedPlayer) { |
| 155 | + window[ cName ] = function(data) { |
| 156 | + if ( embedPlayer._propagateEvents ) { |
| 157 | + embedPlayer[methodName](data); |
| 158 | + } |
| 159 | + }; |
| 160 | + }(gKdpCallbackName, this); |
| 161 | + |
| 162 | + // Add the listener to the KDP flash player: |
| 163 | + this.playerElement.addJsListener(bindName, gKdpCallbackName); |
| 164 | + }, |
| 165 | + |
| 166 | + /** |
| 167 | + * on Pause callback from the kaltura flash player calls parent_pause to |
| 168 | + * update the interface |
| 169 | + */ |
| 170 | + onPause : function() { |
| 171 | + this.parent_pause(); |
| 172 | + }, |
| 173 | + |
| 174 | + /** |
| 175 | + * onPlay function callback from the kaltura flash player directly call the |
| 176 | + * parent_play |
| 177 | + */ |
| 178 | + onPlay : function() { |
| 179 | + this.parent_play(); |
| 180 | + }, |
| 181 | + |
| 182 | + onDurationChange : function(data, id) { |
| 183 | + mw.log(" onDurationChange: " + data.newValue); |
| 184 | + // update the duration: |
| 185 | + this.duration = data.newValue; |
| 186 | + }, |
| 187 | + |
| 188 | + /** |
| 189 | + * play method calls parent_play to update the interface |
| 190 | + */ |
| 191 | + play : function() { |
| 192 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 193 | + this.playerElement.sendNotification('doPlay'); |
| 194 | + } |
| 195 | + this.parent_play(); |
| 196 | + }, |
| 197 | + |
| 198 | + /** |
| 199 | + * pause method calls parent_pause to update the interface |
| 200 | + */ |
| 201 | + pause : function() { |
| 202 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 203 | + this.playerElement.sendNotification('doPause'); |
| 204 | + } |
| 205 | + this.parent_pause(); |
| 206 | + }, |
| 207 | + |
| 208 | + /** |
| 209 | + * Issues a seek to the playerElement |
| 210 | + * |
| 211 | + * @param {Float} |
| 212 | + * percentage Percentage of total stream length to seek to |
| 213 | + */ |
| 214 | + doSeek : function(percentage) { |
| 215 | + var _this = this; |
| 216 | + var seekTime = percentage * this.getDuration(); |
| 217 | + mw.log( 'EmbedPlayerKalturaKplayer:: doSeek: ' + percentage + ' time:' + seekTime ); |
| 218 | + if (this.supportsURLTimeEncoding()) { |
| 219 | + |
| 220 | + // Make sure we could not do a local seek instead: |
| 221 | + if (!(percentage < this.bufferedPercent |
| 222 | + && this.playerElement.duration && !this.didSeekJump)) { |
| 223 | + // We support URLTimeEncoding call parent seek: |
| 224 | + this.parent_doSeek( percentage ); |
| 225 | + return; |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 229 | + if (this.playerElement) { |
| 230 | + // Issue the seek to the flash player: |
| 231 | + this.playerElement.sendNotification('doSeek', seekTime); |
| 232 | + |
| 233 | + // Kdp is missing seek done callback |
| 234 | + setTimeout(function() { |
| 235 | + _this.seeking = false; |
| 236 | + }, 500); |
| 237 | + } else { |
| 238 | + // try to do a play then seek: |
| 239 | + this.doPlayThenSeek(percentage); |
| 240 | + } |
| 241 | + // Run the onSeeking interface update |
| 242 | + this.controlBuilder.onSeek(); |
| 243 | + }, |
| 244 | + |
| 245 | + /** |
| 246 | + * Seek in a existing stream |
| 247 | + * |
| 248 | + * @param {Float} |
| 249 | + * percentage Percentage of the stream to seek to between 0 and 1 |
| 250 | + */ |
| 251 | + doPlayThenSeek : function(percentage) { |
| 252 | + mw.log('flash::doPlayThenSeek::'); |
| 253 | + var _this = this; |
| 254 | + // issue the play request |
| 255 | + this.play(); |
| 256 | + |
| 257 | + // let the player know we are seeking |
| 258 | + _this.seeking = true; |
| 259 | + |
| 260 | + var getPlayerCount = 0; |
| 261 | + var readyForSeek = function() { |
| 262 | + _this.getPlayerElement(); |
| 263 | + // if we have duration then we are ready to do the seek ( flash can't |
| 264 | + // seek untill there is some buffer ) |
| 265 | + if (_this.playerElement && _this.playerElement.sendNotification |
| 266 | + && _this.getDuration() && _this.bufferedPercent) { |
| 267 | + var seekTime = percentage * _this.getDuration(); |
| 268 | + // Issue the seek to the flash player: |
| 269 | + _this.playerElement.sendNotification('doSeek', seekTime); |
| 270 | + } else { |
| 271 | + // Try to get player for 20 seconds: |
| 272 | + if (getPlayerCount < 400) { |
| 273 | + setTimeout(readyForSeek, 50); |
| 274 | + getPlayerCount++; |
| 275 | + } else { |
| 276 | + mw.log('Error:doPlayThenSeek failed'); |
| 277 | + } |
| 278 | + } |
| 279 | + }; |
| 280 | + readyForSeek(); |
| 281 | + }, |
| 282 | + |
| 283 | + /** |
| 284 | + * Issues a volume update to the playerElement |
| 285 | + * |
| 286 | + * @param {Float} |
| 287 | + * percentage Percentage to update volume to |
| 288 | + */ |
| 289 | + setPlayerElementVolume : function(percentage) { |
| 290 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 291 | + this.playerElement.sendNotification('changeVolume', percentage); |
| 292 | + } |
| 293 | + }, |
| 294 | + |
| 295 | + /** |
| 296 | + * function called by flash at set interval to update the playhead. |
| 297 | + */ |
| 298 | + onUpdatePlayhead : function(playheadValue) { |
| 299 | + this.flashCurrentTime = playheadValue; |
| 300 | + }, |
| 301 | + |
| 302 | + /** |
| 303 | + * function called by flash when the total media size changes |
| 304 | + */ |
| 305 | + onBytesTotalChange : function(data, id) { |
| 306 | + this.bytesTotal = data.newValue; |
| 307 | + }, |
| 308 | + |
| 309 | + /** |
| 310 | + * function called by flash applet when download bytes changes |
| 311 | + */ |
| 312 | + onBytesDownloadedChange : function(data, id) { |
| 313 | + mw.log('onBytesDownloadedChange'); |
| 314 | + this.bytesLoaded = data.newValue; |
| 315 | + this.bufferedPercent = this.bytesLoaded / this.bytesTotal; |
| 316 | + |
| 317 | + // Fire the parent html5 action |
| 318 | + $(this).trigger('progress', { |
| 319 | + 'loaded' : this.bytesLoaded, |
| 320 | + 'total' : this.bytesTotal |
| 321 | + }); |
| 322 | + }, |
| 323 | + |
| 324 | + /** |
| 325 | + * Get the embed player time |
| 326 | + */ |
| 327 | + getPlayerElementTime : function() { |
| 328 | + // update currentTime |
| 329 | + return this.flashCurrentTime; |
| 330 | + }, |
| 331 | + |
| 332 | + /** |
| 333 | + * Get the embed fla object player Element |
| 334 | + */ |
| 335 | + getPlayerElement : function() { |
| 336 | + this.playerElement = document.getElementById(this.pid); |
| 337 | + return this.playerElement; |
| 338 | + } |
| 339 | +}; |
| 340 | + |
| 341 | +/** |
| 342 | + * function called once player is ready. |
| 343 | + * |
| 344 | + * NOTE: playerID is not always passed so we can't use this: |
| 345 | + */ |
| 346 | +function onKdpReady(playerId) { |
| 347 | + mw.log("player is ready::" + playerId); |
| 348 | +} |
| 349 | + |
| 350 | +/* |
| 351 | + * ! SWFObject v2.2 <http://code.google.com/p/swfobject/> is released under the |
| 352 | + * MIT License <http://www.opensource.org/licenses/mit-license.php> |
| 353 | + */ |
| 354 | + |
| 355 | +var swfobject = function() { |
| 356 | + |
| 357 | + var UNDEF = "undefined", OBJECT = "object", SHOCKWAVE_FLASH = "Shockwave Flash", SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", FLASH_MIME_TYPE = "application/x-shockwave-flash", EXPRESS_INSTALL_ID = "SWFObjectExprInst", ON_READY_STATE_CHANGE = "onreadystatechange", |
| 358 | + |
| 359 | + win = window, doc = document, nav = navigator, |
| 360 | + |
| 361 | + plugin = false, domLoadFnArr = [ main ], regObjArr = [], objIdArr = [], listenersArr = [], storedAltContent, storedAltContentId, storedCallbackFn, storedCallbackObj, isDomLoaded = false, isExpressInstallActive = false, dynamicStylesheet, dynamicStylesheetMedia, autoHideShow = true, |
| 362 | + |
| 363 | + /* |
| 364 | + * Centralized function for browser feature detection - User agent string |
| 365 | + * detection is only used when no good alternative is possible - Is executed |
| 366 | + * directly for optimal performance |
| 367 | + */ |
| 368 | + ua = function() { |
| 369 | + var w3cdom = typeof doc.getElementById != UNDEF |
| 370 | + && typeof doc.getElementsByTagName != UNDEF |
| 371 | + && typeof doc.createElement != UNDEF, u = nav.userAgent |
| 372 | + .toLowerCase(), p = nav.platform.toLowerCase(), windows = p ? /win/ |
| 373 | + .test(p) |
| 374 | + : /win/.test(u), mac = p ? /mac/.test(p) : /mac/.test(u), webkit = /webkit/ |
| 375 | + .test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, |
| 376 | + "$1")) : false, // returns either the webkit version or false if |
| 377 | + // not webkit |
| 378 | + ie = !+"\v1", // feature detection based on Andrea Giammarchi's |
| 379 | + // solution: |
| 380 | + // http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html |
| 381 | + playerVersion = [ 0, 0, 0 ], d = null; |
| 382 | + if (typeof nav.plugins != UNDEF |
| 383 | + && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { |
| 384 | + d = nav.plugins[SHOCKWAVE_FLASH].description; |
| 385 | + if (d |
| 386 | + && !(typeof nav.mimeTypes != UNDEF |
| 387 | + && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin |
| 388 | + // indicates |
| 389 | + // whether |
| 390 | + // plug-ins |
| 391 | + // are |
| 392 | + // enabled |
| 393 | + // or |
| 394 | + // disabled |
| 395 | + // in |
| 396 | + // Safari |
| 397 | + // 3+ |
| 398 | + plugin = true; |
| 399 | + ie = false; // cascaded feature detection for Internet Explorer |
| 400 | + d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); |
| 401 | + playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); |
| 402 | + playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), |
| 403 | + 10); |
| 404 | + playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace( |
| 405 | + /^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; |
| 406 | + } |
| 407 | + } else if (typeof win.ActiveXObject != UNDEF) { |
| 408 | + try { |
| 409 | + var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); |
| 410 | + if (a) { // a will return null when ActiveX is disabled |
| 411 | + d = a.GetVariable("$version"); |
| 412 | + if (d) { |
| 413 | + ie = true; // cascaded feature detection for Internet |
| 414 | + // Explorer |
| 415 | + d = d.split(" ")[1].split(","); |
| 416 | + playerVersion = [ parseInt(d[0], 10), |
| 417 | + parseInt(d[1], 10), parseInt(d[2], 10) ]; |
| 418 | + } |
| 419 | + } |
| 420 | + } catch (e) { |
| 421 | + } |
| 422 | + } |
| 423 | + return { |
| 424 | + w3 : w3cdom, |
| 425 | + pv : playerVersion, |
| 426 | + wk : webkit, |
| 427 | + ie : ie, |
| 428 | + win : windows, |
| 429 | + mac : mac |
| 430 | + }; |
| 431 | + }(); |
| 432 | + |
| 433 | + function callDomLoadFunctions() { |
| 434 | + if (isDomLoaded) { |
| 435 | + return; |
| 436 | + } |
| 437 | + try { // test if we can really add/remove elements to/from the DOM; we |
| 438 | + // don't want to fire it too early |
| 439 | + var t = doc.getElementsByTagName("body")[0] |
| 440 | + .appendChild(createElement("span")); |
| 441 | + t.parentNode.removeChild(t); |
| 442 | + } catch (e) { |
| 443 | + return; |
| 444 | + } |
| 445 | + isDomLoaded = true; |
| 446 | + var dl = domLoadFnArr.length; |
| 447 | + for ( var i = 0; i < dl; i++) { |
| 448 | + domLoadFnArr[i](); |
| 449 | + } |
| 450 | + } |
| 451 | + |
| 452 | + function addDomLoadEvent(fn) { |
| 453 | + if (isDomLoaded) { |
| 454 | + fn(); |
| 455 | + } else { |
| 456 | + domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only |
| 457 | + // available in IE5.5+ |
| 458 | + } |
| 459 | + } |
| 460 | + |
| 461 | + /* |
| 462 | + * Cross-browser onload - Based on James Edwards' solution: |
| 463 | + * http://brothercake.com/site/resources/scripts/onload/ - Will fire an |
| 464 | + * event as soon as a web page including all of its assets are loaded |
| 465 | + */ |
| 466 | + function addLoadEvent(fn) { |
| 467 | + if (typeof win.addEventListener != UNDEF) { |
| 468 | + win.addEventListener("load", fn, false); |
| 469 | + } else if (typeof doc.addEventListener != UNDEF) { |
| 470 | + doc.addEventListener("load", fn, false); |
| 471 | + } else if (typeof win.attachEvent != UNDEF) { |
| 472 | + addListener(win, "onload", fn); |
| 473 | + } else if (typeof win.onload == "function") { |
| 474 | + var fnOld = win.onload; |
| 475 | + win.onload = function() { |
| 476 | + fnOld(); |
| 477 | + fn(); |
| 478 | + }; |
| 479 | + } else { |
| 480 | + win.onload = fn; |
| 481 | + } |
| 482 | + } |
| 483 | + |
| 484 | + /* |
| 485 | + * Main function - Will preferably execute onDomLoad, otherwise onload (as a |
| 486 | + * fallback) |
| 487 | + */ |
| 488 | + function main() { |
| 489 | + if (plugin) { |
| 490 | + testPlayerVersion(); |
| 491 | + } else { |
| 492 | + matchVersions(); |
| 493 | + } |
| 494 | + } |
| 495 | + |
| 496 | + /* |
| 497 | + * Detect the Flash Player version for non-Internet Explorer browsers - |
| 498 | + * Detecting the plug-in version via the object element is more precise than |
| 499 | + * using the plugins collection item's description: a. Both release and |
| 500 | + * build numbers can be detected b. Avoid wrong descriptions by corrupt |
| 501 | + * installers provided by Adobe c. Avoid wrong descriptions by multiple |
| 502 | + * Flash Player entries in the plugin Array, caused by incorrect browser |
| 503 | + * imports - Disadvantage of this method is that it depends on the |
| 504 | + * availability of the DOM, while the plugins collection is immediately |
| 505 | + * available |
| 506 | + */ |
| 507 | + function testPlayerVersion() { |
| 508 | + var b = doc.getElementsByTagName("body")[0]; |
| 509 | + var o = createElement(OBJECT); |
| 510 | + o.setAttribute("type", FLASH_MIME_TYPE); |
| 511 | + var t = b.appendChild(o); |
| 512 | + if (t) { |
| 513 | + var counter = 0; |
| 514 | + (function() { |
| 515 | + if (typeof t.GetVariable != UNDEF) { |
| 516 | + try { |
| 517 | + var d = t.GetVariable("$version"); |
| 518 | + if (d) { |
| 519 | + d = d.split(" ")[1].split(","); |
| 520 | + ua.pv = [ parseInt(d[0], 10), parseInt(d[1], 10), |
| 521 | + parseInt(d[2], 10) ]; |
| 522 | + } |
| 523 | + } catch (e) { |
| 524 | + // error in grabbing flash version |
| 525 | + } |
| 526 | + } else if (counter < 10) { |
| 527 | + counter++; |
| 528 | + setTimeout(arguments.callee, 10); |
| 529 | + return; |
| 530 | + } |
| 531 | + b.removeChild(o); |
| 532 | + t = null; |
| 533 | + matchVersions(); |
| 534 | + })(); |
| 535 | + } else { |
| 536 | + matchVersions(); |
| 537 | + } |
| 538 | + } |
| 539 | + |
| 540 | + /* |
| 541 | + * Perform Flash Player and SWF version matching; static publishing only |
| 542 | + */ |
| 543 | + function matchVersions() { |
| 544 | + var rl = regObjArr.length; |
| 545 | + if (rl > 0) { |
| 546 | + for ( var i = 0; i < rl; i++) { // for each registered object |
| 547 | + // element |
| 548 | + var id = regObjArr[i].id; |
| 549 | + var cb = regObjArr[i].callbackFn; |
| 550 | + var cbObj = { |
| 551 | + success : false, |
| 552 | + id : id |
| 553 | + }; |
| 554 | + if (ua.pv[0] > 0) { |
| 555 | + var obj = getElementById(id); |
| 556 | + if (obj) { |
| 557 | + if (hasPlayerVersion(regObjArr[i].swfVersion) |
| 558 | + && !(ua.wk && ua.wk < 312)) { // Flash Player |
| 559 | + // version >= |
| 560 | + // published SWF |
| 561 | + // version: |
| 562 | + // Houston, we |
| 563 | + // have a match! |
| 564 | + setVisibility(id, true); |
| 565 | + if (cb) { |
| 566 | + cbObj.success = true; |
| 567 | + cbObj.ref = getObjectById(id); |
| 568 | + cb(cbObj); |
| 569 | + } |
| 570 | + } else if (regObjArr[i].expressInstall |
| 571 | + && canExpressInstall()) { // show the Adobe |
| 572 | + // Express Install |
| 573 | + // dialog if set by |
| 574 | + // the web page |
| 575 | + // author and if |
| 576 | + // supported |
| 577 | + var att = {}; |
| 578 | + att.data = regObjArr[i].expressInstall; |
| 579 | + att.width = obj.getAttribute("width") || "0"; |
| 580 | + att.height = obj.getAttribute("height") || "0"; |
| 581 | + if (obj.getAttribute("class")) { |
| 582 | + att.styleclass = obj.getAttribute("class"); |
| 583 | + } |
| 584 | + if (obj.getAttribute("align")) { |
| 585 | + att.align = obj.getAttribute("align"); |
| 586 | + } |
| 587 | + // parse HTML object param element's name-value |
| 588 | + // pairs |
| 589 | + var par = {}; |
| 590 | + var p = obj.getElementsByTagName("param"); |
| 591 | + var pl = p.length; |
| 592 | + for ( var j = 0; j < pl; j++) { |
| 593 | + if (p[j].getAttribute("name").toLowerCase() != "movie") { |
| 594 | + par[p[j].getAttribute("name")] = p[j] |
| 595 | + .getAttribute("value"); |
| 596 | + } |
| 597 | + } |
| 598 | + showExpressInstall(att, par, id, cb); |
| 599 | + } else { // Flash Player and SWF version mismatch or |
| 600 | + // an older Webkit engine that ignores the |
| 601 | + // HTML object element's nested param |
| 602 | + // elements: display alternative content |
| 603 | + // instead of SWF |
| 604 | + displayAltContent(obj); |
| 605 | + if (cb) { |
| 606 | + cb(cbObj); |
| 607 | + } |
| 608 | + } |
| 609 | + } |
| 610 | + } else { // if no Flash Player is installed or the fp version |
| 611 | + // cannot be detected we let the HTML object element |
| 612 | + // do its job (either show a SWF or alternative |
| 613 | + // content) |
| 614 | + setVisibility(id, true); |
| 615 | + if (cb) { |
| 616 | + var o = getObjectById(id); // test whether there is an |
| 617 | + // HTML object element or |
| 618 | + // not |
| 619 | + if (o && typeof o.SetVariable != UNDEF) { |
| 620 | + cbObj.success = true; |
| 621 | + cbObj.ref = o; |
| 622 | + } |
| 623 | + cb(cbObj); |
| 624 | + } |
| 625 | + } |
| 626 | + } |
| 627 | + } |
| 628 | + } |
| 629 | + |
| 630 | + function getObjectById(objectIdStr) { |
| 631 | + var r = null; |
| 632 | + var o = getElementById(objectIdStr); |
| 633 | + if (o && o.nodeName == "OBJECT") { |
| 634 | + if (typeof o.SetVariable != UNDEF) { |
| 635 | + r = o; |
| 636 | + } else { |
| 637 | + var n = o.getElementsByTagName(OBJECT)[0]; |
| 638 | + if (n) { |
| 639 | + r = n; |
| 640 | + } |
| 641 | + } |
| 642 | + } |
| 643 | + return r; |
| 644 | + } |
| 645 | + |
| 646 | + /* |
| 647 | + * Requirements for Adobe Express Install - only one instance can be active |
| 648 | + * at a time - fp 6.0.65 or higher - Win/Mac OS only - no Webkit engines |
| 649 | + * older than version 312 |
| 650 | + */ |
| 651 | + function canExpressInstall() { |
| 652 | + return !isExpressInstallActive && hasPlayerVersion("6.0.65") |
| 653 | + && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); |
| 654 | + } |
| 655 | + |
| 656 | + /* |
| 657 | + * Show the Adobe Express Install dialog - Reference: |
| 658 | + * http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 |
| 659 | + */ |
| 660 | + function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { |
| 661 | + isExpressInstallActive = true; |
| 662 | + storedCallbackFn = callbackFn || null; |
| 663 | + storedCallbackObj = { |
| 664 | + success : false, |
| 665 | + id : replaceElemIdStr |
| 666 | + }; |
| 667 | + var obj = getElementById(replaceElemIdStr); |
| 668 | + if (obj) { |
| 669 | + if (obj.nodeName == "OBJECT") { // static publishing |
| 670 | + storedAltContent = abstractAltContent(obj); |
| 671 | + storedAltContentId = null; |
| 672 | + } else { // dynamic publishing |
| 673 | + storedAltContent = obj; |
| 674 | + storedAltContentId = replaceElemIdStr; |
| 675 | + } |
| 676 | + att.id = EXPRESS_INSTALL_ID; |
| 677 | + if (typeof att.width == UNDEF |
| 678 | + || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { |
| 679 | + att.width = "310"; |
| 680 | + } |
| 681 | + if (typeof att.height == UNDEF |
| 682 | + || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { |
| 683 | + att.height = "137"; |
| 684 | + } |
| 685 | + doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; |
| 686 | + var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", fv = "MMredirectURL=" |
| 687 | + + win.location.toString().replace(/&/g, "%26") |
| 688 | + + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; |
| 689 | + if (typeof par.flashvars != UNDEF) { |
| 690 | + par.flashvars += "&" + fv; |
| 691 | + } else { |
| 692 | + par.flashvars = fv; |
| 693 | + } |
| 694 | + // IE only: when a SWF is loading (AND: not available in cache) wait |
| 695 | + // for the readyState of the object element to become 4 before |
| 696 | + // removing it, |
| 697 | + // because you cannot properly cancel a loading SWF file without |
| 698 | + // breaking browser load references, also obj.onreadystatechange |
| 699 | + // doesn't work |
| 700 | + if (ua.ie && ua.win && obj.readyState != 4) { |
| 701 | + var newObj = createElement("div"); |
| 702 | + replaceElemIdStr += "SWFObjectNew"; |
| 703 | + newObj.setAttribute("id", replaceElemIdStr); |
| 704 | + obj.parentNode.insertBefore(newObj, obj); // insert |
| 705 | + // placeholder div |
| 706 | + // that will be |
| 707 | + // replaced by the |
| 708 | + // object element |
| 709 | + // that loads |
| 710 | + // expressinstall.swf |
| 711 | + obj.style.display = "none"; |
| 712 | + (function() { |
| 713 | + if (obj.readyState == 4) { |
| 714 | + obj.parentNode.removeChild(obj); |
| 715 | + } else { |
| 716 | + setTimeout(arguments.callee, 10); |
| 717 | + } |
| 718 | + })(); |
| 719 | + } |
| 720 | + createSWF(att, par, replaceElemIdStr); |
| 721 | + } |
| 722 | + } |
| 723 | + |
| 724 | + /* |
| 725 | + * Functions to abstract and display alternative content |
| 726 | + */ |
| 727 | + function displayAltContent(obj) { |
| 728 | + if (ua.ie && ua.win && obj.readyState != 4) { |
| 729 | + // IE only: when a SWF is loading (AND: not available in cache) wait |
| 730 | + // for the readyState of the object element to become 4 before |
| 731 | + // removing it, |
| 732 | + // because you cannot properly cancel a loading SWF file without |
| 733 | + // breaking browser load references, also obj.onreadystatechange |
| 734 | + // doesn't work |
| 735 | + var el = createElement("div"); |
| 736 | + obj.parentNode.insertBefore(el, obj); // insert placeholder div |
| 737 | + // that will be replaced by |
| 738 | + // the alternative content |
| 739 | + el.parentNode.replaceChild(abstractAltContent(obj), el); |
| 740 | + obj.style.display = "none"; |
| 741 | + (function() { |
| 742 | + if (obj.readyState == 4) { |
| 743 | + obj.parentNode.removeChild(obj); |
| 744 | + } else { |
| 745 | + setTimeout(arguments.callee, 10); |
| 746 | + } |
| 747 | + })(); |
| 748 | + } else { |
| 749 | + obj.parentNode.replaceChild(abstractAltContent(obj), obj); |
| 750 | + } |
| 751 | + } |
| 752 | + |
| 753 | + function abstractAltContent(obj) { |
| 754 | + var ac = createElement("div"); |
| 755 | + if (ua.win && ua.ie) { |
| 756 | + ac.innerHTML = obj.innerHTML; |
| 757 | + } else { |
| 758 | + var nestedObj = obj.getElementsByTagName(OBJECT)[0]; |
| 759 | + if (nestedObj) { |
| 760 | + var c = nestedObj.childNodes; |
| 761 | + if (c) { |
| 762 | + var cl = c.length; |
| 763 | + for ( var i = 0; i < cl; i++) { |
| 764 | + if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") |
| 765 | + && !(c[i].nodeType == 8)) { |
| 766 | + ac.appendChild(c[i].cloneNode(true)); |
| 767 | + } |
| 768 | + } |
| 769 | + } |
| 770 | + } |
| 771 | + } |
| 772 | + return ac; |
| 773 | + } |
| 774 | + |
| 775 | + /* |
| 776 | + * Cross-browser dynamic SWF creation |
| 777 | + */ |
| 778 | + function createSWF(attObj, parObj, id) { |
| 779 | + var r, el = getElementById(id); |
| 780 | + if (ua.wk && ua.wk < 312) { |
| 781 | + return r; |
| 782 | + } |
| 783 | + if (el) { |
| 784 | + if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the |
| 785 | + // object element, it will |
| 786 | + // inherit the 'id' from the |
| 787 | + // alternative content |
| 788 | + attObj.id = id; |
| 789 | + } |
| 790 | + if (ua.ie && ua.win) { // Internet Explorer + the HTML object |
| 791 | + // element + W3C DOM methods do not combine: |
| 792 | + // fall back to outerHTML |
| 793 | + var att = ""; |
| 794 | + for ( var i in attObj) { |
| 795 | + if (attObj[i] != Object.prototype[i]) { // filter out |
| 796 | + // prototype |
| 797 | + // additions from |
| 798 | + // other potential |
| 799 | + // libraries |
| 800 | + if (i.toLowerCase() == "data") { |
| 801 | + parObj.movie = attObj[i]; |
| 802 | + } else if (i.toLowerCase() == "styleclass") { // 'class' |
| 803 | + // is an |
| 804 | + // ECMA4 |
| 805 | + // reserved |
| 806 | + // keyword |
| 807 | + att += ' class="' + attObj[i] + '"'; |
| 808 | + } else if (i.toLowerCase() != "classid") { |
| 809 | + att += ' ' + i + '="' + attObj[i] + '"'; |
| 810 | + } |
| 811 | + } |
| 812 | + } |
| 813 | + var par = ""; |
| 814 | + for ( var j in parObj) { |
| 815 | + if (parObj[j] != Object.prototype[j]) { // filter out |
| 816 | + // prototype |
| 817 | + // additions from |
| 818 | + // other potential |
| 819 | + // libraries |
| 820 | + par += '<param name="' + j + '" value="' + parObj[j] |
| 821 | + + '" />'; |
| 822 | + } |
| 823 | + } |
| 824 | + el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' |
| 825 | + + att + '>' + par + '</object>'; |
| 826 | + objIdArr[objIdArr.length] = attObj.id; // stored to fix object |
| 827 | + // 'leaks' on unload |
| 828 | + // (dynamic publishing |
| 829 | + // only) |
| 830 | + r = getElementById(attObj.id); |
| 831 | + } else { // well-behaving browsers |
| 832 | + var o = createElement(OBJECT); |
| 833 | + o.setAttribute("type", FLASH_MIME_TYPE); |
| 834 | + for ( var m in attObj) { |
| 835 | + if (attObj[m] != Object.prototype[m]) { // filter out |
| 836 | + // prototype |
| 837 | + // additions from |
| 838 | + // other potential |
| 839 | + // libraries |
| 840 | + if (m.toLowerCase() == "styleclass") { // 'class' is an |
| 841 | + // ECMA4 |
| 842 | + // reserved |
| 843 | + // keyword |
| 844 | + o.setAttribute("class", attObj[m]); |
| 845 | + } else if (m.toLowerCase() != "classid") { // filter |
| 846 | + // out IE |
| 847 | + // specific |
| 848 | + // attribute |
| 849 | + o.setAttribute(m, attObj[m]); |
| 850 | + } |
| 851 | + } |
| 852 | + } |
| 853 | + for ( var n in parObj) { |
| 854 | + if (parObj[n] != Object.prototype[n] |
| 855 | + && n.toLowerCase() != "movie") { // filter out |
| 856 | + // prototype |
| 857 | + // additions |
| 858 | + // from other |
| 859 | + // potential |
| 860 | + // libraries and |
| 861 | + // IE specific |
| 862 | + // param element |
| 863 | + createObjParam(o, n, parObj[n]); |
| 864 | + } |
| 865 | + } |
| 866 | + el.parentNode.replaceChild(o, el); |
| 867 | + r = o; |
| 868 | + } |
| 869 | + } |
| 870 | + return r; |
| 871 | + } |
| 872 | + |
| 873 | + function createObjParam(el, pName, pValue) { |
| 874 | + var p = createElement("param"); |
| 875 | + p.setAttribute("name", pName); |
| 876 | + p.setAttribute("value", pValue); |
| 877 | + el.appendChild(p); |
| 878 | + } |
| 879 | + |
| 880 | + /* |
| 881 | + * Cross-browser SWF removal - Especially needed to safely and completely |
| 882 | + * remove a SWF in Internet Explorer |
| 883 | + */ |
| 884 | + function removeSWF(id) { |
| 885 | + var obj = getElementById(id); |
| 886 | + if (obj && obj.nodeName == "OBJECT") { |
| 887 | + if (ua.ie && ua.win) { |
| 888 | + obj.style.display = "none"; |
| 889 | + (function() { |
| 890 | + if (obj.readyState == 4) { |
| 891 | + removeObjectInIE(id); |
| 892 | + } else { |
| 893 | + setTimeout(arguments.callee, 10); |
| 894 | + } |
| 895 | + })(); |
| 896 | + } else { |
| 897 | + obj.parentNode.removeChild(obj); |
| 898 | + } |
| 899 | + } |
| 900 | + } |
| 901 | + |
| 902 | + function removeObjectInIE(id) { |
| 903 | + var obj = getElementById(id); |
| 904 | + if (obj) { |
| 905 | + for ( var i in obj) { |
| 906 | + if (typeof obj[i] == "function") { |
| 907 | + obj[i] = null; |
| 908 | + } |
| 909 | + } |
| 910 | + obj.parentNode.removeChild(obj); |
| 911 | + } |
| 912 | + } |
| 913 | + |
| 914 | + /* |
| 915 | + * Functions to optimize JavaScript compression |
| 916 | + */ |
| 917 | + function getElementById(id) { |
| 918 | + var el = null; |
| 919 | + try { |
| 920 | + el = doc.getElementById(id); |
| 921 | + } catch (e) { |
| 922 | + } |
| 923 | + return el; |
| 924 | + } |
| 925 | + |
| 926 | + function createElement(el) { |
| 927 | + return doc.createElement(el); |
| 928 | + } |
| 929 | + |
| 930 | + /* |
| 931 | + * Updated attachEvent function for Internet Explorer - Stores attachEvent |
| 932 | + * information in an Array, so on unload the detachEvent functions can be |
| 933 | + * called to avoid memory leaks |
| 934 | + */ |
| 935 | + function addListener(target, eventType, fn) { |
| 936 | + target.attachEvent(eventType, fn); |
| 937 | + listenersArr[listenersArr.length] = [ target, eventType, fn ]; |
| 938 | + } |
| 939 | + |
| 940 | + /* |
| 941 | + * Flash Player and SWF content version matching |
| 942 | + */ |
| 943 | + function hasPlayerVersion(rv) { |
| 944 | + var pv = ua.pv, v = rv.split("."); |
| 945 | + v[0] = parseInt(v[0], 10); |
| 946 | + v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" |
| 947 | + // instead of "9.0.0" |
| 948 | + v[2] = parseInt(v[2], 10) || 0; |
| 949 | + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] |
| 950 | + && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; |
| 951 | + } |
| 952 | + |
| 953 | + /* |
| 954 | + * Cross-browser dynamic CSS creation - Based on Bobby van der Sluis' |
| 955 | + * solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php |
| 956 | + */ |
| 957 | + function createCSS(sel, decl, media, newStyle) { |
| 958 | + if (ua.ie && ua.mac) { |
| 959 | + return; |
| 960 | + } |
| 961 | + var h = doc.getElementsByTagName("head")[0]; |
| 962 | + if (!h) { |
| 963 | + return; |
| 964 | + } // to also support badly authored HTML pages that lack a head |
| 965 | + // element |
| 966 | + var m = (media && typeof media == "string") ? media : "screen"; |
| 967 | + if (newStyle) { |
| 968 | + dynamicStylesheet = null; |
| 969 | + dynamicStylesheetMedia = null; |
| 970 | + } |
| 971 | + if (!dynamicStylesheet || dynamicStylesheetMedia != m) { |
| 972 | + // create dynamic stylesheet + get a global reference to it |
| 973 | + var s = createElement("style"); |
| 974 | + s.setAttribute("type", "text/css"); |
| 975 | + s.setAttribute("media", m); |
| 976 | + dynamicStylesheet = h.appendChild(s); |
| 977 | + if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF |
| 978 | + && doc.styleSheets.length > 0) { |
| 979 | + dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; |
| 980 | + } |
| 981 | + dynamicStylesheetMedia = m; |
| 982 | + } |
| 983 | + // add style rule |
| 984 | + if (ua.ie && ua.win) { |
| 985 | + if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { |
| 986 | + dynamicStylesheet.addRule(sel, decl); |
| 987 | + } |
| 988 | + } else { |
| 989 | + if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { |
| 990 | + dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" |
| 991 | + + decl + "}")); |
| 992 | + } |
| 993 | + } |
| 994 | + } |
| 995 | + |
| 996 | + function setVisibility(id, isVisible) { |
| 997 | + if (!autoHideShow) { |
| 998 | + return; |
| 999 | + } |
| 1000 | + var v = isVisible ? "visible" : "hidden"; |
| 1001 | + if (isDomLoaded && getElementById(id)) { |
| 1002 | + getElementById(id).style.visibility = v; |
| 1003 | + } else { |
| 1004 | + createCSS("#" + id, "visibility:" + v); |
| 1005 | + } |
| 1006 | + } |
| 1007 | + |
| 1008 | + /* |
| 1009 | + * Filter to avoid XSS attacks |
| 1010 | + */ |
| 1011 | + function urlEncodeIfNecessary(s) { |
| 1012 | + var regex = /[\\\"<>\.;]/; |
| 1013 | + var hasBadChars = regex.exec(s) != null; |
| 1014 | + return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) |
| 1015 | + : s; |
| 1016 | + } |
| 1017 | + |
| 1018 | + /* |
| 1019 | + * Release memory to avoid memory leaks caused by closures, fix hanging |
| 1020 | + * audio/video threads and force open sockets/NetConnections to disconnect |
| 1021 | + * (Internet Explorer only) |
| 1022 | + */ |
| 1023 | + var cleanup = function() { |
| 1024 | + if (ua.ie && ua.win) { |
| 1025 | + window.attachEvent("onunload", function() { |
| 1026 | + // remove listeners to avoid memory leaks |
| 1027 | + var ll = listenersArr.length; |
| 1028 | + for ( var i = 0; i < ll; i++) { |
| 1029 | + listenersArr[i][0].detachEvent(listenersArr[i][1], |
| 1030 | + listenersArr[i][2]); |
| 1031 | + } |
| 1032 | + // cleanup dynamically embedded objects to fix audio/video |
| 1033 | + // threads and force open sockets and NetConnections to |
| 1034 | + // disconnect |
| 1035 | + var il = objIdArr.length; |
| 1036 | + for ( var j = 0; j < il; j++) { |
| 1037 | + removeSWF(objIdArr[j]); |
| 1038 | + } |
| 1039 | + // cleanup library's main closures to avoid memory leaks |
| 1040 | + for ( var k in ua) { |
| 1041 | + ua[k] = null; |
| 1042 | + } |
| 1043 | + ua = null; |
| 1044 | + for ( var l in swfobject) { |
| 1045 | + swfobject[l] = null; |
| 1046 | + } |
| 1047 | + swfobject = null; |
| 1048 | + }); |
| 1049 | + } |
| 1050 | + }(); |
| 1051 | + |
| 1052 | + return { |
| 1053 | + /* |
| 1054 | + * Public API - Reference: |
| 1055 | + * http://code.google.com/p/swfobject/wiki/documentation |
| 1056 | + */ |
| 1057 | + registerObject : function(objectIdStr, swfVersionStr, xiSwfUrlStr, |
| 1058 | + callbackFn) { |
| 1059 | + if (ua.w3 && objectIdStr && swfVersionStr) { |
| 1060 | + var regObj = {}; |
| 1061 | + regObj.id = objectIdStr; |
| 1062 | + regObj.swfVersion = swfVersionStr; |
| 1063 | + regObj.expressInstall = xiSwfUrlStr; |
| 1064 | + regObj.callbackFn = callbackFn; |
| 1065 | + regObjArr[regObjArr.length] = regObj; |
| 1066 | + setVisibility(objectIdStr, false); |
| 1067 | + } else if (callbackFn) { |
| 1068 | + callbackFn( { |
| 1069 | + success : false, |
| 1070 | + id : objectIdStr |
| 1071 | + }); |
| 1072 | + } |
| 1073 | + }, |
| 1074 | + |
| 1075 | + getObjectById : function(objectIdStr) { |
| 1076 | + if (ua.w3) { |
| 1077 | + return getObjectById(objectIdStr); |
| 1078 | + } |
| 1079 | + }, |
| 1080 | + // XXX added by mdale ( since we know the dom is ready, |
| 1081 | + // this works better. |
| 1082 | + callDomLoadFunctions : function() { |
| 1083 | + callDomLoadFunctions(); |
| 1084 | + }, |
| 1085 | + |
| 1086 | + embedSWF : function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, |
| 1087 | + swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, |
| 1088 | + callbackFn) { |
| 1089 | + var callbackObj = { |
| 1090 | + success : false, |
| 1091 | + id : replaceElemIdStr |
| 1092 | + }; |
| 1093 | + if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr |
| 1094 | + && replaceElemIdStr && widthStr && heightStr |
| 1095 | + && swfVersionStr) { |
| 1096 | + setVisibility(replaceElemIdStr, false); |
| 1097 | + addDomLoadEvent(function() { |
| 1098 | + widthStr += ""; // auto-convert to string |
| 1099 | + heightStr += ""; |
| 1100 | + var att = {}; |
| 1101 | + if (attObj && typeof attObj === OBJECT) { |
| 1102 | + for ( var i in attObj) { // copy object to avoid the |
| 1103 | + // use of references, |
| 1104 | + // because web authors often |
| 1105 | + // reuse attObj for multiple |
| 1106 | + // SWFs |
| 1107 | + att[i] = attObj[i]; |
| 1108 | + } |
| 1109 | + } |
| 1110 | + att.data = swfUrlStr; |
| 1111 | + att.width = widthStr; |
| 1112 | + att.height = heightStr; |
| 1113 | + var par = {}; |
| 1114 | + if (parObj && typeof parObj === OBJECT) { |
| 1115 | + for ( var j in parObj) { // copy object to avoid the |
| 1116 | + // use of references, |
| 1117 | + // because web authors often |
| 1118 | + // reuse parObj for multiple |
| 1119 | + // SWFs |
| 1120 | + par[j] = parObj[j]; |
| 1121 | + } |
| 1122 | + } |
| 1123 | + if (flashvarsObj && typeof flashvarsObj === OBJECT) { |
| 1124 | + for ( var k in flashvarsObj) { // copy object to avoid |
| 1125 | + // the use of |
| 1126 | + // references, because |
| 1127 | + // web authors often |
| 1128 | + // reuse flashvarsObj |
| 1129 | + // for multiple SWFs |
| 1130 | + if (typeof par.flashvars != UNDEF) { |
| 1131 | + par.flashvars += "&" + k + "=" |
| 1132 | + + flashvarsObj[k]; |
| 1133 | + } else { |
| 1134 | + par.flashvars = k + "=" + flashvarsObj[k]; |
| 1135 | + } |
| 1136 | + } |
| 1137 | + } |
| 1138 | + if (hasPlayerVersion(swfVersionStr)) { // create SWF |
| 1139 | + var obj = createSWF(att, par, replaceElemIdStr); |
| 1140 | + if (att.id == replaceElemIdStr) { |
| 1141 | + setVisibility(replaceElemIdStr, true); |
| 1142 | + } |
| 1143 | + callbackObj.success = true; |
| 1144 | + callbackObj.ref = obj; |
| 1145 | + } else if (xiSwfUrlStr && canExpressInstall()) { // show |
| 1146 | + // Adobe |
| 1147 | + // Express |
| 1148 | + // Install |
| 1149 | + att.data = xiSwfUrlStr; |
| 1150 | + showExpressInstall(att, par, replaceElemIdStr, |
| 1151 | + callbackFn); |
| 1152 | + return; |
| 1153 | + } else { // show alternative content |
| 1154 | + setVisibility(replaceElemIdStr, true); |
| 1155 | + } |
| 1156 | + if (callbackFn) { |
| 1157 | + callbackFn(callbackObj); |
| 1158 | + } |
| 1159 | + }); |
| 1160 | + } else if (callbackFn) { |
| 1161 | + callbackFn(callbackObj); |
| 1162 | + } |
| 1163 | + }, |
| 1164 | + |
| 1165 | + switchOffAutoHideShow : function() { |
| 1166 | + autoHideShow = false; |
| 1167 | + }, |
| 1168 | + |
| 1169 | + ua : ua, |
| 1170 | + |
| 1171 | + getFlashPlayerVersion : function() { |
| 1172 | + return { |
| 1173 | + major : ua.pv[0], |
| 1174 | + minor : ua.pv[1], |
| 1175 | + release : ua.pv[2] |
| 1176 | + }; |
| 1177 | + }, |
| 1178 | + |
| 1179 | + hasFlashPlayerVersion : hasPlayerVersion, |
| 1180 | + |
| 1181 | + createSWF : function(attObj, parObj, replaceElemIdStr) { |
| 1182 | + if (ua.w3) { |
| 1183 | + return createSWF(attObj, parObj, replaceElemIdStr); |
| 1184 | + } else { |
| 1185 | + return undefined; |
| 1186 | + } |
| 1187 | + }, |
| 1188 | + |
| 1189 | + showExpressInstall : function(att, par, replaceElemIdStr, callbackFn) { |
| 1190 | + if (ua.w3 && canExpressInstall()) { |
| 1191 | + showExpressInstall(att, par, replaceElemIdStr, callbackFn); |
| 1192 | + } |
| 1193 | + }, |
| 1194 | + |
| 1195 | + removeSWF : function(objElemIdStr) { |
| 1196 | + if (ua.w3) { |
| 1197 | + removeSWF(objElemIdStr); |
| 1198 | + } |
| 1199 | + }, |
| 1200 | + |
| 1201 | + createCSS : function(selStr, declStr, mediaStr, newStyleBoolean) { |
| 1202 | + if (ua.w3) { |
| 1203 | + createCSS(selStr, declStr, mediaStr, newStyleBoolean); |
| 1204 | + } |
| 1205 | + }, |
| 1206 | + |
| 1207 | + addDomLoadEvent : addDomLoadEvent, |
| 1208 | + |
| 1209 | + addLoadEvent : addLoadEvent, |
| 1210 | + |
| 1211 | + getQueryParamValue : function(param) { |
| 1212 | + var q = doc.location.search || doc.location.hash; |
| 1213 | + if (q) { |
| 1214 | + if (/\?/.test(q)) { |
| 1215 | + q = q.split("?")[1]; |
| 1216 | + } // strip question mark |
| 1217 | + if (param == null) { |
| 1218 | + return urlEncodeIfNecessary(q); |
| 1219 | + } |
| 1220 | + var pairs = q.split("&"); |
| 1221 | + for ( var i = 0; i < pairs.length; i++) { |
| 1222 | + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { |
| 1223 | + return urlEncodeIfNecessary(pairs[i].substring((pairs[i] |
| 1224 | + .indexOf("=") + 1))); |
| 1225 | + } |
| 1226 | + } |
| 1227 | + } |
| 1228 | + return ""; |
| 1229 | +}, |
| 1230 | + |
| 1231 | +// For internal usage only |
| 1232 | + expressInstallCallback : function() { |
| 1233 | + if (isExpressInstallActive) { |
| 1234 | + var obj = getElementById(EXPRESS_INSTALL_ID); |
| 1235 | + if (obj && storedAltContent) { |
| 1236 | + obj.parentNode.replaceChild(storedAltContent, obj); |
| 1237 | + if (storedAltContentId) { |
| 1238 | + setVisibility(storedAltContentId, true); |
| 1239 | + if (ua.ie && ua.win) { |
| 1240 | + storedAltContent.style.display = "block"; |
| 1241 | + } |
| 1242 | + } |
| 1243 | + if (storedCallbackFn) { |
| 1244 | + storedCallbackFn(storedCallbackObj); |
| 1245 | + } |
| 1246 | + } |
| 1247 | + isExpressInstallActive = false; |
| 1248 | + } |
| 1249 | + } |
| 1250 | + }; |
| 1251 | +}(); |
| 1252 | + |
| 1253 | +} )( window.mediaWiki, window.jQuery ); |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerKplayer.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 1254 | + text/plain |
Added: svn:eol-style |
2 | 1255 | + native |
Index: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedTypes.js |
— | — | @@ -0,0 +1,225 @@ |
| 2 | +/** |
| 3 | + * mw.EmbedTypes object handles setting and getting of supported embed types: |
| 4 | + * closely mirrors OggHandler so that its easier to share efforts in this area: |
| 5 | + * http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/OggHandler/OggPlayer.js |
| 6 | + */ |
| 7 | +( function( mw, $ ) { |
| 8 | + |
| 9 | +mw.EmbedTypes = { |
| 10 | + |
| 11 | + // MediaPlayers object ( supports methods for quering set of browser players ) |
| 12 | + mediaPlayers: null, |
| 13 | + |
| 14 | + // Detect flag for completion |
| 15 | + detect_done:false, |
| 16 | + |
| 17 | + /** |
| 18 | + * Runs the detect method and update the detect_done flag |
| 19 | + * |
| 20 | + * @constructor |
| 21 | + */ |
| 22 | + init: function() { |
| 23 | + // detect supported types |
| 24 | + this.detect(); |
| 25 | + this.detect_done = true; |
| 26 | + }, |
| 27 | + |
| 28 | + getMediaPlayers: function(){ |
| 29 | + if( this.mediaPlayers ){ |
| 30 | + return this.mediaPlayers; |
| 31 | + } |
| 32 | + this.mediaPlayers = new mediaPlayers(); |
| 33 | + // detect available players |
| 34 | + this.detectPlayers(); |
| 35 | + return this.mediaPlayers; |
| 36 | + }, |
| 37 | + |
| 38 | + /** |
| 39 | + * If the browsers supports a given mimetype |
| 40 | + * |
| 41 | + * @param {String} |
| 42 | + * mimeType Mime type for browser plug-in check |
| 43 | + */ |
| 44 | + supportedMimeType: function( mimeType ) { |
| 45 | + for ( var i =0; i < navigator.plugins.length; i++ ) { |
| 46 | + var plugin = navigator.plugins[i]; |
| 47 | + if ( typeof plugin[ mimeType ] != "undefined" ) |
| 48 | + return true; |
| 49 | + } |
| 50 | + return false; |
| 51 | + }, |
| 52 | + |
| 53 | + /** |
| 54 | + * Detects what plug-ins the client supports |
| 55 | + */ |
| 56 | + detectPlayers: function() { |
| 57 | + mw.log( "embedPlayer: running detect" ); |
| 58 | + // In Mozilla, navigator.javaEnabled() only tells us about preferences, we need to |
| 59 | + // search navigator.mimeTypes to see if it's installed |
| 60 | + try{ |
| 61 | + var javaEnabled = navigator.javaEnabled(); |
| 62 | + } catch ( e ){ |
| 63 | + |
| 64 | + } |
| 65 | + // Some browsers filter out duplicate mime types, hiding some plugins |
| 66 | + var uniqueMimesOnly = $j.browser.opera || $j.browser.safari; |
| 67 | + |
| 68 | + // Opera will switch off javaEnabled in preferences if java can't be |
| 69 | + // found. And it doesn't register an application/x-java-applet mime type like |
| 70 | + // Mozilla does. |
| 71 | + if ( javaEnabled && ( navigator.appName == 'Opera' ) ) { |
| 72 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 73 | + } |
| 74 | + |
| 75 | + // ActiveX plugins |
| 76 | + if ( $j.browser.msie ) { |
| 77 | + // check for flash |
| 78 | + if ( this.testActiveX( 'ShockwaveFlash.ShockwaveFlash' ) ) { |
| 79 | + this.mediaPlayers.addPlayer( kplayer ); |
| 80 | + // this.mediaPlayers.addPlayer( flowPlayer ); |
| 81 | + } |
| 82 | + // VLC |
| 83 | + if ( this.testActiveX( 'VideoLAN.VLCPlugin.2' ) ) { |
| 84 | + this.mediaPlayers.addPlayer( vlcPlayer ); |
| 85 | + } |
| 86 | + |
| 87 | + // Java ActiveX |
| 88 | + if ( this.testActiveX( 'JavaWebStart.isInstalled' ) ) { |
| 89 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 90 | + } |
| 91 | + |
| 92 | + // quicktime (currently off) |
| 93 | + // if ( this.testActiveX( |
| 94 | + // 'QuickTimeCheckObject.QuickTimeCheck.1' ) ) |
| 95 | + // this.mediaPlayers.addPlayer(quicktimeActiveXPlayer); |
| 96 | + } |
| 97 | + // <video> element |
| 98 | + if ( typeof HTMLVideoElement == 'object' // Firefox, Safari |
| 99 | + || typeof HTMLVideoElement == 'function' ) // Opera |
| 100 | + { |
| 101 | + // Test what codecs the native player supports: |
| 102 | + try { |
| 103 | + var dummyvid = document.createElement( "video" ); |
| 104 | + if( dummyvid.canPlayType ) { |
| 105 | + // Add the webm player |
| 106 | + if( dummyvid.canPlayType('video/webm; codecs="vp8, vorbis"') ){ |
| 107 | + this.mediaPlayers.addPlayer( webmNativePlayer ); |
| 108 | + } |
| 109 | + |
| 110 | + // Test for h264: |
| 111 | + if ( dummyvid.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"' ) ) { |
| 112 | + this.mediaPlayers.addPlayer( h264NativePlayer ); |
| 113 | + } |
| 114 | + // For now if Android assume we support h264Native (FIXME |
| 115 | + // test on real devices ) |
| 116 | + if ( mw.isAndroid2() ){ |
| 117 | + this.mediaPlayers.addPlayer( h264NativePlayer ); |
| 118 | + } |
| 119 | + |
| 120 | + // Test for ogg |
| 121 | + if ( dummyvid.canPlayType( 'video/ogg; codecs="theora,vorbis"' ) ) { |
| 122 | + this.mediaPlayers.addPlayer( oggNativePlayer ); |
| 123 | + // older versions of safari do not support canPlayType, |
| 124 | + // but xiph qt registers mimetype via quicktime plugin |
| 125 | + } else if ( this.supportedMimeType( 'video/ogg' ) ) { |
| 126 | + this.mediaPlayers.addPlayer( oggNativePlayer ); |
| 127 | + } |
| 128 | + } |
| 129 | + } catch ( e ) { |
| 130 | + mw.log( 'could not run canPlayType ' + e ); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // "navigator" plugins |
| 135 | + if ( navigator.mimeTypes && navigator.mimeTypes.length > 0 ) { |
| 136 | + for ( var i = 0; i < navigator.mimeTypes.length; i++ ) { |
| 137 | + var type = navigator.mimeTypes[i].type; |
| 138 | + var semicolonPos = type.indexOf( ';' ); |
| 139 | + if ( semicolonPos > -1 ) { |
| 140 | + type = type.substr( 0, semicolonPos ); |
| 141 | + } |
| 142 | + // mw.log( 'on type: ' + type ); |
| 143 | + var pluginName = navigator.mimeTypes[i].enabledPlugin ? navigator.mimeTypes[i].enabledPlugin.name : ''; |
| 144 | + if ( !pluginName ) { |
| 145 | + // In case it is null or undefined |
| 146 | + pluginName = ''; |
| 147 | + } |
| 148 | + if ( pluginName.toLowerCase() == 'vlc multimedia plugin' || pluginName.toLowerCase() == 'vlc multimedia plug-in' ) { |
| 149 | + this.mediaPlayers.addPlayer( vlcPlayer ); |
| 150 | + continue; |
| 151 | + } |
| 152 | + |
| 153 | + if ( type == 'application/x-java-applet' ) { |
| 154 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 155 | + continue; |
| 156 | + } |
| 157 | + |
| 158 | + if ( (type == 'video/mpeg' || type == 'video/x-msvideo') && |
| 159 | + pluginName.toLowerCase() == 'vlc multimedia plugin' ) { |
| 160 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 161 | + } |
| 162 | + |
| 163 | + if ( type == 'application/ogg' ) { |
| 164 | + if ( pluginName.toLowerCase() == 'vlc multimedia plugin' ) { |
| 165 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 166 | + // else if ( pluginName.indexOf( 'QuickTime' ) > -1 ) |
| 167 | + // this.mediaPlayers.addPlayer(quicktimeMozillaPlayer); |
| 168 | + } else { |
| 169 | + this.mediaPlayers.addPlayer( oggPluginPlayer ); |
| 170 | + } |
| 171 | + continue; |
| 172 | + } else if ( uniqueMimesOnly ) { |
| 173 | + if ( type == 'application/x-vlc-player' ) { |
| 174 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 175 | + continue; |
| 176 | + } else if ( type == 'video/quicktime' ) { |
| 177 | + // this.mediaPlayers.addPlayer(quicktimeMozillaPlayer); |
| 178 | + continue; |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + if ( type == 'application/x-shockwave-flash' ) { |
| 183 | + |
| 184 | + this.mediaPlayers.addPlayer( kplayer ); |
| 185 | + // this.mediaPlayers.addPlayer( flowPlayer ); |
| 186 | + |
| 187 | + // check version to add omtk: |
| 188 | + if( navigator.plugins["Shockwave Flash"] ){ |
| 189 | + var flashDescription = navigator.plugins["Shockwave Flash"].description; |
| 190 | + var descArray = flashDescription.split( " " ); |
| 191 | + var tempArrayMajor = descArray[2].split( "." ); |
| 192 | + var versionMajor = tempArrayMajor[0]; |
| 193 | + // mw.log("version of flash: " + versionMajor); |
| 194 | + } |
| 195 | + continue; |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + // Allow extensions to detect and add their own "players" |
| 201 | + mw.log("trigger::embedPlayerUpdateMediaPlayersEvent"); |
| 202 | + $( mw ).trigger( 'embedPlayerUpdateMediaPlayersEvent' , this.mediaPlayers ); |
| 203 | + |
| 204 | + }, |
| 205 | + |
| 206 | + /** |
| 207 | + * Test IE for activeX by name |
| 208 | + * |
| 209 | + * @param {String} |
| 210 | + * name Name of ActiveXObject to look for |
| 211 | + */ |
| 212 | + testActiveX : function ( name ) { |
| 213 | + mw.log("EmbedPlayer::detect: test testActiveX: " + name); |
| 214 | + var hasObj = true; |
| 215 | + try { |
| 216 | + // No IE, not a class called "name", it's a variable |
| 217 | + var obj = new ActiveXObject( '' + name ); |
| 218 | + } catch ( e ) { |
| 219 | + hasObj = false; |
| 220 | + } |
| 221 | + return hasObj; |
| 222 | + } |
| 223 | +}; |
| 224 | + |
| 225 | + |
| 226 | +} )( mediaWiki, jQuery ); |
Property changes on: trunk/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedTypes.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 227 | + text/plain |