Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/load.php |
— | — | @@ -30,6 +30,6 @@ |
31 | 31 | |
32 | 32 | // Respond to resource loading request |
33 | 33 | $resourceLoader = new MwEmbedResourceLoader(); |
34 | | -$resourceLoader->respond( new ResourceLoaderContext( $resourceLoader, $wgRequest ) ); |
| 34 | +$resourceLoader->respond( new MwEmbedResourceLoaderContext( $resourceLoader, $wgRequest ) ); |
35 | 35 | |
36 | 36 | |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedWebStartSetup.php |
— | — | @@ -100,6 +100,7 @@ |
101 | 101 | } |
102 | 102 | } |
103 | 103 | |
104 | | -# Add the resource loader hook: |
| 104 | +# Add the resource loader hooks |
105 | 105 | $wgHooks['ResourceLoaderRegisterModules'][] = 'MwEmbedResourceManager::registerModules'; |
| 106 | +$wgHooks['ResourceLoaderGetConfigVars'][] = 'MwEmbedResourceManager::registerConfigVars'; |
106 | 107 | |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedMediaWikiGlobalFunctions.php |
— | — | @@ -193,7 +193,47 @@ |
194 | 194 | |
195 | 195 | return $output; |
196 | 196 | } |
| 197 | +// simple mediaWiki msg retrival: |
| 198 | +$wgMessageCache = array(); |
| 199 | +function wfMsgExt( $key, $options = array() ){ |
| 200 | + global $wgLoadedMsgKeysFlag, $wgMessageCache, $mwLanguageCode; |
197 | 201 | |
| 202 | + $langKey = ( $options['language'] )? $options['language'] : 'en'; |
| 203 | + |
| 204 | + // Check if $wgMessageCache is empty, if so poulate will all its messages: |
| 205 | + if( count( $wgMessageCache) === 0){ |
| 206 | + mwEmbedLoadMsgKeys( $langKey ); |
| 207 | + } |
| 208 | + if ( isset( $wgMessageCache[ $key ] ) ) { |
| 209 | + return $wgMessageCache[ $key ]; |
| 210 | + } else { |
| 211 | + return '[' . $key . ']'; |
| 212 | + } |
| 213 | +} |
| 214 | + |
| 215 | +/** |
| 216 | + * Load all the msg keys into $wgMessageCache |
| 217 | + * @param $langKey String Language key to be used |
| 218 | + */ |
| 219 | +function mwEmbedLoadMsgKeys( $langKey ){ |
| 220 | + global $wgExtensionMessagesFiles, $wgMessageCache; |
| 221 | + |
| 222 | + foreach( $wgExtensionMessagesFiles as $msgFile ){ |
| 223 | + if( !is_file( $msgFile ) ) { |
| 224 | + throw new MWException( "Missing msgFile: " . htmlspecialchars( $msgFile ) . "\n" ); |
| 225 | + } |
| 226 | + require( $msgFile ); |
| 227 | + // First include the English fallback: |
| 228 | + $wgMessageCache = array_merge( $wgMessageCache, $messages[ 'en' ] ); |
| 229 | + |
| 230 | + // Then override with the current language: |
| 231 | + if( isset( $messages[ $langKey ] ) ) { |
| 232 | + $wgMessageCache = array_merge( $wgMessageCache, $messages[ $langKey ] ); |
| 233 | + } |
| 234 | + } |
| 235 | + $wgLoadedMsgKeysFlag = true; |
| 236 | +} |
| 237 | + |
198 | 238 | // Memcached stubs: |
199 | 239 | /** |
200 | 240 | * Get a cache key |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedAutoLoader.php |
— | — | @@ -26,6 +26,7 @@ |
27 | 27 | 'JSMin' => 'includes/libs/JSMin.php', |
28 | 28 | |
29 | 29 | # MwEmbed files ( that get autoloaded ): |
| 30 | + 'MwEmbedResourceLoaderContext' => 'includes/MwEmbedResourceLoaderContext.php', |
30 | 31 | 'MwEmbedResourceLoader' => 'includes/MwEmbedResourceLoader.php', |
31 | 32 | 'MwEmbedResourceLoaderFileModule' => 'includes/MwEmbedResourceLoaderFileModule.php', |
32 | 33 | 'MwEmbedResourceLoaderStartUpModule' => 'includes/MwEmbedResourceLoaderStartUpModule.php', |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedResourceManager.php |
— | — | @@ -10,6 +10,7 @@ |
11 | 11 | class MwEmbedResourceManager { |
12 | 12 | |
13 | 13 | protected static $moduleSet = array(); |
| 14 | + protected static $moduleConfig = array(); |
14 | 15 | |
15 | 16 | /** |
16 | 17 | * Register mwEmbeed resource set |
— | — | @@ -35,7 +36,7 @@ |
36 | 37 | $wgExtensionMessagesFiles[ 'MwEmbed.' . $moduleName ] = $fullResourcePath . '/' . $moduleName . '.i18n.php'; |
37 | 38 | |
38 | 39 | // Get the mwEmbed module config |
39 | | - $resourceList = require_once( $fullResourcePath . '/' . $moduleName . '.php' ); |
| 40 | + $resourceList = include( $fullResourcePath . '/' . $moduleName . '.php' ); |
40 | 41 | // Look for special 'messages' => 'moduleFile' key and load all modules file messages: |
41 | 42 | foreach( $resourceList as $name => $resources ){ |
42 | 43 | if( isset( $resources['messageFile'] ) && is_file( $fullResourcePath . '/' .$resources['messageFile'] ) ){ |
— | — | @@ -53,12 +54,22 @@ |
54 | 55 | ); |
55 | 56 | } |
56 | 57 | |
57 | | - // @@TODO add $moduleInfo['config'] |
| 58 | + // Check for module config ( @@TODO support per-module config ) |
| 59 | + $configPath = $fullResourcePath . '/' . $moduleName . '.config.php'; |
| 60 | + if( is_file( $configPath ) ){ |
| 61 | + self::$moduleConfig[ $moduleName ] = include( $configPath ); |
| 62 | + } |
58 | 63 | |
59 | 64 | // Add the resource list into the module set with its provided path |
60 | 65 | self::$moduleSet[ $mwEmbedResourcePath ] = $resourceList; |
61 | 66 | } |
62 | 67 | |
| 68 | + public static function registerConfigVars( &$vars ){ |
| 69 | + foreach( self::$moduleConfig as $moduleName => $config ){ |
| 70 | + $vars = array_merge( $vars, $config ); |
| 71 | + } |
| 72 | + return $vars; |
| 73 | + } |
63 | 74 | /** |
64 | 75 | * ResourceLoaderRegisterModules hook |
65 | 76 | * |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedResourceLoaderContext.php |
— | — | @@ -0,0 +1,17 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Object passed around to modules which contains information about the state |
| 6 | + * of a specific loader request |
| 7 | + */ |
| 8 | +class MwEmbedResourceLoaderContext extends ResourceLoaderContext{ |
| 9 | + |
| 10 | + public function getDirection() { |
| 11 | + if ( $this->direction === null ) { |
| 12 | + $this->direction = $this->request->getVal( 'dir' ); |
| 13 | + } |
| 14 | + return $this->direction; |
| 15 | + } |
| 16 | +} |
| 17 | + |
| 18 | +?> |
\ No newline at end of file |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/DefaultSettings.php |
— | — | @@ -13,9 +13,9 @@ |
14 | 14 | /** |
15 | 15 | * Guess at URL to resource loader load.php |
16 | 16 | */ |
17 | | -$wgResourceLoaderUrl = ( isset( $_SERVER['HTTPS'] ) )? 'https' : 'http'; |
18 | | -$wgResourceLoaderUrl.= '://' . $_SERVER['SERVER_NAME'] . dirname( $_SERVER['SCRIPT_NAME'] ) . '/load.php'; |
19 | | -$wgLoadScript = $wgResourceLoaderUrl; |
| 17 | +$protocol = ( isset( $_SERVER['HTTPS'] ) )? 'https' : 'http'; |
| 18 | +$wgServer = $protocol . '://' . $_SERVER['SERVER_NAME'] . dirname( $_SERVER['SCRIPT_NAME'] ) . '/'; |
| 19 | +$wgLoadScript = $wgServer . 'load.php'; |
20 | 20 | // The list of enabled modules |
21 | 21 | $wgMwEmbedEnabledModules = array(); |
22 | 22 | // By default we enable every module in the "modules" folder |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedMediaWikiStubs.php |
— | — | @@ -95,7 +95,7 @@ |
96 | 96 | $s .= ");\n"; |
97 | 97 | return $s; |
98 | 98 | } |
99 | | -/** |
| 99 | + /** |
100 | 100 | * Returns an escaped string suitable for inclusion in a string literal |
101 | 101 | * for JavaScript source code. |
102 | 102 | * Illegal control characters are assumed not to be present. |
— | — | @@ -192,7 +192,7 @@ |
193 | 193 | $blobs = array(); |
194 | 194 | foreach( $modules as $name => $module ){ |
195 | 195 | $messages = array(); |
196 | | - foreach ( $module->getMessages() as $key ) { |
| 196 | + foreach ( $module->getMessages() as $key ) { |
197 | 197 | $messages[$key] = wfMsgExt( $key, array( 'language' => $lang ) ); |
198 | 198 | } |
199 | 199 | if( count( $messages ) ){ |
— | — | @@ -203,6 +203,19 @@ |
204 | 204 | } |
205 | 205 | } |
206 | 206 | |
| 207 | +/** |
| 208 | + * MediaWiki abstracts the json functions with fallbacks |
| 209 | + * here we just map directly to the native php call: |
| 210 | + */ |
| 211 | +class FormatJson{ |
| 212 | + public static function encode($value, $isHtml=false){ |
| 213 | + return json_encode($value); |
| 214 | + } |
| 215 | + public static function decode( $value, $assoc=false ){ |
| 216 | + return json_decode( $value, $assoc ); |
| 217 | + } |
| 218 | +} |
| 219 | + |
207 | 220 | // Stub WebRequest |
208 | 221 | // Just serving static files, don't have a concept of wiki titles etc.) |
209 | 222 | class WebRequest { |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/MwEmbedResourceLoaderStartUpModule.php |
— | — | @@ -34,8 +34,8 @@ |
35 | 35 | * mwEmbedStartup is dependent on mwEmbedSupport |
36 | 36 | */ |
37 | 37 | public function getStartupModuleList(){ |
38 | | - // Startup modules don't respect dependency manually add 'jquery.triggerQueueCallback', 'mwEmbedSupport' |
39 | | - return array( 'jquery', 'mediawiki', 'mwEmbedSupport' ); |
| 38 | + // Startup modules don't respect dependency manually add 'mwEmbedSupport' dependencies |
| 39 | + return array( 'jquery', 'mediawiki', 'jquery.triggerQueueCallback', 'jquery.mwEmbedUtil', 'mwEmbedSupport' ); |
40 | 40 | } |
41 | 41 | |
42 | 42 | public function getScript( ResourceLoaderContext $context ) { |
— | — | @@ -46,8 +46,15 @@ |
47 | 47 | } |
48 | 48 | |
49 | 49 | protected function getConfig( $context ) { |
50 | | - // @@todo set all the configuration variables |
51 | | - $vars = array(); |
| 50 | + global $wgLoadScript; |
| 51 | + |
| 52 | + |
| 53 | + $vars = array( |
| 54 | + 'wgLoadScript' => $wgLoadScript, |
| 55 | + 'debug' => $context->getDebug(), |
| 56 | + 'skin' => $context->getSkin(), |
| 57 | + 'wgUserLanguage' => $context->getLanguage(), |
| 58 | + ); |
52 | 59 | wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) ); |
53 | 60 | |
54 | 61 | return $vars; |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/resourceloader/ResourceLoaderFileModule.php |
— | — | @@ -262,17 +262,7 @@ |
263 | 263 | } |
264 | 264 | // Collect referenced files |
265 | 265 | $this->localFileRefs = array_unique( $this->localFileRefs ); |
266 | | - // If the list has been modified since last time we cached it, update the cache |
267 | | - if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) ) { |
268 | | - $dbw = wfGetDB( DB_MASTER ); |
269 | | - $dbw->replace( 'module_deps', |
270 | | - array( array( 'md_module', 'md_skin' ) ), array( |
271 | | - 'md_module' => $this->getName(), |
272 | | - 'md_skin' => $context->getSkin(), |
273 | | - 'md_deps' => FormatJson::encode( $this->localFileRefs ), |
274 | | - ) |
275 | | - ); |
276 | | - } |
| 266 | + |
277 | 267 | return $styles; |
278 | 268 | } |
279 | 269 | |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/includes/resourceloader/ResourceLoaderStartUpModule.php |
— | — | @@ -169,9 +169,9 @@ |
170 | 170 | // Startup function |
171 | 171 | $configuration = $this->getConfig( $context ); |
172 | 172 | $registrations = self::getModuleRegistrations( $context ); |
173 | | - $out .= "var mwStartUp = function() {\n" . |
| 173 | + $out .= "var mwStartUp = function() {\n" . |
| 174 | + "\t" . Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) ) . |
174 | 175 | "\t$registrations\n" . |
175 | | - "\t" . Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) ) . |
176 | 176 | "};\n"; |
177 | 177 | |
178 | 178 | // Conditional script injection |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/EmbedPlayer.php |
— | — | @@ -1,34 +1,34 @@ |
2 | | -<?php |
| 2 | +<?php |
3 | 3 | |
4 | 4 | // Register all the EmbedPlayer modules |
5 | 5 | return array( |
6 | 6 | "mw.EmbedPlayer" => array( |
7 | 7 | 'scripts' => array( |
8 | | - "resources/mw.EmbedPlayer.js", |
| 8 | + "players/mw.EmbedPlayer.js", |
9 | 9 | "skins/mw.PlayerControlBuilder.js", |
10 | 10 | ), |
11 | 11 | 'dependencies' => array( |
| 12 | + |
12 | 13 | // jQuery dependencies: |
13 | 14 | 'jquery.hoverIntent', |
14 | 15 | 'jquery.cookie', |
15 | 16 | 'jquery.ui.mouse', |
16 | | - '$.fn.menu', |
17 | | - 'mw.style.jquerymenu', |
18 | | - '$.ui.slider' |
| 17 | + 'jquery.menu', |
| 18 | + 'jquery.ui.slider' |
19 | 19 | ), |
20 | 20 | 'styles' => "skins/mw.style.EmbedPlayer.css", |
21 | 21 | 'messageFile' => 'EmbedPlayer.i18n.php', |
22 | 22 | ), |
23 | 23 | |
24 | | - "mw.EmbedPlayerKplayer" => array( 'scripts'=> "resources/mw.EmbedPlayerKplayer.js" ), |
25 | | - "mw.EmbedPlayerGeneric" => array( 'scripts'=> "resources/mw.EmbedPlayerGeneric.js" ), |
26 | | - "mw.EmbedPlayerJava" => array( 'scripts'=> "resources/mw.EmbedPlayerJava.js"), |
27 | | - "mw.EmbedPlayerNative" => array( 'scripts'=> "resources/mw.EmbedPlayerNative.js" ), |
| 24 | + "mw.EmbedPlayerKplayer" => array( 'scripts'=> "players/mw.EmbedPlayerKplayer.js" ), |
| 25 | + "mw.EmbedPlayerGeneric" => array( 'scripts'=> "players/mw.EmbedPlayerGeneric.js" ), |
| 26 | + "mw.EmbedPlayerJava" => array( 'scripts'=> "players/mw.EmbedPlayerJava.js"), |
| 27 | + "mw.EmbedPlayerNative" => array( 'scripts'=> "players/mw.EmbedPlayerNative.js" ), |
28 | 28 | |
29 | | - "mw.EmbedPlayerVlc" => array( 'scripts'=> "resources/mw.EmbedPlayerVlc.js" ), |
| 29 | + "mw.EmbedPlayerVlc" => array( 'scripts'=> "players/mw.EmbedPlayerVlc.js" ), |
30 | 30 | |
31 | | - "mw.IFramePlayerApiServer" => array( 'scripts' => "resources/mw.IFramePlayerApiServer.js" ), |
32 | | - "mw.IFramePlayerApiClient" => array( 'scripts' => "resources/mw.IFramePlayerApiClient.js" ), |
| 31 | + "mw.IFramePlayerApiServer" => array( 'scripts' => "iframeApi/mw.IFramePlayerApiServer.js" ), |
| 32 | + "mw.IFramePlayerApiClient" => array( 'scripts' => "iframeApi/mw.IFramePlayerApiClient.js" ), |
33 | 33 | |
34 | 34 | "mw.PlayerSkinKskin" => array( 'scripts' => "skins/kskin/mw.PlayerSkinKskin.js", |
35 | 35 | 'styles' => "skins/kskin/mw.style.PlayerSkinKskin.css"), |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerGeneric.js |
— | — | @@ -0,0 +1,31 @@ |
| 2 | +/* |
| 3 | +* Simple embed object for unknown application/ogg plugin |
| 4 | +*/ |
| 5 | +mw.EmbedPlayerGeneric = { |
| 6 | + // List of supported features of the generic plugin |
| 7 | + supports: { |
| 8 | + 'playHead':false, |
| 9 | + 'pause':false, |
| 10 | + 'stop':true, |
| 11 | + 'fullscreen':false, |
| 12 | + 'timeDisplay':false, |
| 13 | + 'volumeControl':false |
| 14 | + }, |
| 15 | + |
| 16 | + // Instance name: |
| 17 | + instanceOf:'Generic', |
| 18 | + |
| 19 | + /* |
| 20 | + * Generic embed html |
| 21 | + * |
| 22 | + * @return {String} |
| 23 | + * embed code for generic ogg plugin |
| 24 | + */ |
| 25 | + doEmbedHTML: function() { |
| 26 | + $j( this ).html( |
| 27 | + '<object type="application/ogg" ' + |
| 28 | + 'width="' + this.getWidth() + '" height="' + this.getHeight() + '" ' + |
| 29 | + 'data="' + this.getSrc( this.seek_time_sec ) + '"></object>' |
| 30 | + ); |
| 31 | + } |
| 32 | +}; |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerGeneric.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 33 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/genericEmbed.js:r58211-58321 |
2 | 34 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/genericEmbed.js:r51646 |
Added: svn:eol-style |
3 | 35 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayer.js |
— | — | @@ -0,0 +1,3585 @@ |
| 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 | + '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 | + 'durationHint', |
| 36 | + |
| 37 | + // Media start time |
| 38 | + 'start', |
| 39 | + |
| 40 | + // Media end time |
| 41 | + 'end', |
| 42 | + |
| 43 | + // If the source is the default source |
| 44 | + 'default', |
| 45 | + |
| 46 | + // titleKey ( used for api lookups ) |
| 47 | + 'titleKey' |
| 48 | +] ); |
| 49 | + |
| 50 | +/** |
| 51 | + * Selector based embedPlayer processing |
| 52 | + * |
| 53 | + * @param {Function=} |
| 54 | + * callback Optional Function to be called once video interfaces |
| 55 | + * are ready |
| 56 | + * |
| 57 | + */ |
| 58 | +mw.processEmbedPlayers = function( playerSelect, callback ) { |
| 59 | + mw.log( 'EmbedPlayer:: processEmbedPlayers' ); |
| 60 | + |
| 61 | + |
| 62 | + /** |
| 63 | + * Adds a player element for the embedPlayer to rewrite |
| 64 | + * |
| 65 | + * uses embedPlayer interface on audio / video elements uses mvPlayList |
| 66 | + * interface on playlist elements |
| 67 | + * |
| 68 | + * Once a player interface is established the following chain of functions |
| 69 | + * are called; |
| 70 | + * |
| 71 | + * _this.checkPlayerSources() |
| 72 | + * _this.setupSourcePlayer() |
| 73 | + * _this.inheritEmbedPlayer() |
| 74 | + * _this.selectedPlayer.load() |
| 75 | + * _this.showPlayer() |
| 76 | + * |
| 77 | + * @param {Element} |
| 78 | + * playerElement DOM element to be swapped |
| 79 | + * @param {Object} |
| 80 | + * [Optional] attributes Extra attributes to apply to the player |
| 81 | + * interface |
| 82 | + */ |
| 83 | + var addPlayerElement = function( playerElement ) { |
| 84 | + var _this = this; |
| 85 | + mw.log('processEmbedPlayers: addElement:: ' + playerElement.id ); |
| 86 | + |
| 87 | + var waitForMeta = true; |
| 88 | + |
| 89 | + // Be sure to "stop" the target ( Firefox 3x keeps playing |
| 90 | + // the video even though its been removed from the DOM ) |
| 91 | + if( playerElement.pause ){ |
| 92 | + playerElement.pause(); |
| 93 | + } |
| 94 | + |
| 95 | + // Allow modules to override the wait for metadata flag: |
| 96 | + $j( mw ).trigger( 'checkPlayerWaitForMetaData', playerElement ); |
| 97 | + |
| 98 | + // Update the waitForMeta object if set to boolean false: |
| 99 | + waitForMeta = ( playerElement.waitForMeta === false )? false : true; |
| 100 | + |
| 101 | + |
| 102 | + // Confirm we want to wait for meta data ( if not already set to false by module ) |
| 103 | + if( waitForMeta ){ |
| 104 | + waitForMeta = waitForMetaCheck( playerElement ); |
| 105 | + } |
| 106 | + |
| 107 | + var ranPlayerSwapFlag = false; |
| 108 | + |
| 109 | + // Local callback to runPlayer swap once playerElement has metadata |
| 110 | + function runPlayerSwap() { |
| 111 | + // Don't run player swap twice |
| 112 | + if( ranPlayerSwapFlag ){ |
| 113 | + return ; |
| 114 | + } |
| 115 | + ranPlayerSwapFlag = true; |
| 116 | + mw.log("EmbedPlayer::runPlayerSwap::" + $j( playerElement ).attr('id') ); |
| 117 | + |
| 118 | + var playerInterface = new mw.EmbedPlayer( playerElement , attributes); |
| 119 | + var swapPlayer = swapEmbedPlayerElement( playerElement, playerInterface ); |
| 120 | + |
| 121 | + |
| 122 | + // Trigger the newEmbedPlayerEvent for embedPlayer interface |
| 123 | + mw.log("EmbedPlayer::addPlayerElement :trigger " + playerInterface.id ); |
| 124 | + $j( mw ).trigger ( 'newEmbedPlayerEvent', $j( '#' + playerInterface.id ).get(0) ); |
| 125 | + |
| 126 | + // Issue the checkPlayerSources call to the new player |
| 127 | + // interface: make sure to use the element that is in the DOM: |
| 128 | + $j( '#' + playerInterface.id ).get(0).checkPlayerSources(); |
| 129 | + } |
| 130 | + |
| 131 | + if( waitForMeta && mw.getConfig('EmbedPlayer.WaitForMeta' ) ) { |
| 132 | + mw.log('EmbedPlayer::WaitForMeta ( video missing height (' + |
| 133 | + $j( playerElement ).attr('height') + '), width (' + |
| 134 | + $j( playerElement ).attr('width') + ') or duration: ' + |
| 135 | + $j( playerElement ).attr('duration') |
| 136 | + ); |
| 137 | + $j( playerElement ).bind("loadedmetadata", runPlayerSwap ); |
| 138 | + |
| 139 | + // Time-out of 5 seconds ( maybe still playable but no timely |
| 140 | + // metadata ) |
| 141 | + setTimeout( runPlayerSwap, 5000 ); |
| 142 | + return ; |
| 143 | + } else { |
| 144 | + runPlayerSwap(); |
| 145 | + return ; |
| 146 | + } |
| 147 | + }; |
| 148 | + |
| 149 | + /** |
| 150 | + * Check if we should wait for metadata. |
| 151 | + * |
| 152 | + * @return true if the size is "likely" to be updated by waiting for metadata |
| 153 | + * false if the size has been set via an attribute or is already loaded |
| 154 | + */ |
| 155 | + var waitForMetaCheck = function( playerElement ){ |
| 156 | + var waitForMeta = false; |
| 157 | + |
| 158 | + // Don't wait for metadata for non html5 media elements |
| 159 | + if( !playerElement ){ |
| 160 | + return false; |
| 161 | + } |
| 162 | + if( !playerElement.tagName || ( playerElement.tagName.toLowerCase() != 'audio' && playerElement.tagName.toLowerCase() != 'video' ) ){ |
| 163 | + return false; |
| 164 | + } |
| 165 | + // If we don't have a native player don't wait for metadata |
| 166 | + if( !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'oggNative') && |
| 167 | + !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'webmNative') && |
| 168 | + !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'h264Native' ) ) |
| 169 | + { |
| 170 | + return false; |
| 171 | + } |
| 172 | + |
| 173 | + |
| 174 | + var width = $j( playerElement ).css( 'width' ); |
| 175 | + var height = $j( playerElement ).css( 'height' ); |
| 176 | + |
| 177 | + // Css video defaults ( firefox ) |
| 178 | + if( $j( playerElement ).css( 'width' ) == '300px' && |
| 179 | + $j( playerElement ).css( 'height' ) == '150px' |
| 180 | + ){ |
| 181 | + waitForMeta = true; |
| 182 | + } else { |
| 183 | + // Check if we should wait for duration: |
| 184 | + if( $j( playerElement ).attr( 'duration') || |
| 185 | + $j( playerElement ).attr('durationHint') |
| 186 | + ){ |
| 187 | + // height, width and duration set; do not wait for meta data: |
| 188 | + return false; |
| 189 | + } else { |
| 190 | + waitForMeta = true; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + // Firefox ~ sometimes~ gives -1 for unloaded media |
| 195 | + if ( $j(playerElement).attr( 'width' ) == -1 || $j(playerElement).attr( 'height' ) == -1 ) { |
| 196 | + waitForMeta = true; |
| 197 | + } |
| 198 | + |
| 199 | + // Google Chrome / safari gives 0 width height for unloaded media |
| 200 | + if( $j(playerElement).attr( 'width' ) === 0 || |
| 201 | + $j(playerElement).attr( 'height' ) === 0 |
| 202 | + ) { |
| 203 | + waitForMeta = true; |
| 204 | + } |
| 205 | + |
| 206 | + // Firefox default width height is ~sometimes~ 150 / 300 |
| 207 | + if( this.height == 150 && this.width == 300 ){ |
| 208 | + waitForMeta = true; |
| 209 | + } |
| 210 | + |
| 211 | + // Make sure we have a src attribute or source child |
| 212 | + // ( i.e not a video tag to be dynamically populated or looked up from |
| 213 | + // xml resource description ) |
| 214 | + if( waitForMeta && |
| 215 | + ( |
| 216 | + $j( playerElement ).attr('src') || |
| 217 | + $j( playerElement ).find("source[src]").length !== 0 |
| 218 | + ) |
| 219 | + ) { |
| 220 | + // Detect src type ( if no type set ) |
| 221 | + return true; |
| 222 | + } else { |
| 223 | + // playerElement is not likely to update its meta data ( no src ) |
| 224 | + return false; |
| 225 | + } |
| 226 | + }; |
| 227 | + |
| 228 | + /** |
| 229 | + * swapEmbedPlayerElement |
| 230 | + * |
| 231 | + * Takes a video element as input and swaps it out with an embed player interface |
| 232 | + * |
| 233 | + * @param {Element} |
| 234 | + * targetElement Element to be swapped |
| 235 | + * @param {Object} |
| 236 | + * playerInterface Interface to swap into the target element |
| 237 | + */ |
| 238 | + var swapEmbedPlayerElement = function( targetElement, playerInterface ) { |
| 239 | + mw.log( 'EmbedPlayer::swapEmbedPlayerElement: ' + targetElement.id ); |
| 240 | + // Create a new element to swap the player interface into |
| 241 | + var swapPlayerElement = document.createElement('div'); |
| 242 | + |
| 243 | + // Get properties / methods from playerInterface: |
| 244 | + for ( var method in playerInterface ) { |
| 245 | + if ( method != 'readyState' ) { // readyState crashes IE ( don't include ) |
| 246 | + swapPlayerElement[ method ] = playerInterface[ method ]; |
| 247 | + } |
| 248 | + } |
| 249 | + // Check if we are using native controls or Persistent player ( should keep the video embed around ) |
| 250 | + if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) { |
| 251 | + $j( targetElement ) |
| 252 | + .attr( 'id', playerInterface.pid ) |
| 253 | + .addClass( 'nativeEmbedPlayerPid' ) |
| 254 | + .show() |
| 255 | + .after( |
| 256 | + $j( swapPlayerElement ).css( 'display', 'none' ) |
| 257 | + ); |
| 258 | + |
| 259 | + } else { |
| 260 | + $j( targetElement ).replaceWith( swapPlayerElement ); |
| 261 | + } |
| 262 | + |
| 263 | + |
| 264 | + // Set swapPlayerElement has height / width set and set to loading: |
| 265 | + $j( swapPlayerElement ).css( { |
| 266 | + 'width' : playerInterface.width + 'px', |
| 267 | + 'height' : playerInterface.height + 'px' |
| 268 | + } ); |
| 269 | + |
| 270 | + // If we don't already have a loadSpiner add one: |
| 271 | + if( $j('#loadingSpinner_' + playerInterface.id ).length == 0 ){ |
| 272 | + if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) { |
| 273 | + var $spinner = $j( targetElement ) |
| 274 | + .getAbsoluteOverlaySpinner(); |
| 275 | + }else{ |
| 276 | + var $spinner = $j( swapPlayerElement ).getAbsoluteOverlaySpinner(); |
| 277 | + } |
| 278 | + $spinner.attr('id', 'loadingSpinner_' + playerInterface.id ); |
| 279 | + } |
| 280 | + return swapPlayerElement; |
| 281 | + }; |
| 282 | + |
| 283 | + |
| 284 | + |
| 285 | + |
| 286 | + |
| 287 | + // Add a loader for <div> embed player rewrites: |
| 288 | + $j( playerSelect ).each( function( index, playerElement) { |
| 289 | + |
| 290 | + // Make sure the playerElement has an id: |
| 291 | + if( !$j( playerElement ).attr('id') ){ |
| 292 | + $j( playerElement ).attr( "id", 'mwe_v' + ( index ) ); |
| 293 | + } |
| 294 | + |
| 295 | + // If we are dynamically embedding on a "div" check if we can |
| 296 | + // add a poster image behind the loader: |
| 297 | + if( playerElement.nodeName.toLowerCase() == 'div' |
| 298 | + && ( attributes.poster || $j(playerElement).attr( 'poster' ) ) ){ |
| 299 | + var posterSrc = ( attributes.poster ) ? attributes.poster : $j(playerElement).attr( 'poster' ); |
| 300 | + |
| 301 | + // Set image size: |
| 302 | + var width = $j( playerElement ).width(); |
| 303 | + var height = $j( playerElement ).height(); |
| 304 | + if( !width ){ |
| 305 | + var width = ( attributes.width )? attributes.width : '100%'; |
| 306 | + } |
| 307 | + if( !height ){ |
| 308 | + var height = ( attributes.height )? attributes.height : '100%'; |
| 309 | + } |
| 310 | + |
| 311 | + mw.log('EmbedPlayer:: set loading background: ' + posterSrc); |
| 312 | + $j( playerElement ).append( |
| 313 | + $j( '<img />' ) |
| 314 | + .attr( 'src', posterSrc) |
| 315 | + .css({ |
| 316 | + 'position' : 'absolute', |
| 317 | + 'width' : width, |
| 318 | + 'height' : height |
| 319 | + }) |
| 320 | + ); |
| 321 | + } |
| 322 | + }); |
| 323 | + |
| 324 | + // Create the Global Embed Player Manager ( if not already created ) |
| 325 | + // legacy EmbedPlayerManagerReady event ( should remove ) |
| 326 | + $j( mw ).trigger( 'EmbedPlayerManagerReady' ); |
| 327 | + |
| 328 | + // Make sure we have user preference setup for setting preferences on video selection |
| 329 | + var addedToPlayerManager = false; |
| 330 | + mw.log("EmbedPlayer:: do: " + $j( playerSelect ).length + ' players '); |
| 331 | + |
| 332 | + // Add each selected element to the player manager: |
| 333 | + $j( playerSelect ).each( function( index, playerElement) { |
| 334 | + // Make sure the video tag was not generated by our library: |
| 335 | + if( $j( playerElement ).hasClass( 'nativeEmbedPlayerPid' ) ){ |
| 336 | + $j('#loadingSpinner_' + $j( playerElement ).attr('id') ).hide(); |
| 337 | + mw.log( 'EmbedPlayer::$j.embedPlayer skip embedPlayer gennerated video: ' + playerElement ); |
| 338 | + } else { |
| 339 | + addedToPlayerManager = true; |
| 340 | + // Add the player |
| 341 | + addPlayerElement( playerElement ); |
| 342 | + } |
| 343 | + }); |
| 344 | + if( addedToPlayerManager ){ |
| 345 | + if( callback ){ |
| 346 | + $j( mw ).bind( "playersReadyEvent", callback ); |
| 347 | + } |
| 348 | + } else { |
| 349 | + // Run the callback directly if no players were added |
| 350 | + if( callback ){ |
| 351 | + callback(); |
| 352 | + } |
| 353 | + } |
| 354 | +}; |
| 355 | +/** |
| 356 | + * mediaSource class represents a source for a media element. |
| 357 | + * |
| 358 | + * @param {Element} |
| 359 | + * element: MIME type of the source. |
| 360 | + * @constructor |
| 361 | + */ |
| 362 | +function mediaSource( element ) { |
| 363 | + this.init( element ); |
| 364 | +} |
| 365 | + |
| 366 | +mediaSource.prototype = { |
| 367 | + // MIME type of the source. |
| 368 | + mimeType:null, |
| 369 | + |
| 370 | + // URI of the source. |
| 371 | + uri:null, |
| 372 | + |
| 373 | + // Title of the source. |
| 374 | + title: null, |
| 375 | + |
| 376 | + // True if the source has been marked as the default. |
| 377 | + markedDefault: false, |
| 378 | + |
| 379 | + // True if the source supports url specification of offset and duration |
| 380 | + URLTimeEncoding:false, |
| 381 | + |
| 382 | + // Start offset of the requested segment |
| 383 | + startOffset: 0, |
| 384 | + |
| 385 | + // Duration of the requested segment (0 if not known) |
| 386 | + duration:0, |
| 387 | + |
| 388 | + // Is the source playable |
| 389 | + is_playable: null, |
| 390 | + |
| 391 | + // source id |
| 392 | + id: null, |
| 393 | + |
| 394 | + // Start time in npt format |
| 395 | + start_npt: null, |
| 396 | + |
| 397 | + // End time in npt format |
| 398 | + end_npt: null, |
| 399 | + |
| 400 | + // Language of the file |
| 401 | + srclang: null, |
| 402 | + /** |
| 403 | + * MediaSource constructor: |
| 404 | + */ |
| 405 | + init : function( element ) { |
| 406 | + // mw.log('EmbedPlayer::adding mediaSource: ' + element); |
| 407 | + this.src = $j( element ).attr( 'src' ); |
| 408 | + |
| 409 | + // Set default URLTimeEncoding if we have a time url: |
| 410 | + // not ideal way to discover if content is on an oggz_chop server. |
| 411 | + // should check some other way. |
| 412 | + var pUrl = mw.parseUri ( this.src ); |
| 413 | + if ( typeof pUrl[ 'queryKey' ][ 't' ] != 'undefined' ) { |
| 414 | + this.URLTimeEncoding = true; |
| 415 | + } |
| 416 | + |
| 417 | + var sourceAttr = mw.getConfig( 'EmbedPlayer.SourceAttributes' ); |
| 418 | + |
| 419 | + for ( var i = 0; i < sourceAttr.length; i++ ) { // array loop: |
| 420 | + var attr = sourceAttr[ i ]; |
| 421 | + var attr_value = element.getAttribute( attr ); |
| 422 | + if ( attr_value ) { |
| 423 | + this[ attr ] = attr_value; |
| 424 | + } |
| 425 | + } |
| 426 | + |
| 427 | + |
| 428 | + // Set the content type: |
| 429 | + if ( $j( element ).attr( 'type' ) ) { |
| 430 | + this.mimeType = $j( element ).attr( 'type' ); |
| 431 | + }else if ( $j( element ).attr( 'content-type' ) ) { |
| 432 | + this.mimeType = $j( element ).attr( 'content-type' ); |
| 433 | + }else if( $j( element ).get(0).tagName.toLowerCase() == 'audio' ){ |
| 434 | + // If the element is an "audio" tag set audio format |
| 435 | + this.mimeType = 'audio/ogg'; |
| 436 | + } else { |
| 437 | + this.mimeType = this.detectType( this.src ); |
| 438 | + } |
| 439 | + |
| 440 | + // Conform the mime type to ogg |
| 441 | + if( this.mimeType == 'video/theora') { |
| 442 | + this.mimeType = 'video/ogg'; |
| 443 | + } |
| 444 | + |
| 445 | + if( this.mimeType == 'audio/vorbis') { |
| 446 | + this.mimeType = 'audio/ogg'; |
| 447 | + } |
| 448 | + |
| 449 | + // Check for parent elements ( supplies categories in "track" ) |
| 450 | + if( $j( element ).parent().attr('category') ) { |
| 451 | + this.category = $j( element ).parent().attr('category'); |
| 452 | + } |
| 453 | + |
| 454 | + if( $j( element ).attr( 'default' ) ){ |
| 455 | + this.markedDefault = true; |
| 456 | + } |
| 457 | + |
| 458 | + // Get the url duration ( if applicable ) |
| 459 | + this.getURLDuration(); |
| 460 | + }, |
| 461 | + |
| 462 | + /** |
| 463 | + * Update Source title via Element |
| 464 | + * |
| 465 | + * @param {Element} |
| 466 | + * element Source element to update attributes from |
| 467 | + */ |
| 468 | + updateSource: function( element ) { |
| 469 | + // for now just update the title: |
| 470 | + if ( $j( element ).attr( "title" ) ) { |
| 471 | + this.title = $j( element ).attr( "title" ); |
| 472 | + } |
| 473 | + }, |
| 474 | + |
| 475 | + /** |
| 476 | + * Updates the src time and start & end |
| 477 | + * |
| 478 | + * @param {String} |
| 479 | + * start_time: in NPT format |
| 480 | + * @param {String} |
| 481 | + * end_time: in NPT format |
| 482 | + */ |
| 483 | + updateSrcTime: function ( start_npt, end_npt ) { |
| 484 | + // mw.log("f:updateSrcTime: "+ start_npt+'/'+ end_npt + ' from org: ' + |
| 485 | + // this.start_npt+ '/'+this.end_npt); |
| 486 | + // mw.log("pre uri:" + this.src); |
| 487 | + // if we have time we can use: |
| 488 | + if ( this.URLTimeEncoding ) { |
| 489 | + // make sure its a valid start time / end time (else set default) |
| 490 | + if ( !mw.npt2seconds( start_npt ) ) { |
| 491 | + start_npt = this.start_npt; |
| 492 | + } |
| 493 | + |
| 494 | + if ( !mw.npt2seconds( end_npt ) ) { |
| 495 | + end_npt = this.end_npt; |
| 496 | + } |
| 497 | + |
| 498 | + this.src = mw.replaceUrlParams( this.src, { |
| 499 | + 't': start_npt + '/' + end_npt |
| 500 | + }); |
| 501 | + |
| 502 | + // update the duration |
| 503 | + this.getURLDuration(); |
| 504 | + } |
| 505 | + }, |
| 506 | + |
| 507 | + /** |
| 508 | + * Sets the duration and sets the end time if unset |
| 509 | + * |
| 510 | + * @param {Float} |
| 511 | + * duration: in seconds |
| 512 | + */ |
| 513 | + setDuration: function ( duration ) { |
| 514 | + this.duration = duration; |
| 515 | + if ( !this.end_npt ) { |
| 516 | + this.end_npt = mw.seconds2npt( this.startOffset + duration ); |
| 517 | + } |
| 518 | + }, |
| 519 | + |
| 520 | + /** |
| 521 | + * MIME type accessors function. |
| 522 | + * |
| 523 | + * @return {String} the MIME type of the source. |
| 524 | + */ |
| 525 | + getMIMEType: function() { |
| 526 | + if( this.mimeType ) { |
| 527 | + return this.mimeType; |
| 528 | + } |
| 529 | + this.mimeType = this.detectType( this.src ); |
| 530 | + return this.mimeType; |
| 531 | + }, |
| 532 | + |
| 533 | + /** |
| 534 | + * URI function. |
| 535 | + * |
| 536 | + * @param {Number} |
| 537 | + * serverSeekTime Int: Used to adjust the URI for url based |
| 538 | + * seeks) |
| 539 | + * @return {String} the URI of the source. |
| 540 | + */ |
| 541 | + getSrc: function( serverSeekTime ) { |
| 542 | + if ( !serverSeekTime || !this.URLTimeEncoding ) { |
| 543 | + return this.src; |
| 544 | + } |
| 545 | + var endvar = ''; |
| 546 | + if ( this.end_npt ) { |
| 547 | + endvar = '/' + this.end_npt; |
| 548 | + } |
| 549 | + return mw.replaceUrlParams( this.src, |
| 550 | + { |
| 551 | + 't': mw.seconds2npt( serverSeekTime ) + endvar |
| 552 | + } |
| 553 | + ); |
| 554 | + }, |
| 555 | + |
| 556 | + /** |
| 557 | + * Title accessor function. |
| 558 | + * |
| 559 | + * @return {String} Title of the source. |
| 560 | + */ |
| 561 | + getTitle : function() { |
| 562 | + if( this.title ){ |
| 563 | + return this.title; |
| 564 | + } |
| 565 | + |
| 566 | + // Return a Title based on mime type: |
| 567 | + switch( this.getMIMEType() ) { |
| 568 | + case 'video/h264' : |
| 569 | + return gM( 'mwe-embedplayer-video-h264' ); |
| 570 | + break; |
| 571 | + case 'video/x-flv' : |
| 572 | + return gM( 'mwe-embedplayer-video-flv' ); |
| 573 | + break; |
| 574 | + case 'video/webm' : |
| 575 | + return gM( 'mwe-embedplayer-video-webm'); |
| 576 | + break; |
| 577 | + case 'video/ogg' : |
| 578 | + return gM( 'mwe-embedplayer-video-ogg' ); |
| 579 | + break; |
| 580 | + case 'audio/ogg' : |
| 581 | + return gM( 'mwe-embedplayer-video-audio' ); |
| 582 | + break; |
| 583 | + case 'video/mpeg' : |
| 584 | + return 'MPEG video'; // FIXME: i18n |
| 585 | + break; |
| 586 | + case 'video/x-msvideo' : |
| 587 | + return 'AVI video'; // FIXME: i18n |
| 588 | + break; |
| 589 | + } |
| 590 | + |
| 591 | + // Return tilte based on file name: |
| 592 | + var urlParts = mw.parseUri( this.getSrc() ); |
| 593 | + if( urlParts.file ){ |
| 594 | + return urlParts.file; |
| 595 | + } |
| 596 | + |
| 597 | + // Return the mime type string if not known type. |
| 598 | + return this.mimeType; |
| 599 | + }, |
| 600 | + |
| 601 | + /** |
| 602 | + * |
| 603 | + * Get Duration of the media in milliseconds from the source url. |
| 604 | + * |
| 605 | + * Supports media_url?t=ntp_start/ntp_end url request format |
| 606 | + */ |
| 607 | + getURLDuration : function() { |
| 608 | + // check if we have a URLTimeEncoding: |
| 609 | + if ( this.URLTimeEncoding ) { |
| 610 | + var annoURL = mw.parseUri( this.src ); |
| 611 | + if ( annoURL.queryKey.t ) { |
| 612 | + var times = annoURL.queryKey.t.split( '/' ); |
| 613 | + this.start_npt = times[0]; |
| 614 | + this.end_npt = times[1]; |
| 615 | + this.startOffset = mw.npt2seconds( this.start_npt ); |
| 616 | + this.duration = mw.npt2seconds( this.end_npt ) - this.startOffset; |
| 617 | + } else { |
| 618 | + // look for this info as attributes |
| 619 | + if ( this.startOffset ) { |
| 620 | + this.start_npt = mw.seconds2npt( this.startOffset ); |
| 621 | + } |
| 622 | + if ( this.duration ) { |
| 623 | + this.end_npt = mw.seconds2npt( parseInt( this.duration ) + parseInt( this.startOffset ) ); |
| 624 | + } |
| 625 | + } |
| 626 | + } |
| 627 | + }, |
| 628 | + |
| 629 | + /** |
| 630 | + * Attempts to detect the type of a media file based on the URI. |
| 631 | + * |
| 632 | + * @param {String} |
| 633 | + * uri URI of the media file. |
| 634 | + * @return {String} The guessed MIME type of the file. |
| 635 | + */ |
| 636 | + detectType: function( uri ) { |
| 637 | + // NOTE: if media is on the same server as the javascript |
| 638 | + // we can issue a HEAD request and read the mime type of the media... |
| 639 | + // ( this will detect media mime type independently of the url name ) |
| 640 | + // http://www.jibbering.com/2002/4/httprequest.html |
| 641 | + var urlParts = mw.parseUri( uri ); |
| 642 | + // Get the extension from the url or from the relative name: |
| 643 | + var ext = ( urlParts.file )? /[^.]+$/.exec( urlParts.file ) : /[^.]+$/.exec( uri ); |
| 644 | + switch( ext.toString().toLowerCase() ) { |
| 645 | + case 'smil': |
| 646 | + case 'sml': |
| 647 | + return 'application/smil'; |
| 648 | + break; |
| 649 | + case 'm4v': |
| 650 | + case 'mp4': |
| 651 | + return 'video/h264'; |
| 652 | + break; |
| 653 | + case 'webm': |
| 654 | + return 'video/webm'; |
| 655 | + break; |
| 656 | + case 'srt': |
| 657 | + return 'text/x-srt'; |
| 658 | + break; |
| 659 | + case 'flv': |
| 660 | + return 'video/x-flv'; |
| 661 | + break; |
| 662 | + case 'ogg': |
| 663 | + case 'ogv': |
| 664 | + return 'video/ogg'; |
| 665 | + break; |
| 666 | + case 'oga': |
| 667 | + return 'audio/ogg'; |
| 668 | + break; |
| 669 | + case 'anx': |
| 670 | + return 'video/ogg'; |
| 671 | + break; |
| 672 | + case 'xml': |
| 673 | + return 'text/xml'; |
| 674 | + break; |
| 675 | + case 'avi': |
| 676 | + return 'video/x-msvideo'; |
| 677 | + break; |
| 678 | + case 'mpg': |
| 679 | + return 'video/mpeg'; |
| 680 | + break; |
| 681 | + case 'mpeg': |
| 682 | + return 'video/mpeg'; |
| 683 | + break; |
| 684 | + } |
| 685 | + mw.log( "Error: could not detect type of media src: " + uri ); |
| 686 | + } |
| 687 | +}; |
| 688 | + |
| 689 | +/** |
| 690 | + * A media element corresponding to a <video> element. |
| 691 | + * |
| 692 | + * It is implemented as a collection of mediaSource objects. The media sources |
| 693 | + * will be initialized from the <video> element, its child <source> elements, |
| 694 | + * and/or the ROE file referenced by the <video> element. |
| 695 | + * |
| 696 | + * @param {element} |
| 697 | + * videoElement <video> element used for initialization. |
| 698 | + * @constructor |
| 699 | + */ |
| 700 | +function mediaElement( element ) { |
| 701 | + this.init( element ); |
| 702 | +} |
| 703 | + |
| 704 | +mediaElement.prototype = { |
| 705 | + |
| 706 | + // The array of mediaSource elements. |
| 707 | + sources: null, |
| 708 | + |
| 709 | + // flag for ROE data being added. |
| 710 | + addedROEData: false, |
| 711 | + |
| 712 | + // Selected mediaSource element. |
| 713 | + selectedSource: null, |
| 714 | + |
| 715 | + // Media element thumbnail |
| 716 | + thumbnail: null, |
| 717 | + |
| 718 | + // Media element linkback |
| 719 | + linkback: null, |
| 720 | + |
| 721 | + /** |
| 722 | + * Media Element constructor |
| 723 | + * |
| 724 | + * Sets up a mediaElement from a provided top level "video" element adds any |
| 725 | + * child sources that are found |
| 726 | + * |
| 727 | + * @param {Element} |
| 728 | + * videoElement Element that has src attribute or has children |
| 729 | + * source elements |
| 730 | + */ |
| 731 | + init: function( videoElement ) { |
| 732 | + var _this = this; |
| 733 | + mw.log( "EmbedPlayer::mediaElement:init:" + videoElement.id ); |
| 734 | + this.sources = new Array(); |
| 735 | + |
| 736 | + // Process the videoElement as a source element: |
| 737 | + if ( $j( videoElement ).attr( "src" ) ) { |
| 738 | + _this.tryAddSource( videoElement ); |
| 739 | + } |
| 740 | + |
| 741 | + // Process elements source children |
| 742 | + $j( videoElement ).find( 'source,track' ).each( function( ) { |
| 743 | + _this.tryAddSource( this ); |
| 744 | + } ); |
| 745 | + }, |
| 746 | + |
| 747 | + /** |
| 748 | + * Updates the time request for all sources that have a standard time |
| 749 | + * request argument (ie &t=start_time/end_time) |
| 750 | + * |
| 751 | + * @param {String} |
| 752 | + * start_npt Start time in npt format |
| 753 | + * @param {String} |
| 754 | + * end_npt End time in npt format |
| 755 | + */ |
| 756 | + updateSourceTimes: function( start_npt, end_npt ) { |
| 757 | + var _this = this; |
| 758 | + $j.each( this.sources, function( inx, mediaSource ) { |
| 759 | + mediaSource.updateSrcTime( start_npt, end_npt ); |
| 760 | + } ); |
| 761 | + }, |
| 762 | + |
| 763 | + /** |
| 764 | + * Check for Timed Text tracks |
| 765 | + * |
| 766 | + * @return {Boolean} True if text tracks exist, false if no text tracks are |
| 767 | + * found |
| 768 | + */ |
| 769 | + textSourceExists: function() { |
| 770 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 771 | + if ( this.sources[i].mimeType == 'text/cmml' || |
| 772 | + this.sources[i].mimeType == 'text/x-srt' ) |
| 773 | + { |
| 774 | + return true; |
| 775 | + } |
| 776 | + }; |
| 777 | + return false; |
| 778 | + }, |
| 779 | + |
| 780 | + /** |
| 781 | + * Returns the array of mediaSources of this element. |
| 782 | + * |
| 783 | + * @param {String} |
| 784 | + * [mimeFilter] Filter criteria for set of mediaSources to return |
| 785 | + * @return {Array} mediaSource elements. |
| 786 | + */ |
| 787 | + getSources: function( mimeFilter ) { |
| 788 | + if ( !mimeFilter ) { |
| 789 | + return this.sources; |
| 790 | + } |
| 791 | + // Apply mime filter: |
| 792 | + var source_set = new Array(); |
| 793 | + for ( var i = 0; i < this.sources.length ; i++ ) { |
| 794 | + if ( this.sources[i].mimeType && |
| 795 | + this.sources[i].mimeType.indexOf( mimeFilter ) != -1 ) |
| 796 | + { |
| 797 | + source_set.push( this.sources[i] ); |
| 798 | + } |
| 799 | + } |
| 800 | + return source_set; |
| 801 | + }, |
| 802 | + |
| 803 | + /** |
| 804 | + * Selects a source by id |
| 805 | + * |
| 806 | + * @param {String} |
| 807 | + * source_id Id of the source to select. |
| 808 | + * @return {MediaSource} The selected mediaSource or null if not found |
| 809 | + */ |
| 810 | + getSourceById:function( source_id ) { |
| 811 | + for ( var i = 0; i < this.sources.length ; i++ ) { |
| 812 | + if ( this.sources[i].id == source_id ) { |
| 813 | + return this.sources[i]; |
| 814 | + } |
| 815 | + } |
| 816 | + return null; |
| 817 | + }, |
| 818 | + |
| 819 | + /** |
| 820 | + * Selects a particular source for playback updating the "selectedSource" |
| 821 | + * |
| 822 | + * @param {Number} |
| 823 | + * index Index of source element to set as selectedSource |
| 824 | + */ |
| 825 | + selectSource:function( index ) { |
| 826 | + mw.log( 'EmbedPlayer::mediaElement:selectSource:' + index ); |
| 827 | + var playableSources = this.getPlayableSources(); |
| 828 | + for ( var i = 0; i < playableSources.length; i++ ) { |
| 829 | + if ( i == index ) { |
| 830 | + this.selectedSource = playableSources[i]; |
| 831 | + // Update the user selected format: |
| 832 | + mw.EmbedTypes.getMediaPlayers().setFormatPreference( playableSources[i].mimeType ); |
| 833 | + break; |
| 834 | + } |
| 835 | + } |
| 836 | + }, |
| 837 | + |
| 838 | + /** |
| 839 | + * Selects the default source via cookie preference, default marked, or by |
| 840 | + * id order |
| 841 | + */ |
| 842 | + autoSelectSource: function() { |
| 843 | + mw.log( 'EmbedPlayer::mediaElement::autoSelectSource' ); |
| 844 | + var _this = this; |
| 845 | + // Select the default source |
| 846 | + var playableSources = this.getPlayableSources(); |
| 847 | + var flash_flag = ogg_flag = false; |
| 848 | + |
| 849 | + // Check if there are any playableSources |
| 850 | + if( playableSources.length == 0 ){ |
| 851 | + return false; |
| 852 | + } |
| 853 | + var setSelectedSource = function( source ){ |
| 854 | + _this.selectedSource = source; |
| 855 | + }; |
| 856 | + |
| 857 | + // Set via user-preference |
| 858 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 859 | + var mimeType = playableSources[source].mimeType; |
| 860 | + if ( mw.EmbedTypes.getMediaPlayers().preference[ 'format_preference' ] == mimeType ) { |
| 861 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via preference: ' + playableSources[source].mimeType ); |
| 862 | + setSelectedSource( playableSources[source] ); |
| 863 | + return true; |
| 864 | + } |
| 865 | + } |
| 866 | + |
| 867 | + // Set via module driven preference: |
| 868 | + $j(this).trigger( 'AutoSelectSource', [ playableSources ] ); |
| 869 | + if( _this.selectedSource ){ |
| 870 | + return true; |
| 871 | + } |
| 872 | + |
| 873 | + // Set via marked default: |
| 874 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 875 | + if ( playableSources[ source ].markedDefault ) { |
| 876 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via marked default: ' + playableSources[source].markedDefault ); |
| 877 | + setSelectedSource( playableSources[source] ); |
| 878 | + return true; |
| 879 | + } |
| 880 | + } |
| 881 | + |
| 882 | + // Prefer native playback ( and prefer WebM over ogg and h.264 ) |
| 883 | + var namedSources = []; |
| 884 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 885 | + var mimeType = playableSources[source].mimeType; |
| 886 | + var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ); |
| 887 | + if ( player && player.library == 'Native' ) { |
| 888 | + switch( player.id ){ |
| 889 | + case 'oggNative': |
| 890 | + namedSources['ogg'] = playableSources[ source ]; |
| 891 | + break; |
| 892 | + case 'webmNative': |
| 893 | + namedSources['webm'] = playableSources[ source ]; |
| 894 | + break; |
| 895 | + case 'h264Native': |
| 896 | + namedSources['h264'] = playableSources[ source ]; |
| 897 | + break; |
| 898 | + } |
| 899 | + } |
| 900 | + } |
| 901 | + var codecPref =mw.getConfig( 'EmbedPlayer.CodecPreference'); |
| 902 | + for(var i =0; i < codecPref.length; i++){ |
| 903 | + var codec = codecPref[ i ]; |
| 904 | + if( namedSources[ codec ]){ |
| 905 | + setSelectedSource( namedSources[ codec ] ); |
| 906 | + return true; |
| 907 | + } |
| 908 | + }; |
| 909 | + |
| 910 | + |
| 911 | + // Set h264 via native or flash fallback |
| 912 | + for ( var source = 0; source < playableSources.length; source++ ) { |
| 913 | + var mimeType = playableSources[source].mimeType; |
| 914 | + var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ); |
| 915 | + if ( mimeType == 'video/h264' |
| 916 | + && player |
| 917 | + && ( |
| 918 | + player.library == 'Native' |
| 919 | + || |
| 920 | + player.library == 'Kplayer' |
| 921 | + ) |
| 922 | + ) { |
| 923 | + mw.log('EmbedPlayer::autoSelectSource: Set h264 via native or flash fallback'); |
| 924 | + setSelectedSource( playableSources[ source ] ); |
| 925 | + return true; |
| 926 | + } |
| 927 | + }; |
| 928 | + |
| 929 | + // Else just select first source |
| 930 | + if ( !this.selectedSource ) { |
| 931 | + mw.log( 'EmbedPlayer::autoSelectSource: Set via first source:' + playableSources[0] ); |
| 932 | + setSelectedSource( playableSources[0] ); |
| 933 | + return true; |
| 934 | + } |
| 935 | + // No Source found so no source selected |
| 936 | + return false; |
| 937 | + }, |
| 938 | + |
| 939 | + /** |
| 940 | + * check if the mime is ogg |
| 941 | + */ |
| 942 | + isOgg: function( mimeType ){ |
| 943 | + if ( mimeType == 'video/ogg' |
| 944 | + || mimeType == 'ogg/video' |
| 945 | + || mimeType == 'video/annodex' |
| 946 | + || mimeType == 'application/ogg' |
| 947 | + ) { |
| 948 | + return true; |
| 949 | + } |
| 950 | + return false; |
| 951 | + }, |
| 952 | + |
| 953 | + /** |
| 954 | + * Returns the thumbnail URL for the media element. |
| 955 | + * |
| 956 | + * @returns {String} thumbnail URL |
| 957 | + */ |
| 958 | + getPosterSrc: function( ) { |
| 959 | + return this.poster; |
| 960 | + }, |
| 961 | + |
| 962 | + /** |
| 963 | + * Checks whether there is a stream of a specified MIME type. |
| 964 | + * |
| 965 | + * @param {String} |
| 966 | + * mimeType MIME type to check. |
| 967 | + * @return {Boolean} true if sources include MIME false if not. |
| 968 | + */ |
| 969 | + hasStreamOfMIMEType: function( mimeType ) |
| 970 | + { |
| 971 | + for ( var i = 0; i < this.sources.length; i++ ) |
| 972 | + { |
| 973 | + if ( this.sources[i].getMIMEType() == mimeType ){ |
| 974 | + return true; |
| 975 | + } |
| 976 | + } |
| 977 | + return false; |
| 978 | + }, |
| 979 | + |
| 980 | + /** |
| 981 | + * Checks if media is a playable type |
| 982 | + */ |
| 983 | + isPlayableType: function( mimeType ) { |
| 984 | + if ( mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ) ) { |
| 985 | + return true; |
| 986 | + } else { |
| 987 | + return false; |
| 988 | + } |
| 989 | + }, |
| 990 | + |
| 991 | + /** |
| 992 | + * Adds a single mediaSource using the provided element if the element has a |
| 993 | + * 'src' attribute. |
| 994 | + * |
| 995 | + * @param {Element} |
| 996 | + * element <video>, <source> or <mediaSource> <text> element. |
| 997 | + */ |
| 998 | + tryAddSource: function( element ) { |
| 999 | + // mw.log( 'f:tryAddSource:' + $j( element ).attr( "src" ) ); |
| 1000 | + var newSrc = $j( element ).attr( 'src' ); |
| 1001 | + if ( newSrc ) { |
| 1002 | + // make sure an existing element with the same src does not already |
| 1003 | + // exist: |
| 1004 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 1005 | + if ( this.sources[i].src == newSrc ) { |
| 1006 | + // Source already exists update any new attr: |
| 1007 | + this.sources[i].updateSource( element ); |
| 1008 | + return this.sources[i]; |
| 1009 | + } |
| 1010 | + } |
| 1011 | + } |
| 1012 | + // Create a new source |
| 1013 | + var source = new mediaSource( element ); |
| 1014 | + |
| 1015 | + this.sources.push( source ); |
| 1016 | + // mw.log( 'tryAddSource: added source ::' + source + 'sl:' + |
| 1017 | + // this.sources.length ); |
| 1018 | + return source; |
| 1019 | + }, |
| 1020 | + |
| 1021 | + /** |
| 1022 | + * Get playable sources |
| 1023 | + * |
| 1024 | + * @returns {Array} of playable sources |
| 1025 | + */ |
| 1026 | + getPlayableSources: function() { |
| 1027 | + var playableSources = []; |
| 1028 | + for ( var i = 0; i < this.sources.length; i++ ) { |
| 1029 | + if ( this.isPlayableType( this.sources[i].mimeType ) ) { |
| 1030 | + playableSources.push( this.sources[i] ); |
| 1031 | + } else { |
| 1032 | + mw.log( "type " + this.sources[i].mimeType + ' is not playable' ); |
| 1033 | + } |
| 1034 | + }; |
| 1035 | + return playableSources; |
| 1036 | + }, |
| 1037 | + |
| 1038 | + /** |
| 1039 | + * Imports media sources from ROE data. |
| 1040 | + * |
| 1041 | + * @param roe_data |
| 1042 | + * ROE data. |
| 1043 | + */ |
| 1044 | + addROE: function( roe_data ) { |
| 1045 | + mw.log( 'EmbedPlayer::mediaElement:addROE' ); |
| 1046 | + this.addedROEData = true; |
| 1047 | + var _this = this; |
| 1048 | + if ( roe_data ) { |
| 1049 | + var $roeParsed = $j( roe_data.pay_load ); |
| 1050 | + |
| 1051 | + // Add media sources: |
| 1052 | + $roeParsed.find("mediaSource").each( function( inx, source ) { |
| 1053 | + _this.tryAddSource( source ); |
| 1054 | + } ); |
| 1055 | + |
| 1056 | + // Set the thumbnail: |
| 1057 | + $roeParsed.find( 'img' ).each( function( inx, n ) { |
| 1058 | + if ( $j( n ).attr( "id" ) == "stream_thumb" ) { |
| 1059 | + mw.log( 'roe:set thumb to ' + $j( n ).attr( "src" ) ); |
| 1060 | + _this.poster = $j( n ).attr( "src" ); |
| 1061 | + } |
| 1062 | + } ); |
| 1063 | + |
| 1064 | + // Set the linkback: |
| 1065 | + $roeParsed.find( 'link' ).each( function( inx, n ) { |
| 1066 | + if ( $j( n ).attr( 'id' ) == 'html_linkback' ) { |
| 1067 | + mw.log( 'roe:set linkback to ' + $j( n ).attr( "href" ) ); |
| 1068 | + _this.linkback = $j( n ).attr( 'href' ); |
| 1069 | + } |
| 1070 | + } ); |
| 1071 | + } else { |
| 1072 | + mw.log( 'ROE data empty.' ); |
| 1073 | + } |
| 1074 | + } |
| 1075 | +}; |
| 1076 | + |
| 1077 | + |
| 1078 | +/** |
| 1079 | + * Base embedPlayer object |
| 1080 | + * |
| 1081 | + * @param {Element} |
| 1082 | + * element, the element used for initialization. |
| 1083 | + * @param {Object} |
| 1084 | + * customAttributes Attributes for the video interface that are not |
| 1085 | + * already element attributes |
| 1086 | + * @constructor |
| 1087 | + */ |
| 1088 | +mw.EmbedPlayer = function( element, customAttributes ) { |
| 1089 | + return this.init( element, customAttributes ); |
| 1090 | +}; |
| 1091 | + |
| 1092 | +mw.EmbedPlayer.prototype = { |
| 1093 | + |
| 1094 | + // The mediaElement object containing all mediaSource objects |
| 1095 | + 'mediaElement' : null, |
| 1096 | + |
| 1097 | + // Object that describes the supported feature set of the underling plugin / |
| 1098 | + // Support list is described in PlayerControlBuilder components |
| 1099 | + 'supports': { }, |
| 1100 | + |
| 1101 | + // Preview mode flag, |
| 1102 | + // some plugins don't seek accurately but in preview mode we need |
| 1103 | + // accurate seeks so we do tricks like hide the image until its ready |
| 1104 | + 'previewMode' : false, |
| 1105 | + |
| 1106 | + // Ready to play |
| 1107 | + // NOTE: we should switch over to setting the html5 video ready state |
| 1108 | + 'readyToPlay' : false, |
| 1109 | + |
| 1110 | + // Stores the loading errors |
| 1111 | + 'loadError' : false, |
| 1112 | + |
| 1113 | + // Thumbnail updating flag ( to avoid rewriting an thumbnail thats already |
| 1114 | + // being updated) |
| 1115 | + 'thumbnail_updating' : false, |
| 1116 | + |
| 1117 | + // Poster display flag |
| 1118 | + 'posterDisplayed' : true, |
| 1119 | + |
| 1120 | + // Local variable to hold CMML meeta data about the current clip |
| 1121 | + // for more on CMML see: http://wiki.xiph.org/CMML |
| 1122 | + 'cmmlData': null, |
| 1123 | + |
| 1124 | + // Stores the seek time request, Updated by the doSeek function |
| 1125 | + 'serverSeekTime' : 0, |
| 1126 | + |
| 1127 | + // If the embedPlayer is current 'seeking' |
| 1128 | + 'seeking' : false, |
| 1129 | + |
| 1130 | + // Percent of the clip buffered: |
| 1131 | + 'bufferedPercent' : 0, |
| 1132 | + |
| 1133 | + // Holds the timer interval function |
| 1134 | + 'monitorTimerId' : null, |
| 1135 | + |
| 1136 | + // Buffer flags |
| 1137 | + 'bufferStartFlag' : false, |
| 1138 | + 'bufferEndFlag' : false, |
| 1139 | + |
| 1140 | + // For supporting media fragments stores the play end time |
| 1141 | + 'pauseTime' : null, |
| 1142 | + |
| 1143 | + // On done playing |
| 1144 | + 'donePlayingCount' : 0 |
| 1145 | + , |
| 1146 | + // if player events should be Propagated |
| 1147 | + '_propagateEvents': true, |
| 1148 | + |
| 1149 | + // If the onDone interface should be displayed |
| 1150 | + 'onDoneInterfaceFlag': true, |
| 1151 | + |
| 1152 | + |
| 1153 | + /** |
| 1154 | + * embedPlayer |
| 1155 | + * |
| 1156 | + * @constructor |
| 1157 | + * |
| 1158 | + * @param {Element} |
| 1159 | + * element DOM element that we are building the player interface for. |
| 1160 | + * @param {Object} |
| 1161 | + * customAttributes Attributes supplied via argument (rather than applied to the element) |
| 1162 | + */ |
| 1163 | + init: function( element, customAttributes ) { |
| 1164 | + var _this = this; |
| 1165 | + mw.log('EmbedPlayer: initEmbedPlayer: ' + $j(element).width() ); |
| 1166 | + // Set customAttributes if unset: |
| 1167 | + if ( !customAttributes ) { |
| 1168 | + customAttributes = { }; |
| 1169 | + } |
| 1170 | + var playerAttributes = mw.getConfig( 'EmbedPlayer.Attributes' ); |
| 1171 | + |
| 1172 | + // Setup the player Interface from supported attributes: |
| 1173 | + for ( var attr in playerAttributes ) { |
| 1174 | + if ( customAttributes[ attr ] || customAttributes[ attr ] === false ) { |
| 1175 | + this[ attr ] = customAttributes[ attr ]; |
| 1176 | + } else if ( element.getAttribute( attr ) != null ) { |
| 1177 | + // boolean attributes |
| 1178 | + if( element.getAttribute( attr ) == '' ){ |
| 1179 | + this[ attr ] = true; |
| 1180 | + } else { |
| 1181 | + this[ attr ] = element.getAttribute( attr ); |
| 1182 | + } |
| 1183 | + } else { |
| 1184 | + this[attr] = playerAttributes[attr]; |
| 1185 | + } |
| 1186 | + // string -> boolean |
| 1187 | + if( this[ attr ] == "false" ) this[attr] = false; |
| 1188 | + if( this[ attr ] == "true" ) this[attr] = true; |
| 1189 | + } |
| 1190 | + // |
| 1191 | + |
| 1192 | + |
| 1193 | + if( this.apiTitleKey ){ |
| 1194 | + this.apiTitleKey = decodeURI( this.apiTitleKey ); |
| 1195 | + } |
| 1196 | + |
| 1197 | + // Hide "controls" if using native player controls: |
| 1198 | + if( this.useNativePlayerControls() ){ |
| 1199 | + _this.controls = false; |
| 1200 | + } |
| 1201 | + |
| 1202 | + // Set the poster: |
| 1203 | + if ( $j( element ).attr( 'thumbnail' ) ) { |
| 1204 | + _this.poster = $j( element ).attr( 'thumbnail' ); |
| 1205 | + } |
| 1206 | + if ( $j( element ).attr( 'poster' ) ) { |
| 1207 | + _this.poster = $j( element ).attr( 'poster' ); |
| 1208 | + } |
| 1209 | + |
| 1210 | + // Set the skin name from the class |
| 1211 | + var sn = $j(element).attr( 'class' ); |
| 1212 | + |
| 1213 | + if ( sn && sn != '' ) { |
| 1214 | + for ( var n = 0; n < mw.validSkins.length; n++ ) { |
| 1215 | + if ( sn.indexOf( mw.validSkins[n].toLowerCase() ) !== -1 ) { |
| 1216 | + this.skinName = mw.validSkins[ n ]; |
| 1217 | + } |
| 1218 | + } |
| 1219 | + } |
| 1220 | + |
| 1221 | + // Set the default skin if unset: |
| 1222 | + if ( !this.skinName ) { |
| 1223 | + this.skinName = mw.getConfig( 'EmbedPlayer.SkinName' ); |
| 1224 | + } |
| 1225 | + |
| 1226 | + if( !this.monitorRate ){ |
| 1227 | + this.monitorRate = mw.getConfig( 'EmbedPlayer.MonitorRate' ); |
| 1228 | + } |
| 1229 | + |
| 1230 | + // Make sure startOffset is cast as an float: |
| 1231 | + if ( this.startOffset && this.startOffset.split( ':' ).length >= 2 ) { |
| 1232 | + this.startOffset = parseFloat( mw.npt2seconds( this.startOffset ) ); |
| 1233 | + } |
| 1234 | + |
| 1235 | + // Make sure offset is in float: |
| 1236 | + this.startOffset = parseFloat( this.startOffset ); |
| 1237 | + |
| 1238 | + // Set the source duration ( if provided in the element metaData or |
| 1239 | + // durationHint ) |
| 1240 | + if ( $j( element ).attr( 'duration' ) ) { |
| 1241 | + _this.duration = $j( element ).attr( 'duration' ); |
| 1242 | + } |
| 1243 | + |
| 1244 | + if ( !_this.duration && $j( element ).attr( 'durationHint' ) ) { |
| 1245 | + _this.durationHint = $j( element ).attr( 'durationHint' ); |
| 1246 | + // Convert duration hint if needed: |
| 1247 | + _this.duration = mw.npt2seconds( _this.durationHint ); |
| 1248 | + } |
| 1249 | + |
| 1250 | + // Make sure duration is a float: |
| 1251 | + this.duration = parseFloat( this.duration ); |
| 1252 | + mw.log( 'EmbedPlayer::mediaElement:' + this.id + " duration is: " + this.duration ); |
| 1253 | + |
| 1254 | + // Set the player size attributes based loaded video element: |
| 1255 | + this.loadPlayerSize( element ); |
| 1256 | + // Set the plugin id |
| 1257 | + this.pid = 'pid_' + this.id; |
| 1258 | + |
| 1259 | + // Grab any innerHTML and set it to missing_plugin_html |
| 1260 | + // NOTE: we should strip "source" tags instead of checking and skipping |
| 1261 | + if ( element.innerHTML != '' && element.getElementsByTagName( 'source' ).length == 0 ) { |
| 1262 | + // mw.log( 'innerHTML: ' + element.innerHTML ); |
| 1263 | + this.user_missing_plugin_html = element.innerHTML; |
| 1264 | + } |
| 1265 | + |
| 1266 | + // Add the mediaElement object with the elements sources: |
| 1267 | + this.mediaElement = new mediaElement( element ); |
| 1268 | + |
| 1269 | + // Process attribute "sources" for dynamic embedding |
| 1270 | + if( customAttributes.sources && customAttributes.sources.length ){ |
| 1271 | + for( var i =0; i < customAttributes.sources.length ; i ++ ){ |
| 1272 | + var customSource = customAttributes.sources[i]; |
| 1273 | + if( customSource.src ){ |
| 1274 | + var $source = $j('<source />') |
| 1275 | + .attr( 'src', customSource.src ); |
| 1276 | + // xxx todo pull list of valid source attributes from |
| 1277 | + // mediaSource prototype |
| 1278 | + if( customSource.type ){ |
| 1279 | + $source.attr('type', customSource.type ); |
| 1280 | + } |
| 1281 | + if( customSource.title ){ |
| 1282 | + $source.attr('title', customSource.title ); |
| 1283 | + } |
| 1284 | + this.mediaElement.tryAddSource( $source.get(0) ); |
| 1285 | + } |
| 1286 | + } |
| 1287 | + } |
| 1288 | + }, |
| 1289 | + |
| 1290 | + stopEventPropagation: function(){ |
| 1291 | + this.stopMonitor(); |
| 1292 | + this._propagateEvents = false; |
| 1293 | + }, |
| 1294 | + restoreEventPropagation: function(){ |
| 1295 | + this._propagateEvents = true; |
| 1296 | + this.startMonitor(); |
| 1297 | + }, |
| 1298 | + |
| 1299 | + enableSeekBar: function(){ |
| 1300 | + this.controlBuilder.enableSeekBar(); |
| 1301 | + $j( this ).trigger( 'onEnableSeekBar'); |
| 1302 | + }, |
| 1303 | + disableSeekBar: function(){ |
| 1304 | + this.controlBuilder.disableSeekBar(); |
| 1305 | + $j( this ).trigger( 'ondisableSeekBar'); |
| 1306 | + }, |
| 1307 | + |
| 1308 | + /** |
| 1309 | + * For plugin-players to update supported features |
| 1310 | + */ |
| 1311 | + updateFeatureSupport: function(){ |
| 1312 | + $j( this ).trigger('updateFeatureSupportEvent', this.supports ); |
| 1313 | + return ; |
| 1314 | + }, |
| 1315 | + |
| 1316 | + /** |
| 1317 | + * Set the width & height from css style attribute, element attribute, or by |
| 1318 | + * default value if no css or attribute is provided set a callback to |
| 1319 | + * resize. |
| 1320 | + * |
| 1321 | + * Updates this.width & this.height |
| 1322 | + * |
| 1323 | + * @param {Element} |
| 1324 | + * element Source element to grab size from |
| 1325 | + */ |
| 1326 | + loadPlayerSize: function( element ) { |
| 1327 | + this.height = $j(element).css( 'height' ); |
| 1328 | + this.width = $j(element).css( 'width' ); |
| 1329 | + // Special check for chrome 100% with re-mapping to 32px |
| 1330 | + // ( hopefully no one embeds video at 32x32 ) |
| 1331 | + if( this.height == '32px' || this.height =='32px' ){ |
| 1332 | + this.width = '100%'; |
| 1333 | + this.height = '100%'; |
| 1334 | + } |
| 1335 | + mw.log('EmbedPlayer::loadPlayerSize: css size:' + this.width + ' h: ' + this.height); |
| 1336 | + |
| 1337 | + // Set to parent size ( resize events will cause player size updates) |
| 1338 | + if( this.height.indexOf('100%') != -1 || this.width.indexOf('100%') != -1 ){ |
| 1339 | + $relativeParent = $j(element).parents().filter(function() { |
| 1340 | + // reduce to only relative position or "body" elements |
| 1341 | + return $j(this).is('body') || $j(this).css('position') == 'relative'; |
| 1342 | + }).slice(0,1); // grab only the "first" |
| 1343 | + this.width = $relativeParent.width(); |
| 1344 | + this.height = $relativeParent.height(); |
| 1345 | + } |
| 1346 | + // Make sure height and width are a number |
| 1347 | + this.height = parseInt( this.height ); |
| 1348 | + this.width = parseInt( this.width ); |
| 1349 | + |
| 1350 | + // Set via attribute if CSS is zero or NaN and we have an attribute value: |
| 1351 | + this.height = ( this.height==0 || isNaN( this.height ) |
| 1352 | + && $j(element).attr( 'height' ) ) ? |
| 1353 | + parseInt( $j(element).attr( 'height' ) ): this.height; |
| 1354 | + this.width = ( this.width == 0 || isNaN( this.width ) |
| 1355 | + && $j(element).attr( 'width' ) )? |
| 1356 | + parseInt( $j(element).attr( 'width' ) ): this.width; |
| 1357 | + |
| 1358 | + |
| 1359 | + // Special case for audio |
| 1360 | + // Firefox sets audio height to "0px" while webkit uses 32px .. force |
| 1361 | + // zero: |
| 1362 | + if( element.tagName.toLowerCase() == 'audio' && this.height == '32' ) { |
| 1363 | + this.height = 0; |
| 1364 | + } |
| 1365 | + |
| 1366 | + // Use default aspect ration to get height or width ( if rewriting a |
| 1367 | + // non-audio player ) |
| 1368 | + if( element.tagName.toLowerCase() != 'audio' && this.videoAspect ) { |
| 1369 | + var aspect = this.videoAspect.split( ':' ); |
| 1370 | + if( this.height && !this.width ) { |
| 1371 | + this.width = parseInt( this.height * ( aspect[0] / aspect[1] ) ); |
| 1372 | + } |
| 1373 | + if( this.width && !this.height ) { |
| 1374 | + var apectRatio = ( aspect[1] / aspect[0] ); |
| 1375 | + this.height = parseInt( this.width * ( aspect[1] / aspect[0] ) ); |
| 1376 | + } |
| 1377 | + } |
| 1378 | + |
| 1379 | + // On load sometimes attr is temporally -1 as we don't have video metadata yet. |
| 1380 | + // or in IE we get NaN for width height |
| 1381 | + // |
| 1382 | + // NOTE: browsers that do support height width should set "waitForMeta" flag in addElement |
| 1383 | + if( ( isNaN( this.height )|| isNaN( this.width ) ) || |
| 1384 | + ( this.height == -1 || this.width == -1 ) || |
| 1385 | + // Check for firefox defaults |
| 1386 | + // Note: ideally firefox would not do random guesses at css |
| 1387 | + // values |
| 1388 | + ( (this.height == 150 || this.height == 64 ) && this.width == 300 ) |
| 1389 | + ) { |
| 1390 | + var defaultSize = mw.getConfig( 'EmbedPlayer.DefaultSize' ).split( 'x' ); |
| 1391 | + if( isNaN( this.width ) ){ |
| 1392 | + this.width = defaultSize[0]; |
| 1393 | + } |
| 1394 | + |
| 1395 | + // Special height default for audio tag ( if not set ) |
| 1396 | + if( element.tagName.toLowerCase() == 'audio' ) { |
| 1397 | + this.height = 0; |
| 1398 | + }else{ |
| 1399 | + this.height = defaultSize[1]; |
| 1400 | + } |
| 1401 | + } |
| 1402 | + }, |
| 1403 | + /** |
| 1404 | + * Resize the player to a new size preserving aspect ratio Wraps the |
| 1405 | + * controlBuilder.resizePlayer function |
| 1406 | + */ |
| 1407 | + resizePlayer: function( size , animate, callback){ |
| 1408 | + mw.log("EmbedPlayer::resizePlayer:" + size.width + ' x ' + size.height ); |
| 1409 | + |
| 1410 | + // Check if we are native display then resize the playerElement directly |
| 1411 | + if( this.useNativePlayerControls() ){ |
| 1412 | + if( animate ){ |
| 1413 | + $j( this.getPlayerElement() ).animate( size , callback); |
| 1414 | + } else { |
| 1415 | + $j( this.getPlayerElement() ).css( size ); |
| 1416 | + if( callback ) { |
| 1417 | + callback(); |
| 1418 | + } |
| 1419 | + } |
| 1420 | + } else { |
| 1421 | + this.controlBuilder.resizePlayer( size, animate, callback); |
| 1422 | + } |
| 1423 | + $j( this ).trigger( 'onResizePlayer', [size, animate] ); |
| 1424 | + }, |
| 1425 | + |
| 1426 | + /** |
| 1427 | + * Get the player pixel width not including controls |
| 1428 | + * |
| 1429 | + * @return {Number} pixel height of the video |
| 1430 | + */ |
| 1431 | + getPlayerWidth: function() { |
| 1432 | + return $j( this ).width(); |
| 1433 | + }, |
| 1434 | + |
| 1435 | + /** |
| 1436 | + * Get the player pixel height not including controls |
| 1437 | + * |
| 1438 | + * @return {Number} pixel height of the video |
| 1439 | + */ |
| 1440 | + getPlayerHeight: function() { |
| 1441 | + return $j( this ).height(); |
| 1442 | + }, |
| 1443 | + |
| 1444 | + /** |
| 1445 | + * Check player for sources. If we need to get media sources form an |
| 1446 | + * external file that request is issued here |
| 1447 | + */ |
| 1448 | + checkPlayerSources: function() { |
| 1449 | + mw.log( 'EmbedPlayer::checkPlayerSources: ' + this.id ); |
| 1450 | + var _this = this; |
| 1451 | + |
| 1452 | + // Scope the end of check for player sources so it can be called in a |
| 1453 | + var finishCheckPlayerSources = function(){ |
| 1454 | + // Run embedPlayer sources hook |
| 1455 | + $j( _this ).triggerQueueCallback( 'checkPlayerSourcesEvent', function(){ |
| 1456 | + _this.setupSourcePlayer(); |
| 1457 | + }); |
| 1458 | + }; |
| 1459 | + |
| 1460 | + // NOTE: Should could be moved to mediaWiki Api support module |
| 1461 | + // only load from api if sources are empty: |
| 1462 | + if ( _this.apiTitleKey && this.mediaElement.sources.length == 0) { |
| 1463 | + // Load media from external data |
| 1464 | + mw.log( 'EmbedPlayer::checkPlayerSources: loading apiTitleKey:' + _this.apiTitleKey ); |
| 1465 | + _this.loadSourceFromApi( function(){ |
| 1466 | + finishCheckPlayerSources(); |
| 1467 | + } ); |
| 1468 | + return ; |
| 1469 | + } else { |
| 1470 | + finishCheckPlayerSources(); |
| 1471 | + } |
| 1472 | + }, |
| 1473 | + /** |
| 1474 | + * Empty the player sources |
| 1475 | + */ |
| 1476 | + emptySources: function(){ |
| 1477 | + if( this.mediaElement ){ |
| 1478 | + this.mediaElement.sources = []; |
| 1479 | + this.mediaElement.selectedSource = null; |
| 1480 | + } |
| 1481 | + |
| 1482 | + }, |
| 1483 | + |
| 1484 | + /** |
| 1485 | + * Insert and play a video source ( useful for ads or bumper videos ) |
| 1486 | + * |
| 1487 | + * Only works while video is in active play back. Only tested with native |
| 1488 | + * playback atm. |
| 1489 | + */ |
| 1490 | + switchPlaySrc: function( source ){ |
| 1491 | + mw.log("Error: only native playback supports insertAndPlaySource right now"); |
| 1492 | + }, |
| 1493 | + |
| 1494 | + /** |
| 1495 | + * Load Source video info from mediaWiki Api title key ( this.apiTitleKey ) |
| 1496 | + * |
| 1497 | + * @@todo move this to mediaWiki 'api' module |
| 1498 | + * @param {Function} |
| 1499 | + * callback Function called once loading is complete |
| 1500 | + */ |
| 1501 | + loadSourceFromApi: function( callback ){ |
| 1502 | + var _this = this; |
| 1503 | + if( !_this.apiTitleKey ){ |
| 1504 | + mw.log( 'Error no apiTitleKey'); |
| 1505 | + return false; |
| 1506 | + } |
| 1507 | + |
| 1508 | + // Set local apiProvider via config if not defined |
| 1509 | + if( !_this.apiProvider ) { |
| 1510 | + _this.apiProvider = mw.getConfig( 'EmbedPlayer.ApiProvider' ); |
| 1511 | + } |
| 1512 | + |
| 1513 | + // Setup the request |
| 1514 | + var request = { |
| 1515 | + 'prop': 'imageinfo', |
| 1516 | + // In case the user added File: or Image: to the apiKey: |
| 1517 | + 'titles': 'File:' + unescape( this.apiTitleKey ).replace( /^(File:|Image:)/ , '' ), |
| 1518 | + 'iiprop': 'url|size|dimensions|metadata', |
| 1519 | + 'iiurlwidth': _this.width, |
| 1520 | + 'redirects' : true // automatically resolve redirects |
| 1521 | + }; |
| 1522 | + |
| 1523 | + // Run the request: |
| 1524 | + mw.getJSON( mw.getApiProviderURL( _this.apiProvider ), request, function( data ){ |
| 1525 | + if ( data.query.pages ) { |
| 1526 | + for ( var i in data.query.pages ) { |
| 1527 | + if( i == '-1' ) { |
| 1528 | + callback( false ); |
| 1529 | + return ; |
| 1530 | + } |
| 1531 | + var page = data.query.pages[i]; |
| 1532 | + } |
| 1533 | + } else { |
| 1534 | + callback( false ); |
| 1535 | + return ; |
| 1536 | + } |
| 1537 | + // Make sure we have imageinfo: |
| 1538 | + if( ! page.imageinfo || !page.imageinfo[0] ){ |
| 1539 | + callback( false ); |
| 1540 | + return ; |
| 1541 | + } |
| 1542 | + var imageinfo = page.imageinfo[0]; |
| 1543 | + |
| 1544 | + // Set the poster |
| 1545 | + _this.poster = imageinfo.thumburl; |
| 1546 | + |
| 1547 | + // Add the media src |
| 1548 | + _this.mediaElement.tryAddSource( |
| 1549 | + $j('<source />') |
| 1550 | + .attr( 'src', imageinfo.url ) |
| 1551 | + .get( 0 ) |
| 1552 | + ); |
| 1553 | + |
| 1554 | + // Set the duration |
| 1555 | + if( imageinfo.metadata[2]['name'] == 'length' ) { |
| 1556 | + _this.duration = imageinfo.metadata[2]['value']; |
| 1557 | + } |
| 1558 | + |
| 1559 | + // Set the width height |
| 1560 | + // Make sure we have an accurate aspect ratio |
| 1561 | + if( imageinfo.height != 0 && imageinfo.width != 0 ) { |
| 1562 | + _this.height = parseInt( _this.width * ( imageinfo.height / imageinfo.width ) ); |
| 1563 | + } |
| 1564 | + |
| 1565 | + // Update the css for the player interface |
| 1566 | + $j( _this ).css( 'height', _this.height); |
| 1567 | + |
| 1568 | + callback(); |
| 1569 | + }); |
| 1570 | + }, |
| 1571 | + |
| 1572 | + /** |
| 1573 | + * Set up the select source player |
| 1574 | + * |
| 1575 | + * issues autoSelectSource call |
| 1576 | + * |
| 1577 | + * Sets load error if no source is playable |
| 1578 | + */ |
| 1579 | + setupSourcePlayer: function() { |
| 1580 | + mw.log("EmbedPlayer::setupSourcePlayer: " + this.id + ' sources: ' + this.mediaElement.sources.length ); |
| 1581 | + // Autoseletct the media source |
| 1582 | + this.mediaElement.autoSelectSource(); |
| 1583 | + |
| 1584 | + // Auto select player based on default order |
| 1585 | + if ( !this.mediaElement.selectedSource ) { |
| 1586 | + mw.log( 'setupSourcePlayer:: no sources, type:' + this.type ); |
| 1587 | + } else { |
| 1588 | + this.selectedPlayer = mw.EmbedTypes.getMediaPlayers().defaultPlayer( this.mediaElement.selectedSource.mimeType ); |
| 1589 | + } |
| 1590 | + |
| 1591 | + if ( this.selectedPlayer ) { |
| 1592 | + // Inherit the playback system of the selected player: |
| 1593 | + this.inheritEmbedPlayer(); |
| 1594 | + } else { |
| 1595 | + this.showPluginMissingHTML(); |
| 1596 | + } |
| 1597 | + }, |
| 1598 | + |
| 1599 | + /** |
| 1600 | + * Load and inherit methods from the selected player interface |
| 1601 | + * |
| 1602 | + * @param {Function} |
| 1603 | + * callback Function to be called once playback-system has been |
| 1604 | + * inherited |
| 1605 | + */ |
| 1606 | + inheritEmbedPlayer: function( callback ) { |
| 1607 | + mw.log( "EmbedPlayer::inheritEmbedPlayer:duration is: " + this.getDuration() + ' p: ' + this.id ); |
| 1608 | + |
| 1609 | + // Clear out any non-base embedObj methods: |
| 1610 | + if ( this.instanceOf ) { |
| 1611 | + eval( 'var tmpObj = mw.EmbedPlayer' + this.instanceOf ); |
| 1612 | + for ( var i in tmpObj ) { // for in loop oky for object |
| 1613 | + if ( this[ 'parent_' + i ] ) { |
| 1614 | + this[i] = this[ 'parent_' + i]; |
| 1615 | + } else { |
| 1616 | + this[i] = null; |
| 1617 | + } |
| 1618 | + } |
| 1619 | + } |
| 1620 | + |
| 1621 | + // Set up the new embedObj |
| 1622 | + mw.log( 'EmbedPlayer::inheritEmbedPlayer: embedding with ' + this.selectedPlayer.library ); |
| 1623 | + var _this = this; |
| 1624 | + |
| 1625 | + // Load the selected player |
| 1626 | + this.selectedPlayer.load( function() { |
| 1627 | + mw.log( 'EmbedPlayer::inheritEmbedPlayer ' + _this.selectedPlayer.library + " player loaded for " + _this.id ); |
| 1628 | + |
| 1629 | + // Get embed library player Interface |
| 1630 | + var playerInterface = mw[ 'EmbedPlayer' + _this.selectedPlayer.library ]; |
| 1631 | + |
| 1632 | + for ( var method in playerInterface ) { |
| 1633 | + if ( _this[method] && !_this['parent_' + method] ) { |
| 1634 | + _this['parent_' + method] = _this[method]; |
| 1635 | + } |
| 1636 | + _this[ method ] = playerInterface[method]; |
| 1637 | + } |
| 1638 | + |
| 1639 | + // Update feature support |
| 1640 | + _this.updateFeatureSupport(); |
| 1641 | + |
| 1642 | + _this.getDuration(); |
| 1643 | + |
| 1644 | + // Run player display with timeout to avoid function stacking |
| 1645 | + setTimeout(function(){ |
| 1646 | + _this.showPlayer(); |
| 1647 | + // Call the global player manager to inform this video interface is |
| 1648 | + // ready: |
| 1649 | + mw.playerManager.playerReady( _this ); |
| 1650 | + |
| 1651 | + // Run the callback if provided |
| 1652 | + if ( typeof callback == 'function' ){ |
| 1653 | + callback(); |
| 1654 | + } |
| 1655 | + },1); |
| 1656 | + |
| 1657 | + } ); |
| 1658 | + }, |
| 1659 | + |
| 1660 | + /** |
| 1661 | + * Select a player playback system |
| 1662 | + * |
| 1663 | + * @param {Object} |
| 1664 | + * player Player playback system to be selected player playback |
| 1665 | + * system include vlc, native, java etc. |
| 1666 | + */ |
| 1667 | + selectPlayer: function( player ) { |
| 1668 | + var _this = this; |
| 1669 | + if ( this.selectedPlayer.id != player.id ) { |
| 1670 | + this.selectedPlayer = player; |
| 1671 | + this.inheritEmbedPlayer( function(){ |
| 1672 | + // Hide / remove track container |
| 1673 | + _this.$interface.find( '.track' ).remove(); |
| 1674 | + // We have to re-bind hoverIntent ( has to happen in this scope |
| 1675 | + // ) |
| 1676 | + if( _this.controls && _this.controlBuilder.checkOverlayControls() ){ |
| 1677 | + _this.controlBuilder.showControlBar(); |
| 1678 | + _this.$interface.hoverIntent({ |
| 1679 | + 'sensitivity': 4, |
| 1680 | + 'timeout' : 2000, |
| 1681 | + 'over' : function(){ |
| 1682 | + _this.controlBuilder.showControlBar(); |
| 1683 | + }, |
| 1684 | + 'out' : function(){ |
| 1685 | + _this.controlBuilder.hideControlBar(); |
| 1686 | + } |
| 1687 | + }); |
| 1688 | + } |
| 1689 | + }); |
| 1690 | + } |
| 1691 | + }, |
| 1692 | + |
| 1693 | + /** |
| 1694 | + * Get a time range from the media start and end time |
| 1695 | + * |
| 1696 | + * @return start_npt and end_npt time if present |
| 1697 | + */ |
| 1698 | + getTimeRange: function() { |
| 1699 | + var end_time = (this.controlBuilder.longTimeDisp)? '/' + mw.seconds2npt( this.getDuration() ) : ''; |
| 1700 | + var default_time_range = '0:00' + end_time; |
| 1701 | + if ( !this.mediaElement ) |
| 1702 | + return default_time_range; |
| 1703 | + if ( !this.mediaElement.selectedSource ) |
| 1704 | + return default_time_range; |
| 1705 | + if ( !this.mediaElement.selectedSource.end_npt ) |
| 1706 | + return default_time_range; |
| 1707 | + return this.mediaElement.selectedSource.start_npt + this.mediaElement.selectedSource.end_npt; |
| 1708 | + }, |
| 1709 | + |
| 1710 | + /** |
| 1711 | + * Get the duration of the embed player |
| 1712 | + */ |
| 1713 | + getDuration: function() { |
| 1714 | + return this.duration; |
| 1715 | + }, |
| 1716 | + |
| 1717 | + /** |
| 1718 | + * Get the player height |
| 1719 | + */ |
| 1720 | + getHeight: function() { |
| 1721 | + return this.height; |
| 1722 | + }, |
| 1723 | + |
| 1724 | + /** |
| 1725 | + * Get the player width |
| 1726 | + */ |
| 1727 | + getWidth: function(){ |
| 1728 | + return this.width; |
| 1729 | + }, |
| 1730 | + |
| 1731 | + /** |
| 1732 | + * Check if the selected source is an audio element: |
| 1733 | + */ |
| 1734 | + isAudio: function(){ |
| 1735 | + return ( this.mediaElement.selectedSource.mimeType.indexOf('audio/') !== -1 ); |
| 1736 | + }, |
| 1737 | + |
| 1738 | + /** |
| 1739 | + * Get the plugin embed html ( should be implemented by embed player |
| 1740 | + * interface ) |
| 1741 | + */ |
| 1742 | + doEmbedHTML: function() { |
| 1743 | + return 'Error: function doEmbedHTML should be implemented by embed player interface '; |
| 1744 | + }, |
| 1745 | + |
| 1746 | + /** |
| 1747 | + * Seek function ( should be implemented by embedPlayer interface |
| 1748 | + * playerNative, playerKplayer etc. ) embedPlayer doSeek only handles URL |
| 1749 | + * time seeks |
| 1750 | + */ |
| 1751 | + doSeek: function( percent ) { |
| 1752 | + var _this = this; |
| 1753 | + |
| 1754 | + this.seeking = true; |
| 1755 | + |
| 1756 | + // See if we should do a server side seek ( player independent ) |
| 1757 | + if ( this.supportsURLTimeEncoding() ) { |
| 1758 | + mw.log( 'EmbedPlayer::doSeek:: updated serverSeekTime: ' + mw.seconds2npt ( this.serverSeekTime ) + |
| 1759 | + ' currentTime: ' + _this.currentTime ); |
| 1760 | + // make sure we need to seek: |
| 1761 | + if( _this.currentTime == _this.serverSeekTime ){ |
| 1762 | + return ; |
| 1763 | + } |
| 1764 | + |
| 1765 | + this.stop(); |
| 1766 | + this.didSeekJump = true; |
| 1767 | + // Make sure this.serverSeekTime is up-to-date: |
| 1768 | + this.serverSeekTime = mw.npt2seconds( this.start_npt ) + parseFloat( percent * this.getDuration() ); |
| 1769 | + // Update the slider |
| 1770 | + this.updatePlayHead( percent ); |
| 1771 | + } |
| 1772 | + |
| 1773 | + // Do play request in 100ms ( give the dom time to swap out the embed player ) |
| 1774 | + setTimeout( function() { |
| 1775 | + _this.seeking = false; |
| 1776 | + _this.play(); |
| 1777 | + _this.monitor(); |
| 1778 | + }, 100 ); |
| 1779 | + |
| 1780 | + // Run the onSeeking interface update |
| 1781 | + // NOTE controlBuilder should really bind to html5 events rather |
| 1782 | + // than explicitly calling it or inheriting stuff. |
| 1783 | + this.controlBuilder.onSeek(); |
| 1784 | + }, |
| 1785 | + |
| 1786 | + /** |
| 1787 | + * Seeks to the requested time and issues a callback when ready (should be |
| 1788 | + * overwritten by client that supports frame serving) |
| 1789 | + */ |
| 1790 | + setCurrentTime: function( time, callback ) { |
| 1791 | + mw.log( 'Error: base embed setCurrentTime can not frame serve (override via plugin)' ); |
| 1792 | + }, |
| 1793 | + |
| 1794 | + /** |
| 1795 | + * On clip done action. Called once a clip is done playing |
| 1796 | + */ |
| 1797 | + onClipDone: function() { |
| 1798 | + var _this = this; |
| 1799 | + // don't run onclipdone if _propagateEvents is off |
| 1800 | + if( !_this._propagateEvents ){ |
| 1801 | + return ; |
| 1802 | + } |
| 1803 | + mw.log( 'EmbedPlayer::onClipDone:' + this.id + ' doneCount:' + this.donePlayingCount + ' stop state:' +this.isStopped() ); |
| 1804 | + // Only run stopped once: |
| 1805 | + if( !this.isStopped() ){ |
| 1806 | + // Stop the monitor and event propagation |
| 1807 | + this.stopEventPropagation(); |
| 1808 | + |
| 1809 | + // Show the control bar: |
| 1810 | + this.controlBuilder.showControlBar(); |
| 1811 | + |
| 1812 | + // Update the clip done playing count: |
| 1813 | + this.donePlayingCount ++; |
| 1814 | + |
| 1815 | + // Run the ended trigger ( allow the ended object to prevent default actions ) |
| 1816 | + mw.log("EmbedPlayer::onClipDone:Trigger ended"); |
| 1817 | + |
| 1818 | + // TOOD we should improve the end event flow |
| 1819 | + $j( this ).trigger( 'ended' ); |
| 1820 | + |
| 1821 | + // if the ended event did not trigger more timeline actions run the actual stop: |
| 1822 | + if( this.onDoneInterfaceFlag ){ |
| 1823 | + this.stop(); |
| 1824 | + // restore event propagation |
| 1825 | + this.restoreEventPropagation(); |
| 1826 | + // Check if we have the "loop" property set |
| 1827 | + if( this.loop ) { |
| 1828 | + this.play(); |
| 1829 | + return; |
| 1830 | + } |
| 1831 | + |
| 1832 | + // Stop the clip (load the thumbnail etc) |
| 1833 | + this.serverSeekTime = 0; |
| 1834 | + this.updatePlayHead( 0 ); |
| 1835 | + |
| 1836 | + // Make sure we are not in preview mode( no end clip actions in |
| 1837 | + // preview mode) |
| 1838 | + if ( this.previewMode ) { |
| 1839 | + return ; |
| 1840 | + } |
| 1841 | + |
| 1842 | + // Do the controlBuilder onClip done interface |
| 1843 | + this.controlBuilder.onClipDone(); |
| 1844 | + } |
| 1845 | + } |
| 1846 | + }, |
| 1847 | + |
| 1848 | + |
| 1849 | + /** |
| 1850 | + * Shows the video Thumbnail, updates pause state |
| 1851 | + */ |
| 1852 | + showThumbnail: function() { |
| 1853 | + var _this = this; |
| 1854 | + mw.log( 'EmbedPlayer::showThumbnail' + this.posterDisplayed ); |
| 1855 | + |
| 1856 | + // Close Menu Overlay: |
| 1857 | + this.controlBuilder.closeMenuOverlay(); |
| 1858 | + |
| 1859 | + // update the thumbnail html: |
| 1860 | + this.updatePosterHTML(); |
| 1861 | + |
| 1862 | + this.paused = true; |
| 1863 | + this.posterDisplayed = true; |
| 1864 | + // Make sure the controlBuilder bindings are up-to-date |
| 1865 | + this.controlBuilder.addControlBindings(); |
| 1866 | + |
| 1867 | + // Once the thumbnail is shown run the mediaReady trigger (if not using |
| 1868 | + // native controls) |
| 1869 | + if( !this.useNativePlayerControls() ){ |
| 1870 | + mw.log("mediaLoaded"); |
| 1871 | + $j( this ).trigger( 'mediaLoaded' ); |
| 1872 | + } |
| 1873 | + }, |
| 1874 | + |
| 1875 | + /** |
| 1876 | + * Show the player |
| 1877 | + */ |
| 1878 | + showPlayer: function () { |
| 1879 | + mw.log( 'EmbedPlayer:: Show player: ' + this.id + ' interace: w:' + this.width + ' h:' + this.height ); |
| 1880 | + var _this = this; |
| 1881 | + // Set-up the local controlBuilder instance: |
| 1882 | + this.controlBuilder = new mw.PlayerControlBuilder( this ); |
| 1883 | + var _this = this; |
| 1884 | + |
| 1885 | + |
| 1886 | + // Make sure we have mwplayer_interface |
| 1887 | + if( $j( this ).parent( '.mwplayer_interface' ).length == 0 ) { |
| 1888 | + // Select "player" |
| 1889 | + $j( this ).wrap( |
| 1890 | + $j('<div>') |
| 1891 | + .addClass( 'mwplayer_interface ' + this.controlBuilder.playerClass ) |
| 1892 | + .css({ |
| 1893 | + 'width' : this.width + 'px', |
| 1894 | + 'height' : this.height + 'px', |
| 1895 | + 'position' : 'relative' |
| 1896 | + }) |
| 1897 | + ) |
| 1898 | + // position the "player" absolute inside the relative interface |
| 1899 | + // parent: |
| 1900 | + .css('position', 'absolute'); |
| 1901 | + } |
| 1902 | + |
| 1903 | + // Set up local jQuery object reference to "mwplayer_interface" |
| 1904 | + this.$interface = $j( this ).parent( '.mwplayer_interface' ); |
| 1905 | + // if a isPersistentNativePlayer ( overlay the controls ) |
| 1906 | + if( !this.useNativePlayerControls() && this.isPersistentNativePlayer() ){ |
| 1907 | + this.$interface.css({ |
| 1908 | + 'position' : 'absolute', |
| 1909 | + 'top' : '0px', |
| 1910 | + 'left' : '0px', |
| 1911 | + 'background': null |
| 1912 | + }); |
| 1913 | + |
| 1914 | + if( this.isPersistentNativePlayer() && !_this.controlBuilder.checkOverlayControls() ){ |
| 1915 | + // if Persistent native player always give it the player height |
| 1916 | + $j('#' + this.pid ).css('height', this.height - _this.controlBuilder.height ); |
| 1917 | + } |
| 1918 | + $j( this ).show(); |
| 1919 | + this.controls = true; |
| 1920 | + } |
| 1921 | + if( !this.useNativePlayerControls() && !this.isPersistentNativePlayer() && !_this.controlBuilder.checkOverlayControls() ){ |
| 1922 | + // Update the video size per available control space. |
| 1923 | + $j(this).css('height', this.height - _this.controlBuilder.height ); |
| 1924 | + } |
| 1925 | + |
| 1926 | + // Update Thumbnail for the "player" |
| 1927 | + this.updatePosterHTML(); |
| 1928 | + |
| 1929 | + // Add controls if enabled: |
| 1930 | + if ( this.controls ) { |
| 1931 | + this.controlBuilder.addControls(); |
| 1932 | + } else { |
| 1933 | + // Need to think about this some more... |
| 1934 | + // Interface is hidden if controls are "off" |
| 1935 | + this.$interface.hide(); |
| 1936 | + } |
| 1937 | + |
| 1938 | + // Update temporal url if present |
| 1939 | + this.updateTemporalUrl(); |
| 1940 | + |
| 1941 | + |
| 1942 | + if ( this.autoplay ) { |
| 1943 | + mw.log( 'EmbedPlayer::showPlayer::activating autoplay' ); |
| 1944 | + // Issue a non-blocking play request |
| 1945 | + setTimeout(function(){ |
| 1946 | + _this.play(); |
| 1947 | + },1); |
| 1948 | + } |
| 1949 | + |
| 1950 | + }, |
| 1951 | + /** |
| 1952 | + * Media framgments handler based on: |
| 1953 | + * http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#fragment-dimensions |
| 1954 | + * |
| 1955 | + * We support seconds and npt ( normal play time ) |
| 1956 | + * |
| 1957 | + * Updates the player per fragment url info if present |
| 1958 | + * |
| 1959 | + */ |
| 1960 | + updateTemporalUrl: function(){ |
| 1961 | + var sourceHash = /[^\#]+$/.exec( this.getSrc() ).toString(); |
| 1962 | + if( sourceHash.indexOf('t=') === 0 ){ |
| 1963 | + // parse the times |
| 1964 | + var times = sourceHash.substr(2).split(','); |
| 1965 | + if( times[0] ){ |
| 1966 | + // update the current time |
| 1967 | + this.currentTime = mw.npt2seconds( times[0].toString() ); |
| 1968 | + } |
| 1969 | + if( times[1] ){ |
| 1970 | + this.pauseTime = mw.npt2seconds( times[1].toString() ); |
| 1971 | + // ignore invalid ranges: |
| 1972 | + if( this.pauseTime < this.currentTime ){ |
| 1973 | + this.pauseTime = null; |
| 1974 | + } |
| 1975 | + } |
| 1976 | + // Update the play head |
| 1977 | + this.updatePlayHead( this.currentTime / this.duration ); |
| 1978 | + // Update status: |
| 1979 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) ); |
| 1980 | + } |
| 1981 | + }, |
| 1982 | + /** |
| 1983 | + * Get missing plugin html (check for user included code) |
| 1984 | + * |
| 1985 | + * @param {String} |
| 1986 | + * [misssingType] missing type mime |
| 1987 | + */ |
| 1988 | + showPluginMissingHTML: function( ) { |
| 1989 | + mw.log("EmbedPlayer::showPluginMissingHTML"); |
| 1990 | + // Control builder ( for play button ) |
| 1991 | + this.controlBuilder = new mw.PlayerControlBuilder( this ); |
| 1992 | + |
| 1993 | + // Remove loader |
| 1994 | + $j('#loadingSpinner_' + this.id ).remove(); |
| 1995 | + |
| 1996 | + // Get mime type for un-supported formats: |
| 1997 | + this.updatePosterHTML(); |
| 1998 | + |
| 1999 | + // Set the play button to the first available source: |
| 2000 | + $j(this).find('.play-btn-large') |
| 2001 | + .unbind('click') |
| 2002 | + .wrap( |
| 2003 | + $j('<a />').attr( { |
| 2004 | + 'href': this.mediaElement.sources[0].getSrc(), |
| 2005 | + 'title' : gM('mwe-embedplayer-play_clip') |
| 2006 | + } ) |
| 2007 | + ); |
| 2008 | + }, |
| 2009 | + |
| 2010 | + /** |
| 2011 | + * Update the video time request via a time request string |
| 2012 | + * |
| 2013 | + * @param {String} |
| 2014 | + * time_req |
| 2015 | + */ |
| 2016 | + updateVideoTimeReq: function( time_req ) { |
| 2017 | + mw.log( 'EmbedPlayer::updateVideoTimeReq:' + time_req ); |
| 2018 | + var time_parts = time_req.split( '/' ); |
| 2019 | + this.updateVideoTime( time_parts[0], time_parts[1] ); |
| 2020 | + }, |
| 2021 | + |
| 2022 | + /** |
| 2023 | + * Update Video time from provided start_npt and end_npt values |
| 2024 | + * |
| 2025 | + * @param {String} |
| 2026 | + * start_npt the new start time in npt format |
| 2027 | + * @pamra {String} end_npt the new end time in npt format |
| 2028 | + */ |
| 2029 | + updateVideoTime: function( start_npt, end_npt ) { |
| 2030 | + // update media |
| 2031 | + this.mediaElement.updateSourceTimes( start_npt, end_npt ); |
| 2032 | + |
| 2033 | + // update mv_time |
| 2034 | + this.controlBuilder.setStatus( start_npt + '/' + end_npt ); |
| 2035 | + |
| 2036 | + // reset slider |
| 2037 | + this.updatePlayHead( 0 ); |
| 2038 | + |
| 2039 | + // reset seek_offset: |
| 2040 | + if ( this.mediaElement.selectedSource.URLTimeEncoding ) { |
| 2041 | + this.serverSeekTime = 0; |
| 2042 | + } else { |
| 2043 | + this.serverSeekTime = mw.npt2seconds( start_npt ); |
| 2044 | + } |
| 2045 | + }, |
| 2046 | + |
| 2047 | + |
| 2048 | + /** |
| 2049 | + * Update Thumb time with npt formated time |
| 2050 | + * |
| 2051 | + * @param {String} |
| 2052 | + * time NPT formated time to update thumbnail |
| 2053 | + */ |
| 2054 | + updateThumbTimeNPT: function( time ) { |
| 2055 | + this.updateThumbTime( mw.npt2seconds( time ) - parseInt( this.startOffset ) ); |
| 2056 | + }, |
| 2057 | + |
| 2058 | + /** |
| 2059 | + * Update the thumb with a new time |
| 2060 | + * |
| 2061 | + * @param {Float} |
| 2062 | + * floatSeconds Time to update the thumb to |
| 2063 | + */ |
| 2064 | + updateThumbTime:function( floatSeconds ) { |
| 2065 | + // mw.log('updateThumbTime:'+floatSeconds); |
| 2066 | + var _this = this; |
| 2067 | + if ( typeof this.org_thum_src == 'undefined' ) { |
| 2068 | + this.org_thum_src = this.poster; |
| 2069 | + } |
| 2070 | + if ( this.org_thum_src.indexOf( 't=' ) !== -1 ) { |
| 2071 | + this.last_thumb_url = mw.replaceUrlParams( this.org_thum_src, |
| 2072 | + { |
| 2073 | + 't' : mw.seconds2npt( floatSeconds + parseInt( this.startOffset ) ) |
| 2074 | + } |
| 2075 | + ); |
| 2076 | + if ( !this.thumbnail_updating ) { |
| 2077 | + this.updatePoster( this.last_thumb_url , false ); |
| 2078 | + this.last_thumb_url = null; |
| 2079 | + } |
| 2080 | + } |
| 2081 | + }, |
| 2082 | + |
| 2083 | + /** |
| 2084 | + * Updates the displayed thumbnail via percent of the stream |
| 2085 | + * |
| 2086 | + * @param {Float} |
| 2087 | + * percent Percent of duration to update thumb |
| 2088 | + */ |
| 2089 | + updateThumbPerc:function( percent ) { |
| 2090 | + return this.updateThumbTime( ( this.getDuration() * percent ) ); |
| 2091 | + }, |
| 2092 | + |
| 2093 | + /** |
| 2094 | + * Updates the thumbnail if the thumbnail is being displayed |
| 2095 | + * |
| 2096 | + * @param {String} |
| 2097 | + * src New src of thumbnail |
| 2098 | + * @param {Boolean} |
| 2099 | + * quick_switch true switch happens instantly false / undefined |
| 2100 | + * animated cross fade |
| 2101 | + */ |
| 2102 | + updatePosterSrc: function( src, quick_switch ) { |
| 2103 | + // make sure we don't go to the same url if we are not already updating: |
| 2104 | + if ( !this.thumbnail_updating && $j( '#img_thumb_' + this.id ).attr( 'src' ) == src ) |
| 2105 | + return false; |
| 2106 | + // if we are already updating don't issue a new update: |
| 2107 | + if ( this.thumbnail_updating && $j( '#new_img_thumb_' + this.id ).attr( 'src' ) == src ) |
| 2108 | + return false; |
| 2109 | + |
| 2110 | + mw.log( 'update thumb: ' + src ); |
| 2111 | + |
| 2112 | + if ( quick_switch ) { |
| 2113 | + $j( '#img_thumb_' + this.id ).attr( 'src', src ); |
| 2114 | + } else { |
| 2115 | + var _this = this; |
| 2116 | + // if still animating remove new_img_thumb_ |
| 2117 | + if ( this.thumbnail_updating == true ) |
| 2118 | + $j( '#new_img_thumb_' + this.id ).stop().remove(); |
| 2119 | + |
| 2120 | + if ( this.posterDisplayed ) { |
| 2121 | + mw.log( 'set to thumb:' + src ); |
| 2122 | + this.thumbnail_updating = true; |
| 2123 | + $j( this ).append( |
| 2124 | + $j('<img />') |
| 2125 | + .attr({ |
| 2126 | + 'src' : src, |
| 2127 | + 'id' : 'new_img_thumb_' + this.id, |
| 2128 | + 'width' : this.width, |
| 2129 | + 'height': this.height |
| 2130 | + }) |
| 2131 | + .css( { |
| 2132 | + 'display' : 'none', |
| 2133 | + 'position' : 'absolute', |
| 2134 | + 'z-index' : 2, |
| 2135 | + 'top' : '0px', |
| 2136 | + 'left' : '0px' |
| 2137 | + }) |
| 2138 | + ); |
| 2139 | + // mw.log('appended: new_img_thumb_'); |
| 2140 | + $j( '#new_img_thumb_' + this.id ).fadeIn( "slow", function() { |
| 2141 | + // once faded in remove org and rename new: |
| 2142 | + $j( '#img_thumb_' + _this.id ).remove(); |
| 2143 | + $j( '#new_img_thumb_' + _this.id ).attr( 'id', 'img_thumb_' + _this.id ); |
| 2144 | + $j( '#img_thumb_' + _this.id ).css( 'z-index', '1' ); |
| 2145 | + _this.thumbnail_updating = false; |
| 2146 | + // mw.log("done fadding in "+ |
| 2147 | + // $j('#img_thumb_'+_this.id).attr("src")); |
| 2148 | + |
| 2149 | + // if we have a thumb queued update to that |
| 2150 | + if ( _this.last_thumb_url ) { |
| 2151 | + var src_url = _this.last_thumb_url; |
| 2152 | + _this.last_thumb_url = null; |
| 2153 | + _this.updatePosterSrc( src_url ); |
| 2154 | + } |
| 2155 | + } ); |
| 2156 | + } |
| 2157 | + } |
| 2158 | + }, |
| 2159 | + // update the video poster: |
| 2160 | + updatePosterSrc: function( posterSrc ){ |
| 2161 | + this.poster = posterSrc; |
| 2162 | + }, |
| 2163 | + /** |
| 2164 | + * Returns the HTML code for the video when it is in thumbnail mode. |
| 2165 | + * playing, configuring the player, inline cmml display, HTML linkback, |
| 2166 | + * download, and embed code. |
| 2167 | + */ |
| 2168 | + updatePosterHTML: function () { |
| 2169 | + mw.log( 'EmbedPlayer:updatePosterHTML::' + this.id ); |
| 2170 | + var thumb_html = ''; |
| 2171 | + var class_atr = ''; |
| 2172 | + var style_atr = ''; |
| 2173 | + |
| 2174 | + if( this.useNativePlayerControls() ){ |
| 2175 | + this.showNativePlayer(); |
| 2176 | + return ; |
| 2177 | + } |
| 2178 | + |
| 2179 | + // Set by default thumb value if not found |
| 2180 | + var posterSrc = ( this.poster ) ? this.poster : |
| 2181 | + mw.getConfig( 'imagesPath' ) + 'vid_default_thumb.jpg'; |
| 2182 | + |
| 2183 | + // Update PersistentNativePlayer poster: |
| 2184 | + if( this.isPersistentNativePlayer() ){ |
| 2185 | + $j( '#' + this.pid ).attr('poster', posterSrc); |
| 2186 | + } else { |
| 2187 | + // Poster support is not very consistent in browsers |
| 2188 | + // use a jpg poster image: |
| 2189 | + $j( this ).html( |
| 2190 | + $j( '<img />' ) |
| 2191 | + .css({ |
| 2192 | + 'position' : 'relative', |
| 2193 | + 'width' : '100%', |
| 2194 | + 'height' : '100%' |
| 2195 | + }) |
| 2196 | + .attr({ |
| 2197 | + 'id' : 'img_thumb_' + this.id, |
| 2198 | + 'src' : posterSrc |
| 2199 | + }) |
| 2200 | + .addClass( 'playerPoster' ) |
| 2201 | + ); |
| 2202 | + } |
| 2203 | + if ( this.controls && this.controlBuilder |
| 2204 | + && this.height > this.controlBuilder.getComponentHeight( 'playButtonLarge' ) |
| 2205 | + ) { |
| 2206 | + $j( this ).append( |
| 2207 | + this.controlBuilder.getComponent( 'playButtonLarge' ) |
| 2208 | + ); |
| 2209 | + } |
| 2210 | + }, |
| 2211 | + |
| 2212 | + /** |
| 2213 | + * Checks if native controls should be used |
| 2214 | + * |
| 2215 | + * @param [player] |
| 2216 | + * Object Optional player object to check controls attribute |
| 2217 | + * @returns boolean true if the mwEmbed player interface should be used |
| 2218 | + * false if the mwEmbed player interface should not be used |
| 2219 | + */ |
| 2220 | + useNativePlayerControls: function() { |
| 2221 | + |
| 2222 | + if( this.usenativecontrols === true ){ |
| 2223 | + return true; |
| 2224 | + } |
| 2225 | + if( mw.getConfig('EmbedPlayer.NativeControls') === true ) { |
| 2226 | + return true; |
| 2227 | + } |
| 2228 | + |
| 2229 | + // Do some device detection devices that don't support overlays |
| 2230 | + // and go into full screen once play is clicked: |
| 2231 | + if( mw.isAndroid2() || mw.isIpod() || mw.isIphone() ){ |
| 2232 | + return true; |
| 2233 | + } |
| 2234 | + // iPad can use html controls if its a persistantPlayer in the dom before loading ) |
| 2235 | + // else it needs to use native controls: |
| 2236 | + if( mw.isIpad() ){ |
| 2237 | + if( this.isPersistentNativePlayer() && mw.getConfig('EmbedPlayer.EnableIpadHTMLControls') ){ |
| 2238 | + return false; |
| 2239 | + } else { |
| 2240 | + // Set warning that your trying to do iPad controls without persistent native player: |
| 2241 | + if( mw.getConfig('EmbedPlayer.EnableIpadHTMLControls') ){ |
| 2242 | + mw.log("Error:: Trying to set EnableIpadHTMLControls without Persistent Native Player"); |
| 2243 | + } |
| 2244 | + return true; |
| 2245 | + } |
| 2246 | + } |
| 2247 | + return false; |
| 2248 | + }, |
| 2249 | + |
| 2250 | + isPersistentNativePlayer: function(){ |
| 2251 | + return $j('#' + this.pid ).hasClass('persistentNativePlayer'); |
| 2252 | + }, |
| 2253 | + |
| 2254 | + |
| 2255 | + /** |
| 2256 | + * Show the native player embed code |
| 2257 | + * |
| 2258 | + * This is for cases where the main library needs to "get out of the way" |
| 2259 | + * since the device only supports a limited subset of the html5 and won't |
| 2260 | + * work with an html javascirpt interface |
| 2261 | + */ |
| 2262 | + showNativePlayer: function(){ |
| 2263 | + var _this = this; |
| 2264 | + // Empty the player of any child nodes |
| 2265 | + $j(this).empty(); |
| 2266 | + |
| 2267 | + // Remove the player loader spinner if it exists |
| 2268 | + $j('#loadingSpinner_' + this.id ).remove(); |
| 2269 | + |
| 2270 | + // Setup the source |
| 2271 | + var source = this.mediaElement.getSources( 'video/h264' )[0]; |
| 2272 | + // Support fake user agent |
| 2273 | + if( !source || !source.src ){ |
| 2274 | + mw.log( 'Warning: Your probably fakeing the iPhone userAgent ( no h.264 source )' ); |
| 2275 | + source = this.mediaElement.getSources( 'video/ogg' )[0]; |
| 2276 | + } |
| 2277 | + |
| 2278 | + // Setup videoAttribues |
| 2279 | + var videoAttribues = { |
| 2280 | + 'poster': _this.poster, |
| 2281 | + 'src' : source.src, |
| 2282 | + 'controls' : 'true' |
| 2283 | + }; |
| 2284 | + if( this.loop ){ |
| 2285 | + videoAttribues[ 'loop' ] = 'true'; |
| 2286 | + } |
| 2287 | + var cssStyle = { |
| 2288 | + 'width' : _this.width, |
| 2289 | + 'height' : _this.height |
| 2290 | + }; |
| 2291 | + |
| 2292 | + // If not a persistentNativePlayer swap the video tag |
| 2293 | + // completely instead of just updating properties: |
| 2294 | + $j( '#' + this.pid ).replaceWith( |
| 2295 | + _this.getNativePlayerHtml( videoAttribues, cssStyle ) |
| 2296 | + ); |
| 2297 | + |
| 2298 | + // Bind native events: |
| 2299 | + this.applyMediaElementBindings(); |
| 2300 | + |
| 2301 | + // Android only can play with a special play button ( no native controls |
| 2302 | + // persistentNativePlayer has no controls: |
| 2303 | + if( mw.isAndroid2() ){ |
| 2304 | + this.addPlayBtnLarge(); |
| 2305 | + } |
| 2306 | + return ; |
| 2307 | + }, |
| 2308 | + addPlayBtnLarge:function(){ |
| 2309 | + var _this = this; |
| 2310 | + var $pid = $j( '#' + _this.pid ); |
| 2311 | + $pid.siblings('.play-btn-large').remove(); |
| 2312 | + $playButton = this.controlBuilder.getComponent('playButtonLarge'); |
| 2313 | + $pid.after( |
| 2314 | + $playButton |
| 2315 | + .css({ |
| 2316 | + 'left' : parseInt( $pid.position().left ) + parseInt( $playButton.css('left') ), |
| 2317 | + 'top' : parseInt( $pid.position().top ) + parseInt( $playButton.css('top') ) |
| 2318 | + }) |
| 2319 | + ); |
| 2320 | + }, |
| 2321 | + /** |
| 2322 | + * Should be set via native embed support |
| 2323 | + */ |
| 2324 | + getNativePlayerHtml: function(){ |
| 2325 | + return $j('<div />' ) |
| 2326 | + .css( 'width', this.getWidth() ) |
| 2327 | + .html( 'Error: Trying to get native html5 player without native support for codec' ); |
| 2328 | + }, |
| 2329 | + /** |
| 2330 | + * Should be set via native embed support |
| 2331 | + */ |
| 2332 | + applyMediaElementBindings: function(){ |
| 2333 | + return ; |
| 2334 | + }, |
| 2335 | + |
| 2336 | + /** |
| 2337 | + * Gets code to embed the player remotely for "share" this player links |
| 2338 | + */ |
| 2339 | + getEmbeddingHTML: function() { |
| 2340 | + switch( mw.getConfig( 'EmbedPlayer.ShareEmbedMode' ) ){ |
| 2341 | + case 'iframe': |
| 2342 | + return this.getShareIframeObject(); |
| 2343 | + break; |
| 2344 | + case 'videojs': |
| 2345 | + return this.getShareEmbedVideoJs(); |
| 2346 | + break; |
| 2347 | + } |
| 2348 | + }, |
| 2349 | + |
| 2350 | + /** |
| 2351 | + * Get the share embed object code |
| 2352 | + * |
| 2353 | + * NOTE this could probably share a bit more code with getShareEmbedVideoJs |
| 2354 | + */ |
| 2355 | + getShareIframeObject: function(){ |
| 2356 | + |
| 2357 | + // If using a gadget do the new embed format: |
| 2358 | + // NOTE this should be factored out into mediaWiki gadget helper |
| 2359 | + if( typeof wgServer != 'undefined' && typeof mwCheckForGadget != 'undefined' ){ |
| 2360 | + var iframeUrl = wgServer + |
| 2361 | + wgArticlePath.replace( /\$1/, 'File:' + |
| 2362 | + unescape( this.apiTitleKey ).replace( /^(File:|Image:)/ , '' ) ) + |
| 2363 | + '?' + mw.getConfig( 'Mw.AppendWithJS' ) + |
| 2364 | + '&embedplayer=yes'; |
| 2365 | + } else if ( typeof(mw.IA) != 'undefined') { |
| 2366 | + var iframeUrl = mw.IA.embedUrl(); |
| 2367 | + } else { |
| 2368 | + // old style embed: |
| 2369 | + var iframeUrl = mw.getMwEmbedPath() + 'mwEmbedFrame.php?'; |
| 2370 | + var params = {'src[]':[]}; |
| 2371 | + |
| 2372 | + // TODO move to mediaWiki Support module |
| 2373 | + if( this.apiTitleKey ) { |
| 2374 | + params.apiTitleKey = this.apiTitleKey; |
| 2375 | + if ( this.apiProvider ) { |
| 2376 | + // Commons always uses the commons api provider ( special hack should refactor ) |
| 2377 | + if( mw.parseUri( document.URL ).host == 'commons.wikimedia.org'){ |
| 2378 | + this.apiProvider = 'commons'; |
| 2379 | + } |
| 2380 | + params.apiProvider = this.apiProvider; |
| 2381 | + } |
| 2382 | + } else { |
| 2383 | + // Output all the video sources: |
| 2384 | + for( var i=0; i < this.mediaElement.sources.length; i++ ){ |
| 2385 | + var source = this.mediaElement.sources[i]; |
| 2386 | + if( source.src ) { |
| 2387 | + params['src[]'].push(mw.absoluteUrl( source.src )); |
| 2388 | + } |
| 2389 | + } |
| 2390 | + // Output the poster attr |
| 2391 | + if( this.poster ){ |
| 2392 | + params.poster = this.poster; |
| 2393 | + } |
| 2394 | + } |
| 2395 | + |
| 2396 | + // Set the skin if set to something other than default |
| 2397 | + if( this.skinName ){ |
| 2398 | + params.skin = this.skinName; |
| 2399 | + } |
| 2400 | + |
| 2401 | + if( this.duration ) { |
| 2402 | + params.durationHint = parseFloat( this.duration ); |
| 2403 | + } |
| 2404 | + iframeUrl += $j.param( params ); |
| 2405 | + } |
| 2406 | + |
| 2407 | + // Set up embedFrame src path |
| 2408 | + var embedCode = '<iframe src="' + mw.escapeQuotesHTML( iframeUrl ) + '" '; |
| 2409 | + |
| 2410 | + // Set width / height of embed object |
| 2411 | + embedCode += 'width="' + this.getPlayerWidth() +'" '; |
| 2412 | + embedCode += 'height="' + this.getPlayerHeight() + '" '; |
| 2413 | + embedCode += 'frameborder="0" '; |
| 2414 | + |
| 2415 | + // Close up the embedCode tag: |
| 2416 | + embedCode+='></iframe>'; |
| 2417 | + |
| 2418 | + // Return the embed code |
| 2419 | + return embedCode; |
| 2420 | + }, |
| 2421 | + |
| 2422 | + /** |
| 2423 | + * Get the share embed Video tag code |
| 2424 | + */ |
| 2425 | + getShareEmbedVideoJs: function(){ |
| 2426 | + |
| 2427 | + // Set the embed tag type: |
| 2428 | + var embedtag = ( this.isAudio() )? 'audio': 'video'; |
| 2429 | + |
| 2430 | + // Set up the mwEmbed js include: |
| 2431 | + var embedCode = '<script type="text/javascript" ' + |
| 2432 | + 'src="' + |
| 2433 | + mw.escapeQuotesHTML( |
| 2434 | + mw.absoluteUrl( |
| 2435 | + mw.getMwEmbedSrc() |
| 2436 | + ) |
| 2437 | + ) + '"></script>' + |
| 2438 | + '<' + embedtag + ' '; |
| 2439 | + |
| 2440 | + if( this.poster ) { |
| 2441 | + embedCode += 'poster="' + |
| 2442 | + mw.escapeQuotesHTML( mw.absoluteUrl( this.poster ) ) + |
| 2443 | + '" '; |
| 2444 | + } |
| 2445 | + |
| 2446 | + // Set the skin if set to something other than default |
| 2447 | + if( this.skinName ){ |
| 2448 | + embedCode += 'class="' + |
| 2449 | + mw.escapeQuotesHTML( this.skinName ) + |
| 2450 | + '" '; |
| 2451 | + } |
| 2452 | + |
| 2453 | + if( this.duration ) { |
| 2454 | + embedCode +='durationHint="' + parseFloat( this.duration ) + '" '; |
| 2455 | + } |
| 2456 | + |
| 2457 | + if( this.width || this.height ){ |
| 2458 | + embedCode +='style="'; |
| 2459 | + embedCode += ( this.width )? 'width:' + this.width +'px;': ''; |
| 2460 | + embedCode += ( this.height )? 'height:' + this.height +'px;': ''; |
| 2461 | + embedCode += '" '; |
| 2462 | + } |
| 2463 | + |
| 2464 | + // TODO move to mediaWiki Support module |
| 2465 | + if( this.apiTitleKey ) { |
| 2466 | + embedCode += 'apiTitleKey="' + mw.escapeQuotesHTML( this.apiTitleKey ) + '" '; |
| 2467 | + if ( this.apiProvider ) { |
| 2468 | + embedCode += 'apiProvider="' + mw.escapeQuotesHTML( this.apiProvider ) + '" '; |
| 2469 | + } |
| 2470 | + // close the video tag |
| 2471 | + embedCode += '></video>'; |
| 2472 | + |
| 2473 | + } else { |
| 2474 | + // Close the video attr |
| 2475 | + embedCode += '>'; |
| 2476 | + |
| 2477 | + // Output all the video sources: |
| 2478 | + for( var i=0; i < this.mediaElement.sources.length; i++ ){ |
| 2479 | + var source = this.mediaElement.sources[i]; |
| 2480 | + if( source.src ) { |
| 2481 | + embedCode +='<source src="' + |
| 2482 | + mw.absoluteUrl( source.src ) + |
| 2483 | + '" ></source>'; |
| 2484 | + } |
| 2485 | + } |
| 2486 | + // Close the video tag |
| 2487 | + embedCode += '</video>'; |
| 2488 | + } |
| 2489 | + |
| 2490 | + return embedCode; |
| 2491 | + }, |
| 2492 | + |
| 2493 | + /** |
| 2494 | + * Base Embed Controls |
| 2495 | + */ |
| 2496 | + |
| 2497 | + /** |
| 2498 | + * The Play Action |
| 2499 | + * |
| 2500 | + * Handles play requests, updates relevant states: seeking =false paused = |
| 2501 | + * false Updates pause button Starts the "monitor" |
| 2502 | + */ |
| 2503 | + play: function() { |
| 2504 | + var _this = this; |
| 2505 | + mw.log( "EmbedPlayer:: play: " + this._propagateEvents ); |
| 2506 | + if( ! this._propagateEvents ){ |
| 2507 | + return ; |
| 2508 | + } |
| 2509 | + // Hide any overlay: |
| 2510 | + this.controlBuilder.closeMenuOverlay(); |
| 2511 | + |
| 2512 | + // Check if thumbnail is being displayed and embed html |
| 2513 | + if ( this.posterDisplayed ) { |
| 2514 | + if ( !this.selectedPlayer ) { |
| 2515 | + this.showPluginMissingHTML(); |
| 2516 | + return; |
| 2517 | + } else { |
| 2518 | + this.posterDisplayed = false; |
| 2519 | + // Hide any button if present: |
| 2520 | + this.$interface.find( '.play-btn-large' ).remove(); |
| 2521 | + this.doEmbedHTML(); |
| 2522 | + } |
| 2523 | + } |
| 2524 | + |
| 2525 | + if( this.paused === true ){ |
| 2526 | + this.paused = false; |
| 2527 | + // Check if we should Trigger the play event |
| 2528 | + mw.log("EmbedPlayer:: trigger play even::" + !this.paused); |
| 2529 | + if( ! this.doMethodsAutoTrigger() ) { |
| 2530 | + $j( this ).trigger( 'play' ); |
| 2531 | + } |
| 2532 | + } |
| 2533 | + |
| 2534 | + // If we previously finished playing this clip run the "replay hook" |
| 2535 | + if( this.donePlayingCount > 0 && !this.paused) { |
| 2536 | + mw.log("replayEvent"); |
| 2537 | + $j( this ).trigger( 'replayEvent' ); |
| 2538 | + } |
| 2539 | + |
| 2540 | + this.$interface.find('.play-btn span') |
| 2541 | + .removeClass( 'ui-icon-play' ) |
| 2542 | + .addClass( 'ui-icon-pause' ); |
| 2543 | + |
| 2544 | + this.$interface.find( '.play-btn' ) |
| 2545 | + .unbind() |
| 2546 | + .buttonHover() |
| 2547 | + .click( function( ) { |
| 2548 | + _this.pause(); |
| 2549 | + } ) |
| 2550 | + .attr( 'title', gM( 'mwe-embedplayer-pause_clip' ) ); |
| 2551 | + |
| 2552 | + // Start the monitor if not already started |
| 2553 | + this.monitor(); |
| 2554 | + }, |
| 2555 | + /** |
| 2556 | + * Base embed pause Updates the play/pause button state. |
| 2557 | + * |
| 2558 | + * There is no general way to pause the video must be overwritten by embed |
| 2559 | + * object to support this functionality. |
| 2560 | + */ |
| 2561 | + pause: function( event ) { |
| 2562 | + var _this = this; |
| 2563 | + // Trigger the pause event if not already paused and using native |
| 2564 | + // controls: |
| 2565 | + if( this.paused === false ){ |
| 2566 | + this.paused = true; |
| 2567 | + mw.log('EmbedPlayer:trigger pause:' + this.paused); |
| 2568 | + if( ! this.doMethodsAutoTrigger() ){ |
| 2569 | + $j( this ).trigger( 'pause' ); |
| 2570 | + } |
| 2571 | + } |
| 2572 | + |
| 2573 | + // update the ctrl "paused state" |
| 2574 | + this.$interface.find('.play-btn span' ) |
| 2575 | + .removeClass( 'ui-icon-pause' ) |
| 2576 | + .addClass( 'ui-icon-play' ); |
| 2577 | + |
| 2578 | + this.$interface.find('.play-btn' ) |
| 2579 | + .unbind('click') |
| 2580 | + .buttonHover() |
| 2581 | + .click( function() { |
| 2582 | + _this.play(); |
| 2583 | + } ) |
| 2584 | + .attr( 'title', gM( 'mwe-embedplayer-play_clip' ) ); |
| 2585 | + }, |
| 2586 | + // special per browser check for autoTrigger events |
| 2587 | + // ideally jQuery would not have this inconsistency. |
| 2588 | + doMethodsAutoTrigger: function(){ |
| 2589 | + if( $j.browser.mozilla && ! mw.versionIsAtLeast('2.0', $j.browser.version ) ){ |
| 2590 | + return true; |
| 2591 | + } |
| 2592 | + return false; |
| 2593 | + }, |
| 2594 | + |
| 2595 | + /** |
| 2596 | + * Maps the html5 load request. There is no general way to "load" clips so |
| 2597 | + * underling plugin-player libs should override. |
| 2598 | + */ |
| 2599 | + load: function() { |
| 2600 | + // should be done by child (no base way to pre-buffer video) |
| 2601 | + mw.log( 'baseEmbed:load call' ); |
| 2602 | + }, |
| 2603 | + |
| 2604 | + |
| 2605 | + /** |
| 2606 | + * Base embed stop |
| 2607 | + * |
| 2608 | + * Updates the player to the stop state shows Thumbnail resets Buffer resets |
| 2609 | + * Playhead slider resets Status |
| 2610 | + */ |
| 2611 | + stop: function() { |
| 2612 | + var _this = this; |
| 2613 | + mw.log( 'EmbedPlayer::stop:' + this.id ); |
| 2614 | + |
| 2615 | + // no longer seeking: |
| 2616 | + this.didSeekJump = false; |
| 2617 | + |
| 2618 | + // Reset current time and prev time and seek offset |
| 2619 | + this.currentTime = this.previousTime = this.serverSeekTime = 0; |
| 2620 | + |
| 2621 | + this.stopMonitor(); |
| 2622 | + |
| 2623 | + // Issue pause to update interface (only call this parent) |
| 2624 | + if( !this.paused ){ |
| 2625 | + this.paused = true; |
| 2626 | + // update the interface |
| 2627 | + if ( this['parent_pause'] ) { |
| 2628 | + this.parent_pause(); |
| 2629 | + } else { |
| 2630 | + this.pause(); |
| 2631 | + } |
| 2632 | + } |
| 2633 | + // Native player controls: |
| 2634 | + if( this.useNativePlayerControls() ){ |
| 2635 | + this.getPlayerElement().currentTime = 0; |
| 2636 | + this.getPlayerElement().pause(); |
| 2637 | + // If on android when we "stop" we re add the large play button |
| 2638 | + if( mw.isAndroid2() ){ |
| 2639 | + this.addPlayBtnLarge(); |
| 2640 | + } |
| 2641 | + } else { |
| 2642 | + // Rewrite the html to thumbnail disp |
| 2643 | + this.showThumbnail(); |
| 2644 | + this.bufferedPercent = 0; // reset buffer state |
| 2645 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 2646 | + |
| 2647 | + // Reset the playhead |
| 2648 | + mw.log("EmbedPlayer::Stop:: Reset play head"); |
| 2649 | + this.updatePlayHead( 0 ); |
| 2650 | + |
| 2651 | + } |
| 2652 | + }, |
| 2653 | + |
| 2654 | + /** |
| 2655 | + * Base Embed mute |
| 2656 | + * |
| 2657 | + * Handles interface updates for toggling mute. Plug-in / player interface |
| 2658 | + * must handle the actual media player update |
| 2659 | + */ |
| 2660 | + toggleMute: function() { |
| 2661 | + mw.log( 'f:toggleMute:: (old state:) ' + this.muted ); |
| 2662 | + if ( this.muted ) { |
| 2663 | + this.muted = false; |
| 2664 | + var percent = this.preMuteVolume; |
| 2665 | + } else { |
| 2666 | + this.muted = true; |
| 2667 | + this.preMuteVolume = this.volume; |
| 2668 | + var percent = 0; |
| 2669 | + } |
| 2670 | + this.setVolume( percent ); |
| 2671 | + // Update the interface |
| 2672 | + this.setInterfaceVolume( percent ); |
| 2673 | + }, |
| 2674 | + |
| 2675 | + /** |
| 2676 | + * Update volume function ( called from interface updates ) |
| 2677 | + * |
| 2678 | + * @param {float} |
| 2679 | + * percent Percent of full volume |
| 2680 | + */ |
| 2681 | + setVolume: function( percent ) { |
| 2682 | + // ignore NaN percent: |
| 2683 | + if( isNaN( percent ) ){ |
| 2684 | + return ; |
| 2685 | + } |
| 2686 | + // Set the local volume attribute |
| 2687 | + this.previousVolume = this.volume = percent; |
| 2688 | + |
| 2689 | + // Un-mute if setting positive volume |
| 2690 | + if( percent != 0 ){ |
| 2691 | + this.muted = false; |
| 2692 | + } |
| 2693 | + |
| 2694 | + // Update the playerElement volume |
| 2695 | + this.setPlayerElementVolume( percent ); |
| 2696 | + |
| 2697 | + // mw.log(" setVolume:: " + percent + ' this.volume is: ' + |
| 2698 | + // this.volume); |
| 2699 | + $j( this ).trigger('volumeChanged', percent ); |
| 2700 | + }, |
| 2701 | + |
| 2702 | + /** |
| 2703 | + * Updates the interface volume |
| 2704 | + * |
| 2705 | + * TODO should move to controlBuilder |
| 2706 | + * |
| 2707 | + * @param {float} |
| 2708 | + * percent Percentage volume to update interface |
| 2709 | + */ |
| 2710 | + setInterfaceVolume: function( percent ) { |
| 2711 | + if( this.supports[ 'volumeControl' ] && |
| 2712 | + this.$interface.find( '.volume-slider' ).length |
| 2713 | + ) { |
| 2714 | + this.$interface.find( '.volume-slider' ).slider( 'value', percent * 100 ); |
| 2715 | + } |
| 2716 | + }, |
| 2717 | + |
| 2718 | + /** |
| 2719 | + * Abstract Update volume Method must be override by plug-in / player |
| 2720 | + * interface |
| 2721 | + */ |
| 2722 | + setPlayerElementVolume: function( percent ) { |
| 2723 | + mw.log('Error player does not support volume adjustment' ); |
| 2724 | + }, |
| 2725 | + |
| 2726 | + /** |
| 2727 | + * Abstract get volume Method must be override by plug-in / player interface |
| 2728 | + * (if player does not override we return the abstract player value ) |
| 2729 | + */ |
| 2730 | + getPlayerElementVolume: function(){ |
| 2731 | + // mw.log(' error player does not support getting volume property' ); |
| 2732 | + return this.volume; |
| 2733 | + }, |
| 2734 | + |
| 2735 | + /** |
| 2736 | + * Abstract get volume muted property must be overwritten by plug-in / |
| 2737 | + * player interface (if player does not override we return the abstract |
| 2738 | + * player value ) |
| 2739 | + */ |
| 2740 | + getPlayerElementMuted: function(){ |
| 2741 | + // mw.log(' error player does not support getting mute property' ); |
| 2742 | + return this.muted; |
| 2743 | + }, |
| 2744 | + |
| 2745 | + /** |
| 2746 | + * Passes a fullscreen request to the controlBuilder interface |
| 2747 | + */ |
| 2748 | + fullscreen: function() { |
| 2749 | + this.controlBuilder.toggleFullscreen(); |
| 2750 | + }, |
| 2751 | + |
| 2752 | + /** |
| 2753 | + * Abstract method to be run post embedding the player Generally should be |
| 2754 | + * overwritten by the plug-in / player |
| 2755 | + */ |
| 2756 | + postEmbedJS:function() { |
| 2757 | + return ; |
| 2758 | + }, |
| 2759 | + |
| 2760 | + /** |
| 2761 | + * Checks the player state based on thumbnail display & paused state |
| 2762 | + * |
| 2763 | + * @return {Boolean} true if playing false if not playing |
| 2764 | + */ |
| 2765 | + isPlaying : function() { |
| 2766 | + if ( this.posterDisplayed ) { |
| 2767 | + // in stopped state |
| 2768 | + return false; |
| 2769 | + } else if ( this.paused ) { |
| 2770 | + // paused state |
| 2771 | + return false; |
| 2772 | + } else { |
| 2773 | + return true; |
| 2774 | + } |
| 2775 | + }, |
| 2776 | + |
| 2777 | + /** |
| 2778 | + * Get paused state |
| 2779 | + * |
| 2780 | + * @return {Boolean} true if playing false if not playing |
| 2781 | + */ |
| 2782 | + isPaused: function() { |
| 2783 | + return this.paused; |
| 2784 | + }, |
| 2785 | + |
| 2786 | + /** |
| 2787 | + * Get Stopped state |
| 2788 | + * |
| 2789 | + * @return {Boolean} true if stopped false if playing |
| 2790 | + */ |
| 2791 | + isStopped: function() { |
| 2792 | + return this.posterDisplayed; |
| 2793 | + }, |
| 2794 | + |
| 2795 | + // TODO temporary hack we need a better stop monitor system |
| 2796 | + stopMonitor: function(){ |
| 2797 | + clearInterval( this.monitorInterval ); |
| 2798 | + this.monitorInterval = 0; |
| 2799 | + }, |
| 2800 | + // TODO temporary hack we need a better stop monitor system |
| 2801 | + startMonitor: function(){ |
| 2802 | + this.monitor(); |
| 2803 | + }, |
| 2804 | + |
| 2805 | + /** |
| 2806 | + * Checks if the currentTime was updated outside of the getPlayerElementTime |
| 2807 | + * function |
| 2808 | + */ |
| 2809 | + checkForCurrentTimeSeek: function(){ |
| 2810 | + var _this = this; |
| 2811 | + // Check if a javascript currentTime change based seek has occurred |
| 2812 | + if( _this.previousTime != _this.currentTime && !this.userSlide && !this.seeking){ |
| 2813 | + // If the time has been updated and is in range issue a seek |
| 2814 | + if( _this.getDuration() && _this.currentTime <= _this.getDuration() ){ |
| 2815 | + var seekPercent = _this.currentTime / _this.getDuration(); |
| 2816 | + mw.log("checkForCurrentTimeSeek::" + _this.previousTime + ' != ' + |
| 2817 | + _this.currentTime + " javascript based currentTime update to " + |
| 2818 | + seekPercent + ' == ' + _this.currentTime ); |
| 2819 | + _this.previousTime = _this.currentTime; |
| 2820 | + this.doSeek( seekPercent ); |
| 2821 | + } |
| 2822 | + } |
| 2823 | + }, |
| 2824 | + |
| 2825 | + /** |
| 2826 | + * Monitor playback and update interface components. underling plugin |
| 2827 | + * objects are responsible for updating currentTime |
| 2828 | + */ |
| 2829 | + monitor: function() { |
| 2830 | + var _this = this; |
| 2831 | + |
| 2832 | + // Check for current time update outside of embed player |
| 2833 | + this.checkForCurrentTimeSeek(); |
| 2834 | + |
| 2835 | + |
| 2836 | + // Update currentTime via embedPlayer |
| 2837 | + _this.currentTime = _this.getPlayerElementTime(); |
| 2838 | + |
| 2839 | + // Update any offsets from server seek |
| 2840 | + if( _this.serverSeekTime && _this.supportsURLTimeEncoding() ){ |
| 2841 | + _this.currentTime = parseInt( _this.serverSeekTime ) + parseInt( _this.getPlayerElementTime() ); |
| 2842 | + } |
| 2843 | + |
| 2844 | + // Update the previousTime ( so we can know if the user-javascript |
| 2845 | + // changed currentTime ) |
| 2846 | + _this.previousTime = _this.currentTime; |
| 2847 | + |
| 2848 | + if( _this.pauseTime && _this.currentTime > _this.pauseTime ){ |
| 2849 | + _this.pause(); |
| 2850 | + _this.pauseTime = null; |
| 2851 | + } |
| 2852 | + |
| 2853 | + |
| 2854 | + // Check if volume was set outside of embed player function |
| 2855 | + // mw.log( ' this.volume: ' + _this.volume + ' prev Volume:: ' + |
| 2856 | + // _this.previousVolume ); |
| 2857 | + if( Math.round( _this.volume * 100 ) != Math.round( _this.previousVolume * 100 ) ) { |
| 2858 | + _this.setInterfaceVolume( _this.volume ); |
| 2859 | + if( _this._propagateEvents ){ |
| 2860 | + $j( this ).trigger('volumeChanged', _this.volume ); |
| 2861 | + } |
| 2862 | + } |
| 2863 | + |
| 2864 | + // Update the previous volume |
| 2865 | + _this.previousVolume = _this.volume; |
| 2866 | + |
| 2867 | + // Update the volume from the player element |
| 2868 | + _this.volume = this.getPlayerElementVolume(); |
| 2869 | + |
| 2870 | + // update the mute state from the player element |
| 2871 | + if( _this.muted != _this.getPlayerElementMuted() && ! _this.isStopped() ){ |
| 2872 | + mw.log( "EmbedPlayer::monitor: muted does not mach embed player" ); |
| 2873 | + _this.toggleMute(); |
| 2874 | + // Make sure they match: |
| 2875 | + _this.muted = _this.getPlayerElementMuted(); |
| 2876 | + } |
| 2877 | + |
| 2878 | + //mw.log( 'Monitor:: ' + this.currentTime + ' duration: ' + ( parseInt( |
| 2879 | + // this.getDuration() ) + 1 ) + ' is seeking: ' + this.seeking ); |
| 2880 | + |
| 2881 | + if ( this.currentTime >= 0 && this.duration ) { |
| 2882 | + if ( !this.userSlide && !this.seeking ) { |
| 2883 | + if ( parseInt( this.startOffset ) != 0 ) { |
| 2884 | + // If start offset include that calculation |
| 2885 | + this.updatePlayHead( ( this.currentTime - this.startOffset ) / this.duration ); |
| 2886 | + var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( parseFloat( this.startOffset ) + parseFloat( this.duration ) ) : ''; |
| 2887 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et ); |
| 2888 | + } else { |
| 2889 | + this.updatePlayHead( this.currentTime / this.duration ); |
| 2890 | + // Only include the end time if longTimeDisp is enabled: |
| 2891 | + var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( this.duration ) : ''; |
| 2892 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et ); |
| 2893 | + } |
| 2894 | + } |
| 2895 | + // Check if we are "done" |
| 2896 | + var endPresentationTime = ( this.startOffset ) ? ( this.startOffset + this.duration ) : this.duration; |
| 2897 | + if ( this.currentTime >= endPresentationTime ) { |
| 2898 | + //mw.log( "mWEmbedPlayer::should run clip done :: " + this.currentTime + ' > ' + endPresentationTime ); |
| 2899 | + this.onClipDone(); |
| 2900 | + } |
| 2901 | + } else { |
| 2902 | + // Media lacks duration just show end time |
| 2903 | + if ( this.isStopped() ) { |
| 2904 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 2905 | + } else if ( this.isPaused() ) { |
| 2906 | + this.controlBuilder.setStatus( gM( 'mwe-embedplayer-paused' ) ); |
| 2907 | + } else if ( this.isPlaying() ) { |
| 2908 | + if ( this.currentTime && ! this.duration ) |
| 2909 | + this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + ' /' ); |
| 2910 | + else |
| 2911 | + this.controlBuilder.setStatus( " - - - " ); |
| 2912 | + } else { |
| 2913 | + this.controlBuilder.setStatus( this.getTimeRange() ); |
| 2914 | + } |
| 2915 | + } |
| 2916 | + |
| 2917 | + // Update buffer information |
| 2918 | + this.updateBufferStatus(); |
| 2919 | + |
| 2920 | + // run the "native" progress event on the virtual html5 object if set |
| 2921 | + if( this.progressEventData ) { |
| 2922 | + // mw.log("trigger:progress event on html5 proxy"); |
| 2923 | + if( _this._propagateEvents ){ |
| 2924 | + $j( this ).trigger( 'progress', this.progressEventData ); |
| 2925 | + } |
| 2926 | + } |
| 2927 | + |
| 2928 | + // Call monitor at 250ms interval. ( use setInterval to avoid stacking |
| 2929 | + // monitor requests ) |
| 2930 | + if( ! this.isStopped() ) { |
| 2931 | + if( !this.monitorInterval ){ |
| 2932 | + this.monitorInterval = setInterval( function(){ |
| 2933 | + if( _this.monitor ) |
| 2934 | + _this.monitor(); |
| 2935 | + }, this.monitorRate ); |
| 2936 | + } |
| 2937 | + } else { |
| 2938 | + // If stopped "stop" monitor: |
| 2939 | + this.stopMonitor(); |
| 2940 | + } |
| 2941 | + |
| 2942 | + // mw.log('trigger:monitor:: ' + this.currentTime ); |
| 2943 | + if( _this._propagateEvents ){ |
| 2944 | + $j( this ).trigger( 'monitorEvent' ); |
| 2945 | + } |
| 2946 | + }, |
| 2947 | + |
| 2948 | + /** |
| 2949 | + * Abstract getPlayerElementTime function |
| 2950 | + */ |
| 2951 | + getPlayerElementTime: function(){ |
| 2952 | + mw.log("Error: getPlayerElementTime should be implemented by embed library"); |
| 2953 | + }, |
| 2954 | + |
| 2955 | + /** |
| 2956 | + * Update the Buffer status based on the local bufferedPercent var |
| 2957 | + */ |
| 2958 | + updateBufferStatus: function() { |
| 2959 | + |
| 2960 | + // Get the buffer target based for playlist vs clip |
| 2961 | + $buffer = this.$interface.find( '.mw_buffer' ); |
| 2962 | + |
| 2963 | + //mw.log(' set bufferd %:' + this.bufferedPercent ); |
| 2964 | + // Update the buffer progress bar (if available ) |
| 2965 | + if ( this.bufferedPercent != 0 ) { |
| 2966 | + // mw.log('Update buffer css: ' + ( this.bufferedPercent * 100 ) + |
| 2967 | + // '% ' + $buffer.length ); |
| 2968 | + if ( this.bufferedPercent > 1 ){ |
| 2969 | + this.bufferedPercent = 1; |
| 2970 | + } |
| 2971 | + $buffer.css({ |
| 2972 | + "width" : ( this.bufferedPercent * 100 ) + '%' |
| 2973 | + }); |
| 2974 | + $j( this ).trigger( 'updateBufferPercent', this.bufferedPercent ); |
| 2975 | + } else { |
| 2976 | + $buffer.css( "width", '0px' ); |
| 2977 | + } |
| 2978 | + |
| 2979 | + // if we have not already run the buffer start hook |
| 2980 | + if( this.bufferedPercent > 0 && !this.bufferStartFlag ) { |
| 2981 | + this.bufferStartFlag = true; |
| 2982 | + mw.log("bufferStart"); |
| 2983 | + $j( this ).trigger( 'bufferStartEvent' ); |
| 2984 | + } |
| 2985 | + |
| 2986 | + // if we have not already run the buffer end hook |
| 2987 | + if( this.bufferedPercent == 1 && !this.bufferEndFlag){ |
| 2988 | + this.bufferEndFlag = true; |
| 2989 | + $j( this ).trigger( 'bufferEndEvent' ); |
| 2990 | + } |
| 2991 | + }, |
| 2992 | + |
| 2993 | + /** |
| 2994 | + * Update the player playhead |
| 2995 | + * |
| 2996 | + * @param {Float} |
| 2997 | + * perc Value between 0 and 1 for position of playhead |
| 2998 | + */ |
| 2999 | + updatePlayHead: function( perc ) { |
| 3000 | + //mw.log( 'EmbedPlayer: updatePlayHead: '+ perc); |
| 3001 | + $playHead = this.$interface.find( '.play_head' ); |
| 3002 | + if ( this.controls && $playHead.length != 0 ) { |
| 3003 | + var val = parseInt( perc * 1000 ); |
| 3004 | + $playHead.slider( 'value', val ); |
| 3005 | + } |
| 3006 | + $j( this ).trigger('updatePlayHeadPercent', perc); |
| 3007 | + }, |
| 3008 | + |
| 3009 | + |
| 3010 | + /** |
| 3011 | + * Helper Functions for selected source |
| 3012 | + */ |
| 3013 | + |
| 3014 | + /** |
| 3015 | + * Get the current selected media source or first source |
| 3016 | + * |
| 3017 | + * @param {Number} Requested time in seconds to be passed to the server if the server supports |
| 3018 | + * supportsURLTimeEncoding |
| 3019 | + * @return src url |
| 3020 | + */ |
| 3021 | + getSrc: function( serverSeekTime ) { |
| 3022 | + if( serverSeekTime ){ |
| 3023 | + this.serverSeekTime = serverSeekTime; |
| 3024 | + } |
| 3025 | + if( this.currentTime && !this.serverSeekTime){ |
| 3026 | + this.serverSeekTime = this.currentTime; |
| 3027 | + } |
| 3028 | + |
| 3029 | + // No media element we can't return src |
| 3030 | + if( !this.mediaElement ){ |
| 3031 | + return false; |
| 3032 | + } |
| 3033 | + |
| 3034 | + // If no source selected auto select the source: |
| 3035 | + if( !this.mediaElement.selectedSource ){ |
| 3036 | + this.mediaElement.autoSelectSource(); |
| 3037 | + }; |
| 3038 | + |
| 3039 | + // Return selected source: |
| 3040 | + if( this.mediaElement.selectedSource ){ |
| 3041 | + // get the first source: |
| 3042 | + return this.mediaElement.selectedSource.getSrc( this.serverSeekTime ); |
| 3043 | + } |
| 3044 | + // No selected source return false: |
| 3045 | + return false; |
| 3046 | + }, |
| 3047 | + |
| 3048 | + /** |
| 3049 | + * If the selected src supports URL time encoding |
| 3050 | + * |
| 3051 | + * @return {Boolean} true if the src supports url time requests false if the |
| 3052 | + * src does not support url time requests |
| 3053 | + */ |
| 3054 | + supportsURLTimeEncoding: function() { |
| 3055 | + var timeUrls = mw.getConfig('EmbedPlayer.EnableURLTimeEncoding') ; |
| 3056 | + if( timeUrls == 'none' ){ |
| 3057 | + return false; |
| 3058 | + } else if( timeUrls == 'always' ){ |
| 3059 | + return this.mediaElement.selectedSource.URLTimeEncoding; |
| 3060 | + } else if( timeUrls == 'flash' ){ |
| 3061 | + if( this.mediaElement.selectedSource.URLTimeEncoding){ |
| 3062 | + // see if the current selected player is flash: |
| 3063 | + return ( this.instanceOf == 'Kplayer' ); |
| 3064 | + } |
| 3065 | + } else { |
| 3066 | + mw.log("Error:: invalid config value for EmbedPlayer.EnableURLTimeEncoding:: " + mw.getConfig('EmbedPlayer.EnableURLTimeEncoding') ); |
| 3067 | + } |
| 3068 | + return false; |
| 3069 | + } |
| 3070 | +}; |
| 3071 | + |
| 3072 | + |
| 3073 | + |
| 3074 | +/** |
| 3075 | + * mediaPlayer represents a media player plugin. |
| 3076 | + * |
| 3077 | + * @param {String} |
| 3078 | + * id id used for the plugin. |
| 3079 | + * @param {Array} |
| 3080 | + * supported_types an array of supported MIME types. |
| 3081 | + * @param {String} |
| 3082 | + * library external script containing the plugin interface code. |
| 3083 | + * @constructor |
| 3084 | + */ |
| 3085 | +function mediaPlayer( id, supported_types, library ) |
| 3086 | +{ |
| 3087 | + this.id = id; |
| 3088 | + this.supported_types = supported_types; |
| 3089 | + this.library = library; |
| 3090 | + this.loaded = false; |
| 3091 | + this.loading_callbacks = new Array(); |
| 3092 | + return this; |
| 3093 | +} |
| 3094 | +mediaPlayer.prototype = { |
| 3095 | + // Id of the mediaPlayer |
| 3096 | + id:null, |
| 3097 | + |
| 3098 | + // Mime types supported by this player |
| 3099 | + supported_types:null, |
| 3100 | + |
| 3101 | + // Player library ie: native, vlc, java etc. |
| 3102 | + library:null, |
| 3103 | + |
| 3104 | + // Flag stores the mediaPlayer load state |
| 3105 | + loaded:false, |
| 3106 | + |
| 3107 | + /** |
| 3108 | + * Checks support for a given MIME type |
| 3109 | + * |
| 3110 | + * @param {String} |
| 3111 | + * type Mime type to check against supported_types |
| 3112 | + * @return {Boolean} true if mime type is supported false if mime type is |
| 3113 | + * unsupported |
| 3114 | + */ |
| 3115 | + supportsMIMEType: function( type ) { |
| 3116 | + for ( var i = 0; i < this.supported_types.length; i++ ) { |
| 3117 | + if ( this.supported_types[i] == type ) |
| 3118 | + return true; |
| 3119 | + } |
| 3120 | + return false; |
| 3121 | + }, |
| 3122 | + |
| 3123 | + /** |
| 3124 | + * Get the "name" of the player from a predictable msg key |
| 3125 | + */ |
| 3126 | + getName: function() { |
| 3127 | + return gM( 'mwe-embedplayer-ogg-player-' + this.id ); |
| 3128 | + }, |
| 3129 | + |
| 3130 | + /** |
| 3131 | + * Loads the player library & player skin config ( if needed ) and then |
| 3132 | + * calls the callback. |
| 3133 | + * |
| 3134 | + * @param {Function} |
| 3135 | + * callback Function to be called once player library is loaded. |
| 3136 | + */ |
| 3137 | + load: function( callback ) { |
| 3138 | + // Load player library ( upper case the first letter of the library ) |
| 3139 | + mw.load( [ |
| 3140 | + 'mw.EmbedPlayer' + this.library.substr(0,1).toUpperCase() + this.library.substr(1) |
| 3141 | + ], function() { |
| 3142 | + callback(); |
| 3143 | + } ); |
| 3144 | + } |
| 3145 | +}; |
| 3146 | + |
| 3147 | +/** |
| 3148 | + * players and supported mime types In an ideal world we would query the plugin |
| 3149 | + * to get what mime types it supports in practice not always reliable/available |
| 3150 | + * |
| 3151 | + * We can't cleanly store these values per library since player library is |
| 3152 | + * loaded post player detection |
| 3153 | + * |
| 3154 | + */ |
| 3155 | + |
| 3156 | +// Flash based players: |
| 3157 | + |
| 3158 | +var kplayer = new mediaPlayer('kplayer', ['video/x-flv', 'video/h264'], 'Kplayer'); |
| 3159 | + |
| 3160 | +// Java based player |
| 3161 | +var cortadoPlayer = new mediaPlayer( 'cortado', ['video/ogg', 'audio/ogg', 'application/ogg'], 'Java' ); |
| 3162 | + |
| 3163 | +// Native html5 players |
| 3164 | +var oggNativePlayer = new mediaPlayer( 'oggNative', ['video/ogg', 'audio/ogg', 'application/ogg' ], 'Native' ); |
| 3165 | +var h264NativePlayer = new mediaPlayer( 'h264Native', ['video/h264'], 'Native' ); |
| 3166 | +var webmNativePlayer = new mediaPlayer( 'webmNative', ['video/webm'], 'Native' ); |
| 3167 | + |
| 3168 | +// VLC player |
| 3169 | +var vlcMineList = ['video/ogg', 'audio/ogg', 'application/ogg', 'video/x-flv', 'video/mp4', 'video/h264', 'video/x-msvideo', 'video/mpeg']; |
| 3170 | +var vlcPlayer = new mediaPlayer( 'vlc-player', vlcMineList, 'Vlc' ); |
| 3171 | + |
| 3172 | +// Generic plugin |
| 3173 | +var oggPluginPlayer = new mediaPlayer( 'oggPlugin', ['video/ogg', 'application/ogg'], 'Generic' ); |
| 3174 | + |
| 3175 | +// HTML player for timed display of html content |
| 3176 | +var htmlPlayer = new mediaPlayer( 'html', ['text/html', 'image/jpeg', 'image/png', 'image/svg'], 'Html' ); |
| 3177 | + |
| 3178 | + |
| 3179 | +/** |
| 3180 | + * mediaPlayers is a collection of mediaPlayer objects supported by the client. |
| 3181 | + * |
| 3182 | + * @constructor |
| 3183 | + */ |
| 3184 | +function mediaPlayers() |
| 3185 | +{ |
| 3186 | + this.init(); |
| 3187 | +} |
| 3188 | + |
| 3189 | +mediaPlayers.prototype = |
| 3190 | +{ |
| 3191 | + // The list of players supported |
| 3192 | + players : null, |
| 3193 | + |
| 3194 | + // Store per mime-type prefrences for players |
| 3195 | + preference : { }, |
| 3196 | + |
| 3197 | + // Stores the default set of players for a given mime type |
| 3198 | + defaultPlayers : { }, |
| 3199 | + |
| 3200 | + /** |
| 3201 | + * Initializartion function sets the default order for players for a given |
| 3202 | + * mime type |
| 3203 | + */ |
| 3204 | + init: function() { |
| 3205 | + this.players = new Array(); |
| 3206 | + this.loadPreferences(); |
| 3207 | + |
| 3208 | + // set up default players order for each library type |
| 3209 | + this.defaultPlayers['video/x-flv'] = ['Kplayer', 'Vlc']; |
| 3210 | + this.defaultPlayers['video/h264'] = ['Native', 'Kplayer', 'Vlc']; |
| 3211 | + |
| 3212 | + this.defaultPlayers['video/ogg'] = ['Native', 'Vlc', 'Java', 'Generic']; |
| 3213 | + this.defaultPlayers['video/webm'] = ['Native', 'Vlc']; |
| 3214 | + this.defaultPlayers['application/ogg'] = ['Native', 'Vlc', 'Java', 'Generic']; |
| 3215 | + this.defaultPlayers['audio/ogg'] = ['Native', 'Vlc', 'Java' ]; |
| 3216 | + this.defaultPlayers['video/mp4'] = ['Vlc']; |
| 3217 | + this.defaultPlayers['video/mpeg'] = ['Vlc']; |
| 3218 | + this.defaultPlayers['video/x-msvideo'] = ['Vlc']; |
| 3219 | + |
| 3220 | + this.defaultPlayers['text/html'] = ['Html']; |
| 3221 | + this.defaultPlayers['image/jpeg'] = ['Html']; |
| 3222 | + this.defaultPlayers['image/png'] = ['Html']; |
| 3223 | + this.defaultPlayers['image/svg'] = ['Html']; |
| 3224 | + |
| 3225 | + }, |
| 3226 | + |
| 3227 | + /** |
| 3228 | + * Adds a Player to the player list |
| 3229 | + * |
| 3230 | + * @param {Object} |
| 3231 | + * player Player object to be added |
| 3232 | + */ |
| 3233 | + addPlayer: function( player ) { |
| 3234 | + for ( var i = 0; i < this.players.length; i++ ) { |
| 3235 | + if ( this.players[i].id == player.id ) { |
| 3236 | + // Player already found |
| 3237 | + return ; |
| 3238 | + } |
| 3239 | + } |
| 3240 | + |
| 3241 | + |
| 3242 | + // Add the player: |
| 3243 | + this.players.push( player ); |
| 3244 | + }, |
| 3245 | + |
| 3246 | + /** |
| 3247 | + * Checks if a player is supported by id |
| 3248 | + */ |
| 3249 | + isSupportedPlayer: function( playerId ){ |
| 3250 | + for( var i=0; i < this.players.length; i++ ){ |
| 3251 | + if( this.players[i].id == playerId ){ |
| 3252 | + return true; |
| 3253 | + } |
| 3254 | + } |
| 3255 | + return false; |
| 3256 | + }, |
| 3257 | + |
| 3258 | + /** |
| 3259 | + * get players that support a given mimeType |
| 3260 | + * |
| 3261 | + * @param {String} |
| 3262 | + * mimeType Mime type of player set |
| 3263 | + * @return {Array} Array of players that support a the requested mime type |
| 3264 | + */ |
| 3265 | + getMIMETypePlayers: function( mimeType ) { |
| 3266 | + var mimePlayers = new Array(); |
| 3267 | + var _this = this; |
| 3268 | + if ( this.defaultPlayers[mimeType] ) { |
| 3269 | + $j.each( this.defaultPlayers[ mimeType ], function( d, lib ) { |
| 3270 | + var library = _this.defaultPlayers[ mimeType ][ d ]; |
| 3271 | + for ( var i = 0; i < _this.players.length; i++ ) { |
| 3272 | + if ( _this.players[i].library == library && _this.players[i].supportsMIMEType( mimeType ) ) { |
| 3273 | + mimePlayers.push( _this.players[i] ); |
| 3274 | + } |
| 3275 | + } |
| 3276 | + } ); |
| 3277 | + } |
| 3278 | + return mimePlayers; |
| 3279 | + }, |
| 3280 | + |
| 3281 | + /** |
| 3282 | + * Default player for a given mime type |
| 3283 | + * |
| 3284 | + * @param {String} |
| 3285 | + * mimeType Mime type of the requested player |
| 3286 | + * @return Player for mime type null if no player found |
| 3287 | + */ |
| 3288 | + defaultPlayer : function( mimeType ) { |
| 3289 | + // mw.log( "get defaultPlayer for " + mimeType ); |
| 3290 | + var mimePlayers = this.getMIMETypePlayers( mimeType ); |
| 3291 | + if ( mimePlayers.length > 0 ) |
| 3292 | + { |
| 3293 | + // Check for prior preference for this mime type |
| 3294 | + for ( var i = 0; i < mimePlayers.length; i++ ) { |
| 3295 | + if ( mimePlayers[i].id == this.preference[mimeType] ) |
| 3296 | + return mimePlayers[i]; |
| 3297 | + } |
| 3298 | + // Otherwise just return the first compatible player |
| 3299 | + // (it will be chosen according to the defaultPlayers list |
| 3300 | + return mimePlayers[0]; |
| 3301 | + } |
| 3302 | + // mw.log( 'No default player found for ' + mimeType ); |
| 3303 | + return null; |
| 3304 | + }, |
| 3305 | + |
| 3306 | + /** |
| 3307 | + * Sets the format preference. |
| 3308 | + * |
| 3309 | + * @param {String} |
| 3310 | + * mimeFormat Prefered format |
| 3311 | + */ |
| 3312 | + setFormatPreference : function ( mimeFormat ) { |
| 3313 | + this.preference['format_preference'] = mimeFormat; |
| 3314 | + mw.setUserConfig( 'playerPref', this.preference); |
| 3315 | + }, |
| 3316 | + |
| 3317 | + /** |
| 3318 | + * Sets the player preference |
| 3319 | + * |
| 3320 | + * @param {String} |
| 3321 | + * playerId Prefered player id |
| 3322 | + * @param {String} |
| 3323 | + * mimeType Mime type for the associated player stream |
| 3324 | + */ |
| 3325 | + setPlayerPreference : function( playerId, mimeType ) { |
| 3326 | + var selectedPlayer = null; |
| 3327 | + for ( var i = 0; i < this.players.length; i++ ) { |
| 3328 | + if ( this.players[i].id == playerId ) { |
| 3329 | + selectedPlayer = this.players[i]; |
| 3330 | + mw.log( 'EmbedPlayer::setPlayerPreference: choosing ' + playerId + ' for ' + mimeType ); |
| 3331 | + this.preference[ mimeType ] = playerId; |
| 3332 | + mw.setUserConfig( 'playerPref', this.preference ); |
| 3333 | + break; |
| 3334 | + } |
| 3335 | + } |
| 3336 | + // Update All the player instances: |
| 3337 | + if ( selectedPlayer ) { |
| 3338 | + var playerList = mw.playerManager.getPlayerList(); |
| 3339 | + for ( var i = 0; i < playerList.length; i++ ) { |
| 3340 | + var embed = $j( '#' + playerList[i] ).get( 0 ); |
| 3341 | + if ( embed.mediaElement.selectedSource && ( embed.mediaElement.selectedSource.mimeType == mimeType ) ) |
| 3342 | + { |
| 3343 | + embed.selectPlayer( selectedPlayer ); |
| 3344 | + mw.log( 'EmbedPlayer::setPlayerPreference: using ' + embed.selectedPlayer.getName() + ' for ' + embed.mediaElement.selectedSource.getTitle() ); |
| 3345 | + } |
| 3346 | + } |
| 3347 | + } |
| 3348 | + }, |
| 3349 | + |
| 3350 | + /** |
| 3351 | + * Loads the user preference settings from a cookie |
| 3352 | + */ |
| 3353 | + loadPreferences : function ( ) { |
| 3354 | + this.preference = { }; |
| 3355 | + // see if we have a cookie set to a clientSupported type: |
| 3356 | + preferenceConfig = $.cookie( 'playerPref' ); |
| 3357 | + if( typeof preferenceConfig == 'object' ) { |
| 3358 | + this.preference = preferenceConfig; |
| 3359 | + } |
| 3360 | + } |
| 3361 | +}; |
| 3362 | + |
| 3363 | +/** |
| 3364 | + * mw.EmbedTypes object handles setting and getting of supported embed types: |
| 3365 | + * closely mirrors OggHandler so that its easier to share efforts in this area: |
| 3366 | + * http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/OggHandler/OggPlayer.js |
| 3367 | + */ |
| 3368 | +mw.EmbedTypes = { |
| 3369 | + |
| 3370 | + // MediaPlayers object ( supports methods for quering set of browser players ) |
| 3371 | + mediaPlayers: null, |
| 3372 | + |
| 3373 | + // Detect flag for completion |
| 3374 | + detect_done:false, |
| 3375 | + |
| 3376 | + /** |
| 3377 | + * Runs the detect method and update the detect_done flag |
| 3378 | + * |
| 3379 | + * @constructor |
| 3380 | + */ |
| 3381 | + init: function() { |
| 3382 | + // detect supported types |
| 3383 | + this.detect(); |
| 3384 | + this.detect_done = true; |
| 3385 | + }, |
| 3386 | + |
| 3387 | + getMediaPlayers: function(){ |
| 3388 | + if( this.mediaPlayers ){ |
| 3389 | + return this.mediaPlayers; |
| 3390 | + } |
| 3391 | + this.mediaPlayers = new mediaPlayers(); |
| 3392 | + // detect available players |
| 3393 | + this.detectPlayers(); |
| 3394 | + return this.mediaPlayers; |
| 3395 | + }, |
| 3396 | + |
| 3397 | + /** |
| 3398 | + * If the browsers supports a given mimetype |
| 3399 | + * |
| 3400 | + * @param {String} |
| 3401 | + * mimeType Mime type for browser plug-in check |
| 3402 | + */ |
| 3403 | + supportedMimeType: function( mimeType ) { |
| 3404 | + for ( var i =0; i < navigator.plugins.length; i++ ) { |
| 3405 | + var plugin = navigator.plugins[i]; |
| 3406 | + if ( typeof plugin[ mimeType ] != "undefined" ) |
| 3407 | + return true; |
| 3408 | + } |
| 3409 | + return false; |
| 3410 | + }, |
| 3411 | + |
| 3412 | + /** |
| 3413 | + * Detects what plug-ins the client supports |
| 3414 | + */ |
| 3415 | + detectPlayers: function() { |
| 3416 | + mw.log( "embedPlayer: running detect" ); |
| 3417 | + // every browser supports html rendering: |
| 3418 | + this.mediaPlayers.addPlayer( htmlPlayer ); |
| 3419 | + // In Mozilla, navigator.javaEnabled() only tells us about preferences, we need to |
| 3420 | + // search navigator.mimeTypes to see if it's installed |
| 3421 | + try{ |
| 3422 | + var javaEnabled = navigator.javaEnabled(); |
| 3423 | + } catch ( e ){ |
| 3424 | + |
| 3425 | + } |
| 3426 | + // Some browsers filter out duplicate mime types, hiding some plugins |
| 3427 | + var uniqueMimesOnly = $j.browser.opera || $j.browser.safari; |
| 3428 | + |
| 3429 | + // Opera will switch off javaEnabled in preferences if java can't be |
| 3430 | + // found. And it doesn't register an application/x-java-applet mime type like |
| 3431 | + // Mozilla does. |
| 3432 | + if ( javaEnabled && ( navigator.appName == 'Opera' ) ) { |
| 3433 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 3434 | + } |
| 3435 | + |
| 3436 | + // ActiveX plugins |
| 3437 | + if ( $j.browser.msie ) { |
| 3438 | + // check for flash |
| 3439 | + if ( this.testActiveX( 'ShockwaveFlash.ShockwaveFlash' ) ) { |
| 3440 | + this.mediaPlayers.addPlayer( kplayer ); |
| 3441 | + // this.mediaPlayers.addPlayer( flowPlayer ); |
| 3442 | + } |
| 3443 | + // VLC |
| 3444 | + if ( this.testActiveX( 'VideoLAN.VLCPlugin.2' ) ) { |
| 3445 | + this.mediaPlayers.addPlayer( vlcPlayer ); |
| 3446 | + } |
| 3447 | + |
| 3448 | + // Java ActiveX |
| 3449 | + if ( this.testActiveX( 'JavaWebStart.isInstalled' ) ) { |
| 3450 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 3451 | + } |
| 3452 | + |
| 3453 | + // quicktime (currently off) |
| 3454 | + // if ( this.testActiveX( |
| 3455 | + // 'QuickTimeCheckObject.QuickTimeCheck.1' ) ) |
| 3456 | + // this.mediaPlayers.addPlayer(quicktimeActiveXPlayer); |
| 3457 | + } |
| 3458 | + // <video> element |
| 3459 | + if ( typeof HTMLVideoElement == 'object' // Firefox, Safari |
| 3460 | + || typeof HTMLVideoElement == 'function' ) // Opera |
| 3461 | + { |
| 3462 | + // Test what codecs the native player supports: |
| 3463 | + try { |
| 3464 | + var dummyvid = document.createElement( "video" ); |
| 3465 | + if( dummyvid.canPlayType ) { |
| 3466 | + // Add the webm player |
| 3467 | + if( dummyvid.canPlayType('video/webm; codecs="vp8, vorbis"') ){ |
| 3468 | + this.mediaPlayers.addPlayer( webmNativePlayer ); |
| 3469 | + } |
| 3470 | + |
| 3471 | + // Test for h264: |
| 3472 | + if ( dummyvid.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"' ) ) { |
| 3473 | + this.mediaPlayers.addPlayer( h264NativePlayer ); |
| 3474 | + } |
| 3475 | + // For now if Android assume we support h264Native (FIXME |
| 3476 | + // test on real devices ) |
| 3477 | + if ( mw.isAndroid2() ){ |
| 3478 | + this.mediaPlayers.addPlayer( h264NativePlayer ); |
| 3479 | + } |
| 3480 | + |
| 3481 | + // Test for ogg |
| 3482 | + if ( dummyvid.canPlayType( 'video/ogg; codecs="theora,vorbis"' ) ) { |
| 3483 | + this.mediaPlayers.addPlayer( oggNativePlayer ); |
| 3484 | + // older versions of safari do not support canPlayType, |
| 3485 | + // but xiph qt registers mimetype via quicktime plugin |
| 3486 | + } else if ( this.supportedMimeType( 'video/ogg' ) ) { |
| 3487 | + this.mediaPlayers.addPlayer( oggNativePlayer ); |
| 3488 | + } |
| 3489 | + } |
| 3490 | + } catch ( e ) { |
| 3491 | + mw.log( 'could not run canPlayType ' + e ); |
| 3492 | + } |
| 3493 | + } |
| 3494 | + |
| 3495 | + // "navigator" plugins |
| 3496 | + if ( navigator.mimeTypes && navigator.mimeTypes.length > 0 ) { |
| 3497 | + for ( var i = 0; i < navigator.mimeTypes.length; i++ ) { |
| 3498 | + var type = navigator.mimeTypes[i].type; |
| 3499 | + var semicolonPos = type.indexOf( ';' ); |
| 3500 | + if ( semicolonPos > -1 ) { |
| 3501 | + type = type.substr( 0, semicolonPos ); |
| 3502 | + } |
| 3503 | + // mw.log( 'on type: ' + type ); |
| 3504 | + var pluginName = navigator.mimeTypes[i].enabledPlugin ? navigator.mimeTypes[i].enabledPlugin.name : ''; |
| 3505 | + if ( !pluginName ) { |
| 3506 | + // In case it is null or undefined |
| 3507 | + pluginName = ''; |
| 3508 | + } |
| 3509 | + if ( pluginName.toLowerCase() == 'vlc multimedia plugin' || pluginName.toLowerCase() == 'vlc multimedia plug-in' ) { |
| 3510 | + this.mediaPlayers.addPlayer( vlcPlayer ); |
| 3511 | + continue; |
| 3512 | + } |
| 3513 | + |
| 3514 | + if ( type == 'application/x-java-applet' ) { |
| 3515 | + this.mediaPlayers.addPlayer( cortadoPlayer ); |
| 3516 | + continue; |
| 3517 | + } |
| 3518 | + |
| 3519 | + if ( (type == 'video/mpeg' || type == 'video/x-msvideo') && |
| 3520 | + pluginName.toLowerCase() == 'vlc multimedia plugin' ) { |
| 3521 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 3522 | + } |
| 3523 | + |
| 3524 | + if ( type == 'application/ogg' ) { |
| 3525 | + if ( pluginName.toLowerCase() == 'vlc multimedia plugin' ) { |
| 3526 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 3527 | + // else if ( pluginName.indexOf( 'QuickTime' ) > -1 ) |
| 3528 | + // this.mediaPlayers.addPlayer(quicktimeMozillaPlayer); |
| 3529 | + } else { |
| 3530 | + this.mediaPlayers.addPlayer( oggPluginPlayer ); |
| 3531 | + } |
| 3532 | + continue; |
| 3533 | + } else if ( uniqueMimesOnly ) { |
| 3534 | + if ( type == 'application/x-vlc-player' ) { |
| 3535 | + this.mediaPlayers.addPlayer( vlcMozillaPlayer ); |
| 3536 | + continue; |
| 3537 | + } else if ( type == 'video/quicktime' ) { |
| 3538 | + // this.mediaPlayers.addPlayer(quicktimeMozillaPlayer); |
| 3539 | + continue; |
| 3540 | + } |
| 3541 | + } |
| 3542 | + |
| 3543 | + if ( type == 'application/x-shockwave-flash' ) { |
| 3544 | + |
| 3545 | + this.mediaPlayers.addPlayer( kplayer ); |
| 3546 | + // this.mediaPlayers.addPlayer( flowPlayer ); |
| 3547 | + |
| 3548 | + // check version to add omtk: |
| 3549 | + if( navigator.plugins["Shockwave Flash"] ){ |
| 3550 | + var flashDescription = navigator.plugins["Shockwave Flash"].description; |
| 3551 | + var descArray = flashDescription.split( " " ); |
| 3552 | + var tempArrayMajor = descArray[2].split( "." ); |
| 3553 | + var versionMajor = tempArrayMajor[0]; |
| 3554 | + // mw.log("version of flash: " + versionMajor); |
| 3555 | + } |
| 3556 | + continue; |
| 3557 | + } |
| 3558 | + } |
| 3559 | + } |
| 3560 | + |
| 3561 | + // Allow extensions to detect and add their own "players" |
| 3562 | + mw.log("trigger::embedPlayerUpdateMediaPlayersEvent"); |
| 3563 | + $j( mw ).trigger( 'embedPlayerUpdateMediaPlayersEvent' , this.mediaPlayers ); |
| 3564 | + |
| 3565 | + }, |
| 3566 | + |
| 3567 | + /** |
| 3568 | + * Test IE for activeX by name |
| 3569 | + * |
| 3570 | + * @param {String} |
| 3571 | + * name Name of ActiveXObject to look for |
| 3572 | + */ |
| 3573 | + testActiveX : function ( name ) { |
| 3574 | + mw.log("EmbedPlayer::detect: test testActiveX: " + name); |
| 3575 | + var hasObj = true; |
| 3576 | + try { |
| 3577 | + // No IE, not a class called "name", it's a variable |
| 3578 | + var obj = new ActiveXObject( '' + name ); |
| 3579 | + } catch ( e ) { |
| 3580 | + hasObj = false; |
| 3581 | + } |
| 3582 | + return hasObj; |
| 3583 | + } |
| 3584 | +}; |
| 3585 | + |
| 3586 | +} )( mediaWiki, jQuery ); |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayer.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 3587 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/embedVideo.js:r51646 |
2 | 3588 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/embedVideo.js:r58211-58321 |
Added: svn:eol-style |
3 | 3589 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerJava.js |
— | — | @@ -0,0 +1,244 @@ |
| 2 | +/** |
| 3 | +* List of domains and hosted location of cortado. Lets clients avoid the security warning for cross domain cortado |
| 4 | +*/ |
| 5 | +window.cortadoDomainLocations = { |
| 6 | + 'upload.wikimedia.org' : 'http://upload.wikimedia.org/jars/cortado.jar' |
| 7 | +}; |
| 8 | + |
| 9 | +// Set the default location for CortadoApplet |
| 10 | +mw.setDefaultConfig( 'relativeCortadoAppletPath', |
| 11 | + mw.getMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/cortado/cortado-ovtk-stripped-0.6.0.jar' |
| 12 | +); |
| 13 | + |
| 14 | +mw.EmbedPlayerJava = { |
| 15 | + |
| 16 | + // Instance name: |
| 17 | + instanceOf: 'Java', |
| 18 | + |
| 19 | + // Supported feature set of the cortado applet: |
| 20 | + supports: { |
| 21 | + 'playHead' : true, |
| 22 | + 'pause' : true, |
| 23 | + 'stop' : true, |
| 24 | + 'fullscreen' : false, |
| 25 | + 'timeDisplay' : true, |
| 26 | + 'volumeControl' : false |
| 27 | + }, |
| 28 | + |
| 29 | + /** |
| 30 | + * Output the the embed html |
| 31 | + */ |
| 32 | + doEmbedHTML: function () { |
| 33 | + var _this = this; |
| 34 | + mw.log( "java play url:" + this.getSrc( this.seek_time_sec ) ); |
| 35 | + |
| 36 | + mw.log('Applet location: ' + this.getAppletLocation() ); |
| 37 | + mw.log('Play media: ' + this.getSrc() ); |
| 38 | + |
| 39 | + // load directly in the page.. |
| 40 | + // ( media must be on the same server or applet must be signed ) |
| 41 | + var appletCode = '' + |
| 42 | + '<applet id="' + this.pid + '" code="com.fluendo.player.Cortado.class" ' + |
| 43 | + 'archive="' + this.getAppletLocation() + '" width="' + parseInt( this.getWidth() ) + '" ' + |
| 44 | + 'height="' + parseInt( this.getHeight() ) + '"> ' + "\n" + |
| 45 | + '<param name="url" value="' + this.getSrc() + '" /> ' + "\n" + |
| 46 | + '<param name="local" value="false"/>' + "\n" + |
| 47 | + '<param name="keepaspect" value="true" />' + "\n" + |
| 48 | + '<param name="video" value="true" />' + "\n" + |
| 49 | + '<param name="showStatus" value="hide" />' + "\n" + |
| 50 | + '<param name="audio" value="true" />' + "\n" + |
| 51 | + '<param name="seekable" value="true" />' + "\n"; |
| 52 | + |
| 53 | + // Add the duration attribute if set: |
| 54 | + if( this.getDuration() ){ |
| 55 | + appletCode += '<param name="duration" value="' + parseFloat( this.getDuration() ) + '" />' + "\n"; |
| 56 | + } |
| 57 | + |
| 58 | + appletCode += '<param name="BufferSize" value="4096" />' + |
| 59 | + '<param name="BufferHigh" value="25">' + |
| 60 | + '<param name="BufferLow" value="5">' + |
| 61 | + '</applet>'; |
| 62 | + |
| 63 | + $j( this ).html( appletCode ); |
| 64 | + |
| 65 | + // Wrap it in an iframe to avoid hanging the event thread in FF 2/3 and similar |
| 66 | + // NOTE: This breaks reference to the applet so disabled for now: |
| 67 | + /*if ( $j.browser.mozilla ) { |
| 68 | + var iframe = document.createElement( 'iframe' ); |
| 69 | + iframe.setAttribute( 'width', this.getWidth() ); |
| 70 | + iframe.setAttribute( 'height', this.getHeight() ); |
| 71 | + iframe.setAttribute( 'scrolling', 'no' ); |
| 72 | + iframe.setAttribute( 'frameborder', 0 ); |
| 73 | + iframe.setAttribute( 'marginWidth', 0 ); |
| 74 | + iframe.setAttribute( 'marginHeight', 0 ); |
| 75 | + iframe.setAttribute( 'id', 'cframe_' + this.id ) |
| 76 | + |
| 77 | + // Append the iframe to the embed object: |
| 78 | + $j( this ).html( iframe ); |
| 79 | + |
| 80 | + // Write out the iframe content: |
| 81 | + var newDoc = iframe.contentDocument; |
| 82 | + newDoc.open(); |
| 83 | + newDoc.write( '<html><body>' + appletCode + '</body></html>' ); |
| 84 | + // spurious error in some versions of FF, no workaround known |
| 85 | + newDoc.close(); |
| 86 | + } else { |
| 87 | + $j( this ).html( appletCode ); |
| 88 | + //} |
| 89 | + */ |
| 90 | + |
| 91 | + // Start the monitor: |
| 92 | + _this.monitor(); |
| 93 | + }, |
| 94 | + |
| 95 | + /** |
| 96 | + * Get the applet location |
| 97 | + */ |
| 98 | + getAppletLocation: function() { |
| 99 | + var mediaSrc = this.getSrc(); |
| 100 | + var appletLoc = false; |
| 101 | + if ( |
| 102 | + !mw.isLocalDomain( mediaSrc ) |
| 103 | + || |
| 104 | + !mw.isLocalDomain( mw.getMwEmbedPath() |
| 105 | + || |
| 106 | + mw.getConfig( 'relativeCortadoAppletPath' ) === false ) |
| 107 | + ){ |
| 108 | + if ( window.cortadoDomainLocations[ mw.parseUri( mediaSrc ).host ] ) { |
| 109 | + appletLoc = window.cortadoDomainLocations[ mw.parseUri( mediaSrc ).host ]; |
| 110 | + } else { |
| 111 | + appletLoc = 'http://theora.org/cortado.jar'; |
| 112 | + } |
| 113 | + } else { |
| 114 | + // Get the local relative cortado applet location: |
| 115 | + appletLoc = mw.getConfig( 'relativeCortadoAppletPath' ); |
| 116 | + } |
| 117 | + return appletLoc; |
| 118 | + }, |
| 119 | + |
| 120 | + /** |
| 121 | + * Get the embed player time |
| 122 | + */ |
| 123 | + getPlayerElementTime: function() { |
| 124 | + this.getPlayerElement(); |
| 125 | + var currentTime = 0; |
| 126 | + if ( this.playerElement ) { |
| 127 | + try { |
| 128 | + // java reads ogg media time.. so no need to add the start or seek offset: |
| 129 | + //mw.log(' ct: ' + this.playerElement.getPlayPosition() + ' ' + this.supportsURLTimeEncoding()); |
| 130 | + |
| 131 | + currentTime = this.playerElement.currentTime; |
| 132 | + // ( java cortado has -1 time ~sometimes~ ) |
| 133 | + /*if ( this.currentTime < 0 ) { |
| 134 | + mw.log( 'pp:' + this.currentTime ); |
| 135 | + // Probably reached clip ( should fire ondone event instead ) |
| 136 | + this.onClipDone(); |
| 137 | + }*/ |
| 138 | + } catch ( e ) { |
| 139 | + mw.log( 'could not get time from jPlayer: ' + e ); |
| 140 | + } |
| 141 | + }else{ |
| 142 | + mw.log(" could not find playerElement " ); |
| 143 | + } |
| 144 | + return currentTime; |
| 145 | + }, |
| 146 | + |
| 147 | + /** |
| 148 | + * Seek in the ogg stream |
| 149 | + * ( Cortado seek does not seem to work very well ) |
| 150 | + * @param {Float} percentage Percentage to seek into the stream |
| 151 | + */ |
| 152 | + doSeek: function( percentage ) { |
| 153 | + mw.log( 'java:seek:p: ' + percentage + ' : ' + this.supportsURLTimeEncoding() + ' dur: ' + this.getDuration() + ' sts:' + this.seek_time_sec ); |
| 154 | + this.getPlayerElement(); |
| 155 | + |
| 156 | + if ( this.supportsURLTimeEncoding() ) { |
| 157 | + this.parent_doSeek( percentage ); |
| 158 | + } else if ( this.playerElement ) { |
| 159 | + // do a (generally broken) local seek: |
| 160 | + mw.log( "Cortado seek is not very accurate :: doSeek::" + ( percentage * parseFloat( this.getDuration() ) ) ); |
| 161 | + this.playerElement.currentTime = ( percentage * parseFloat( this.getDuration() ) ); |
| 162 | + } else { |
| 163 | + this.doPlayThenSeek( percentage ); |
| 164 | + } |
| 165 | + |
| 166 | + // Run the onSeeking interface update |
| 167 | + this.controlBuilder.onSeek(); |
| 168 | + }, |
| 169 | + |
| 170 | + /** |
| 171 | + * Issue a play request then seek to a percentage point in the stream |
| 172 | + * @param {Float} percentage Percentage to seek into the stream |
| 173 | + */ |
| 174 | + doPlayThenSeek: function( percentage ) { |
| 175 | + mw.log( 'doPlayThenSeek' ); |
| 176 | + var _this = this; |
| 177 | + this.play(); |
| 178 | + var rfsCount = 0; |
| 179 | + var readyForSeek = function() { |
| 180 | + _this.getPlayerElement(); |
| 181 | + // if we have .jre ~in theory~ we can seek (but probably not) |
| 182 | + if ( _this.playerElement ) { |
| 183 | + _this.doSeek( perc ); |
| 184 | + } else { |
| 185 | + // try to get player for 10 seconds: |
| 186 | + if ( rfsCount < 200 ) { |
| 187 | + setTimeout( readyForSeek, 50 ); |
| 188 | + rfsCount++; |
| 189 | + } else { |
| 190 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 191 | + } |
| 192 | + } |
| 193 | + }; |
| 194 | + readyForSeek(); |
| 195 | + }, |
| 196 | + |
| 197 | + /** |
| 198 | + * Update the playerElement instance with a pointer to the embed object |
| 199 | + */ |
| 200 | + getPlayerElement: function() { |
| 201 | + if( !$j( '#' + this.pid ).length ) { |
| 202 | + return false; |
| 203 | + } |
| 204 | + //mw.log( 'getPlayerElement::' + this.pid ); |
| 205 | + this.playerElement = $j( '#' + this.pid ).get( 0 ); |
| 206 | + //this.playerElement = document.applets[ 0 ]; |
| 207 | + // NOTE we are currently not using the iframe embed method: |
| 208 | + //if ( $j.browser.mozilla ) { |
| 209 | + // this.playerElement = $j('#cframe_' + this.id).contents().find( '#' + this.pid ); |
| 210 | + //} else { |
| 211 | + // this.playerElement = $j( '#' + this.pid ).get( 0 ); |
| 212 | + //} |
| 213 | + return this.playerElement; |
| 214 | + }, |
| 215 | + |
| 216 | + /** |
| 217 | + * Issue the doPlay request to the playerElement |
| 218 | + * calls parent_play to update interface |
| 219 | + */ |
| 220 | + play: function() { |
| 221 | + this.getPlayerElement(); |
| 222 | + this.parent_play(); |
| 223 | + if ( this.playerElement ) { |
| 224 | + try{ |
| 225 | + this.playerElement.play(); |
| 226 | + }catch( e ){ |
| 227 | + mw.log("EmbedPlayerJava::Could not issue play request"); |
| 228 | + } |
| 229 | + } |
| 230 | + }, |
| 231 | + |
| 232 | + /** |
| 233 | + * Pause playback |
| 234 | + * calls parent_pause to update interface |
| 235 | + */ |
| 236 | + pause: function() { |
| 237 | + this.getPlayerElement(); |
| 238 | + // Update the interface |
| 239 | + this.parent_pause(); |
| 240 | + // Call the pause function if it exists: |
| 241 | + if ( this.playerElement ) { |
| 242 | + this.playerElement.pause(); |
| 243 | + } |
| 244 | + } |
| 245 | +}; |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerJava.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 246 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/javaEmbed.js:r51646 |
2 | 247 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/javaEmbed.js:r58211-58321 |
Added: svn:eol-style |
3 | 248 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerVlc.js |
— | — | @@ -0,0 +1,365 @@ |
| 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 | +mw.EmbedPlayerVlc = { |
| 8 | + |
| 9 | + //Instance Name: |
| 10 | + instanceOf : 'Vlc', |
| 11 | + |
| 12 | + //What the vlc player / plug-in supports: |
| 13 | + supports : { |
| 14 | + 'playHead':true, |
| 15 | + 'pause':true, |
| 16 | + 'stop':true, |
| 17 | + 'fullscreen':true, |
| 18 | + 'timeDisplay':true, |
| 19 | + 'volumeControl':true, |
| 20 | + |
| 21 | + 'playlist_driver':true, // if the object supports playlist functions |
| 22 | + 'overlay':false |
| 23 | + }, |
| 24 | + |
| 25 | + // The previous state of the player instance |
| 26 | + prevState : 0, |
| 27 | + |
| 28 | + // Counter for waiting for vlc embed to be ready |
| 29 | + waitForVlcCount:0, |
| 30 | + |
| 31 | + // Store the current play time for vlc |
| 32 | + vlcCurrentTime: 0, |
| 33 | + |
| 34 | + /** |
| 35 | + * Get embed HTML |
| 36 | + */ |
| 37 | + doEmbedHTML: function() { |
| 38 | + var _this = this; |
| 39 | + $j( this ).html( |
| 40 | + '<object classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921" ' + |
| 41 | + 'codebase="http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab#Version=0,8,6,0" ' + |
| 42 | + 'id="' + this.pid + '" events="True" height="' + this.getPlayerHeight() + '" width="' + this.getPlayerWidth() + '"' + |
| 43 | + '>' + |
| 44 | + '<param name="MRL" value="">' + |
| 45 | + '<param name="ShowDisplay" value="True">' + |
| 46 | + '<param name="AutoLoop" value="False">' + |
| 47 | + '<param name="AutoPlay" value="False">' + |
| 48 | + '<param name="Volume" value="50">' + |
| 49 | + '<param name="StartTime" value="0">' + |
| 50 | + '<embed pluginspage="http://www.videolan.org" type="application/x-vlc-plugin" ' + |
| 51 | + 'progid="VideoLAN.VLCPlugin.2" name="' + this.pid + '" ' + |
| 52 | + 'height="' + this.getHeight() + '" width="' + this.getWidth() + '" ' + |
| 53 | + // set the style too 'just to be sure' |
| 54 | + 'style="width:' + this.getWidth() + 'px;height:' + this.getHeight() + 'px;" ' + |
| 55 | + '>' + |
| 56 | + '</object>' |
| 57 | + ) |
| 58 | + /* |
| 59 | + $j( this ).html( |
| 60 | + '<embed type="application/x-vlc-plugin" pluginspage="http://www.videolan.org" version="VideoLAN.VLCPlugin.2" '+ |
| 61 | + 'width="' + this.width +'" ' + |
| 62 | + 'height="' + this.height + '" ' + |
| 63 | + 'id="' + this.pid + '"> ' + |
| 64 | + '</embed>' |
| 65 | + );*/ |
| 66 | + |
| 67 | + |
| 68 | + // give VLC 150ms to initialize before we start playback |
| 69 | + // @@todo should be able to do this as an ready event |
| 70 | + this.waitForVlcCount = 0; |
| 71 | + setTimeout( function() { |
| 72 | + _this.postEmbedJS(); |
| 73 | + }, 150 ); |
| 74 | + }, |
| 75 | + |
| 76 | + /** |
| 77 | + * Javascript to run post vlc embedding |
| 78 | + * Inserts the requested src to the embed instance |
| 79 | + */ |
| 80 | + postEmbedJS: function() { |
| 81 | + var _this = this; |
| 82 | + // load a pointer to the vlc into the object (this.playerElement) |
| 83 | + this.getPlayerElement(); |
| 84 | + if ( this.playerElement && this.playerElement.playlist) { |
| 85 | + // manipulate the dom object to make sure vlc has the correct size: |
| 86 | + this.playerElement.style.width = this.getWidth(); |
| 87 | + this.playerElement.style.height = this.getHeight(); |
| 88 | + this.playerElement.playlist.items.clear(); |
| 89 | + |
| 90 | + // VLC likes absolute urls: |
| 91 | + var src = mw.absoluteUrl( this.getSrc() ) ; |
| 92 | + |
| 93 | + // @@todo if client supports seeking no need to send seek_offset to URI |
| 94 | + mw.log( 'vlc play::' + src ); |
| 95 | + var itemId = this.playerElement.playlist.add( src ); |
| 96 | + if ( itemId != -1 ) { |
| 97 | + // Play |
| 98 | + this.playerElement.playlist.playItem( itemId ); |
| 99 | + } else { |
| 100 | + mw.log( "error:cannot play at the moment !" ); |
| 101 | + } |
| 102 | + setTimeout( function() { |
| 103 | + _this.monitor(); |
| 104 | + }, 100 ); |
| 105 | + } else { |
| 106 | + mw.log( 'postEmbedJS: vlc not ready' ); |
| 107 | + this.waitForVlcCount++; |
| 108 | + if ( this.waitForVlcCount < 10 ) { |
| 109 | + setTimeout( function() { |
| 110 | + _this.postEmbedJS(); |
| 111 | + }, 100 ); |
| 112 | + } else { |
| 113 | + mw.log( 'vlc never ready' ); |
| 114 | + } |
| 115 | + } |
| 116 | + }, |
| 117 | + |
| 118 | + /** |
| 119 | + * Handles seek requests based on temporal media source type support |
| 120 | + * |
| 121 | + * @param {Float} percent Seek to this percent of the stream |
| 122 | + */ |
| 123 | + doSeek : function( percent ) { |
| 124 | + this.getPlayerElement(); |
| 125 | + if ( this.supportsURLTimeEncoding() ) { |
| 126 | + this.parent_doSeek( percent ); |
| 127 | + } else if ( this.playerElement ) { |
| 128 | + this.seeking = true; |
| 129 | + mw.log( "do vlc http seek to: " + percent ) |
| 130 | + if ( ( this.playerElement.input.state == 3 ) && ( this.playerElement.input.position != percent ) ) |
| 131 | + { |
| 132 | + this.playerElement.input.position = percent; |
| 133 | + this.controlBuilder.setStatus( 'seeking...' ); |
| 134 | + } |
| 135 | + } else { |
| 136 | + this.doPlayThenSeek( percent ); |
| 137 | + } |
| 138 | + this.parent_monitor(); |
| 139 | + }, |
| 140 | + |
| 141 | + /** |
| 142 | + * Issues a play request then seeks to a given time |
| 143 | + * |
| 144 | + * @param {Float} percent Seek to this percent of the stream after playing |
| 145 | + */ |
| 146 | + doPlayThenSeek:function( percent ) { |
| 147 | + mw.log( 'doPlayThenSeekHack' ); |
| 148 | + var _this = this; |
| 149 | + this.play(); |
| 150 | + var rfsCount = 0; |
| 151 | + var readyForSeek = function() { |
| 152 | + _this.getPlayerElement(); |
| 153 | + var newState = _this.playerElement.input.state; |
| 154 | + // if playing we are ready to do the |
| 155 | + if ( newState == 3 ) { |
| 156 | + _this.doSeek( percent ); |
| 157 | + } else { |
| 158 | + // try to get player for 10 seconds: |
| 159 | + if ( rfsCount < 200 ) { |
| 160 | + setTimeout( readyForSeek, 50 ); |
| 161 | + rfsCount++; |
| 162 | + } else { |
| 163 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 164 | + } |
| 165 | + } |
| 166 | + } |
| 167 | + readyForSeek(); |
| 168 | + }, |
| 169 | + |
| 170 | + /** |
| 171 | + * Updates the status time and player state |
| 172 | + */ |
| 173 | + monitor: function() { |
| 174 | + this.getPlayerElement(); |
| 175 | + if ( !this.playerElement ) |
| 176 | + return ; |
| 177 | + try{ |
| 178 | + //mw.log( 'state:' + this.playerElement.input.state); |
| 179 | + //mw.log('time: ' + this.playerElement.input.time); |
| 180 | + //mw.log('pos: ' + this.playerElement.input.position); |
| 181 | + if ( this.playerElement.log.messages.count > 0 ) { |
| 182 | + // there is one or more messages in the log |
| 183 | + var iter = this.playerElement.log.messages.iterator(); |
| 184 | + while ( iter.hasNext ) { |
| 185 | + var msg = iter.next(); |
| 186 | + var msgtype = msg.type.toString(); |
| 187 | + if ( ( msg.severity == 1 ) && ( msgtype == "input" ) ) |
| 188 | + { |
| 189 | + mw.log( msg.message ); |
| 190 | + } |
| 191 | + } |
| 192 | + // clear the log once finished to avoid clogging |
| 193 | + this.playerElement.log.messages.clear(); |
| 194 | + } |
| 195 | + |
| 196 | + var newState = this.playerElement.input.state; |
| 197 | + if ( this.prevState != newState ) { |
| 198 | + if ( newState == 0 ) |
| 199 | + { |
| 200 | + // current media has stopped |
| 201 | + this.onStop(); |
| 202 | + } |
| 203 | + else if ( newState == 1 ) |
| 204 | + { |
| 205 | + // current media is opening/connecting |
| 206 | + this.onOpen(); |
| 207 | + } |
| 208 | + else if ( newState == 2 ) |
| 209 | + { |
| 210 | + // current media is buffering data |
| 211 | + this.onBuffer(); |
| 212 | + } |
| 213 | + else if ( newState == 3 ) |
| 214 | + { |
| 215 | + // current media is now playing |
| 216 | + this.onPlay(); |
| 217 | + } |
| 218 | + else if ( this.playerElement.input.state == 4 ) { |
| 219 | + // current media is now paused |
| 220 | + this.onPause(); |
| 221 | + } |
| 222 | + this.prevState = newState; |
| 223 | + } else if ( newState == 3 ) { |
| 224 | + // current media is playing |
| 225 | + this.onPlaying(); |
| 226 | + } |
| 227 | + } catch( e ){ |
| 228 | + mw.log("EmbedPlayerVlc::Monitor error"); |
| 229 | + } |
| 230 | + // update the status and check timmer via universal parent monitor |
| 231 | + this.parent_monitor(); |
| 232 | + }, |
| 233 | + |
| 234 | + /** |
| 235 | + * Events: |
| 236 | + * NOTE : should be localized: |
| 237 | + */ |
| 238 | + onOpen: function() { |
| 239 | + this.controlBuilder.setStatus( "Opening..." ); |
| 240 | + }, |
| 241 | + onBuffer: function() { |
| 242 | + this.controlBuilder.setStatus( "Buffering..." ); |
| 243 | + }, |
| 244 | + onPlay: function() { |
| 245 | + this.onPlaying(); |
| 246 | + }, |
| 247 | + onPlaying: function() { |
| 248 | + this.seeking = false; |
| 249 | + // for now trust the duration from url over vlc input.length |
| 250 | + if ( !this.getDuration() && this.playerElement.input.length > 0 ) |
| 251 | + { |
| 252 | + // mw.log('setting duration to ' + this.playerElement.input.length /1000); |
| 253 | + this.duration = this.playerElement.input.length / 1000; |
| 254 | + } |
| 255 | + this.vlcCurrentTime = this.playerElement.input.time / 1000; |
| 256 | + }, |
| 257 | + |
| 258 | + /** |
| 259 | + * Get the embed player time |
| 260 | + */ |
| 261 | + getPlayerElementTime: function(){ |
| 262 | + return this.vlcCurrentTime; |
| 263 | + }, |
| 264 | + |
| 265 | + onPause: function() { |
| 266 | + this.parent_pause(); // update the inteface if paused via native control |
| 267 | + }, |
| 268 | + onStop: function() { |
| 269 | + mw.log( 'vlc:onStop:' ); |
| 270 | + if ( !this.seeking ) |
| 271 | + this.onClipDone(); |
| 272 | + }, |
| 273 | + |
| 274 | + /** |
| 275 | + * Handles play requests |
| 276 | + */ |
| 277 | + play : function() { |
| 278 | + mw.log( 'f:vlcPlay' ); |
| 279 | + // Update the interface |
| 280 | + this.parent_play(); |
| 281 | + if ( this.getPlayerElement() ) { |
| 282 | + // plugin is already being present send play call: |
| 283 | + // clear the message log and enable error logging |
| 284 | + if ( this.playerElement.log ) { |
| 285 | + this.playerElement.log.messages.clear(); |
| 286 | + } |
| 287 | + if ( this.playerElement.playlist && typeof this.playerElement.playlist.play == 'function') |
| 288 | + this.playerElement.playlist.play(); |
| 289 | + |
| 290 | + if( typeof this.playerElement.play == 'function' ) |
| 291 | + this.playerElement.play(); |
| 292 | + |
| 293 | + this.paused = false; |
| 294 | + |
| 295 | + // re-start the monitor: |
| 296 | + this.monitor(); |
| 297 | + } |
| 298 | + }, |
| 299 | + |
| 300 | + /** |
| 301 | + * Passes the Pause request to the plugin. |
| 302 | + * calls parent "pause" to update interface |
| 303 | + */ |
| 304 | + pause : function() { |
| 305 | + this.parent_pause(); // update the interface if paused via native control |
| 306 | + if ( this.getPlayerElement() ) { |
| 307 | + try{ |
| 308 | + this.playerElement.playlist.togglePause(); |
| 309 | + } catch( e ){ |
| 310 | + mw.log("EmbedPlayerVlc could not pause video " + e); |
| 311 | + } |
| 312 | + } |
| 313 | + }, |
| 314 | + |
| 315 | + /** |
| 316 | + * Mutes the video |
| 317 | + * calls parent "toggleMute" to update interface |
| 318 | + */ |
| 319 | + toggleMute:function() { |
| 320 | + this.parent_toggleMute(); |
| 321 | + if ( this.getPlayerElement() ) |
| 322 | + this.playerElement.audio.toggleMute(); |
| 323 | + }, |
| 324 | + |
| 325 | + /** |
| 326 | + * Update the player volume |
| 327 | + * @pram {Float} percent Percent of total volume |
| 328 | + */ |
| 329 | + setPlayerElementVolume: function ( percent ) { |
| 330 | + if ( this.getPlayerElement() ) { |
| 331 | + this.playerElement.audio.volume = percent * 100; |
| 332 | + } |
| 333 | + }, |
| 334 | + |
| 335 | + /** |
| 336 | + * Gets the current volume |
| 337 | + * @return {Float} percent percent of total volume |
| 338 | + */ |
| 339 | + getVolumen:function() { |
| 340 | + if ( this.getPlayerElement() ) |
| 341 | + return this.playerElement.audio.volume / 100; |
| 342 | + }, |
| 343 | + |
| 344 | + /** |
| 345 | + * Passes fullscreen request to plugin |
| 346 | + */ |
| 347 | + fullscreen : function() { |
| 348 | + if ( this.playerElement ) { |
| 349 | + if ( this.playerElement.video ){ |
| 350 | + try{ |
| 351 | + this.playerElement.video.toggleFullscreen(); |
| 352 | + } catch ( e ){ |
| 353 | + mw.log("VlcEmbed toggle fullscreen : possible error: " + e); |
| 354 | + } |
| 355 | + } |
| 356 | + } |
| 357 | + }, |
| 358 | + |
| 359 | + /** |
| 360 | + * Get the embed vlc object |
| 361 | + */ |
| 362 | + getPlayerElement : function() { |
| 363 | + this.playerElement = $j( '#' + this.pid ).get(0); |
| 364 | + return this.playerElement; |
| 365 | + } |
| 366 | +}; |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerVlc.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 367 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/vlcEmbed.js:r51646 |
2 | 368 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/vlcEmbed.js:r58211-58321 |
Added: svn:eol-style |
3 | 369 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerNative.js |
— | — | @@ -0,0 +1,692 @@ |
| 2 | +/** |
| 3 | +* Native embed library: |
| 4 | +* |
| 5 | +* Enables embedPlayer support for native html5 browser playback system |
| 6 | +*/ |
| 7 | +mw.EmbedPlayerNative = { |
| 8 | + |
| 9 | + //Instance Name |
| 10 | + instanceOf: 'Native', |
| 11 | + |
| 12 | + // Flag to only load the video ( not play it ) |
| 13 | + onlyLoadFlag:false, |
| 14 | + |
| 15 | + //Callback fired once video is "loaded" |
| 16 | + onLoadedCallback: null, |
| 17 | + |
| 18 | + // The previous "currentTime" to sniff seek actions |
| 19 | + // NOTE the bug where onSeeked does not seem fire consistently may no longer be applicable |
| 20 | + prevCurrentTime: -1, |
| 21 | + |
| 22 | + // Store the progress event ( updated during monitor ) |
| 23 | + progressEventData: null, |
| 24 | + |
| 25 | + // If the media loaded event has been fired |
| 26 | + mediaLoadedFlag: null, |
| 27 | + |
| 28 | + // All the native events per: |
| 29 | + // http://www.w3.org/TR/html5/video.html#mediaevents |
| 30 | + nativeEvents : [ |
| 31 | + 'loadstart', |
| 32 | + 'progress', |
| 33 | + 'suspend', |
| 34 | + 'abort', |
| 35 | + 'error', |
| 36 | + 'emptied', |
| 37 | + 'stalled', |
| 38 | + 'play', |
| 39 | + 'pause', |
| 40 | + 'loadedmetadata', |
| 41 | + 'loadeddata', |
| 42 | + 'waiting', |
| 43 | + 'playing', |
| 44 | + 'canplay', |
| 45 | + 'canplaythough', |
| 46 | + 'seeking', |
| 47 | + 'seeked', |
| 48 | + 'timeupdate', |
| 49 | + 'ended', |
| 50 | + 'ratechange', |
| 51 | + 'durationchange', |
| 52 | + 'volumechange' |
| 53 | + ], |
| 54 | + |
| 55 | + // Native player supported feature set |
| 56 | + supports: { |
| 57 | + 'playHead' : true, |
| 58 | + 'pause' : true, |
| 59 | + 'fullscreen' : true, |
| 60 | + 'timeDisplay' : true, |
| 61 | + 'volumeControl' : true, |
| 62 | + 'overlays' : true |
| 63 | + }, |
| 64 | + |
| 65 | + /** |
| 66 | + * Updates the supported features given the "type of player" |
| 67 | + */ |
| 68 | + updateFeatureSupport: function(){ |
| 69 | + // The native controls function checks for overly support |
| 70 | + // especially the special case of iPad in-dom or not support |
| 71 | + if( this.useNativePlayerControls() ) { |
| 72 | + this.supports.overlays = false; |
| 73 | + this.supports.volumeControl = false; |
| 74 | + } |
| 75 | + // iOS does not support volume control ( only iPad can have controls ) |
| 76 | + if( mw.isIpad() ){ |
| 77 | + this.supports.volumeControl = false; |
| 78 | + } |
| 79 | + this.parent_updateFeatureSupport(); |
| 80 | + }, |
| 81 | + |
| 82 | + /** |
| 83 | + * Return the embed code |
| 84 | + */ |
| 85 | + doEmbedHTML : function () { |
| 86 | + var _this = this; |
| 87 | + |
| 88 | + // Reset some play state flags: |
| 89 | + _this.bufferStartFlag = false; |
| 90 | + _this.bufferEndFlag = false; |
| 91 | + |
| 92 | + mw.log( "EmbedPlayerNative:: play url:" + this.getSrc( this.currentTime ) + ' startOffset: ' + this.start_ntp + ' end: ' + this.end_ntp ); |
| 93 | + |
| 94 | + // Check if using native controls and already the "pid" is already in the DOM |
| 95 | + if( ( this.useNativePlayerControls() |
| 96 | + || |
| 97 | + this.isPersistentNativePlayer() |
| 98 | + ) |
| 99 | + && $j( '#' + this.pid ).length |
| 100 | + && typeof $j( '#' + this.pid ).get(0).play != 'undefined' ) { |
| 101 | + |
| 102 | + // Update the player source: |
| 103 | + $j( '#' + this.pid ).attr( 'src', this.getSrc( this.currentTime ) ); |
| 104 | + $j( '#' + this.pid ).get(0).load(); |
| 105 | + |
| 106 | + _this.postEmbedJS(); |
| 107 | + return ; |
| 108 | + } |
| 109 | + |
| 110 | + $j( this ).html( |
| 111 | + _this.getNativePlayerHtml() |
| 112 | + ); |
| 113 | + |
| 114 | + // Directly run postEmbedJS ( if playerElement is not available it will retry ) |
| 115 | + _this.postEmbedJS(); |
| 116 | + }, |
| 117 | + |
| 118 | + /** |
| 119 | + * Get the native player embed code. |
| 120 | + * |
| 121 | + * @param {object} playerAttribtues Attributes to be override in function call |
| 122 | + * @return {object} cssSet css to apply to the player |
| 123 | + */ |
| 124 | + getNativePlayerHtml: function( playerAttribtues, cssSet ){ |
| 125 | + if( !playerAttribtues) { |
| 126 | + playerAttribtues = {}; |
| 127 | + } |
| 128 | + // Update required attributes |
| 129 | + if( !playerAttribtues['id'] ) playerAttribtues['id'] = this.pid; |
| 130 | + if( !playerAttribtues['src'] ) playerAttribtues['src'] = this.getSrc( this.currentTime); |
| 131 | + |
| 132 | + // If autoplay pass along to attribute ( needed for iPad / iPod no js autoplay support |
| 133 | + if( this.autoplay ) { |
| 134 | + playerAttribtues['autoplay'] = 'true'; |
| 135 | + } |
| 136 | + |
| 137 | + if( !cssSet ){ |
| 138 | + cssSet = {}; |
| 139 | + } |
| 140 | + // Set default width height to 100% of parent container |
| 141 | + if( !cssSet['width'] ) cssSet['width'] = '100%'; |
| 142 | + if( !cssSet['height'] ) cssSet['height'] = '100%'; |
| 143 | + |
| 144 | + // Also need to set the loop param directly for iPad / iPod |
| 145 | + if( this.loop ) { |
| 146 | + playerAttribtues['loop'] = 'true'; |
| 147 | + } |
| 148 | + |
| 149 | + var tagName = ( this.isAudio() ) ? 'audio' : 'video'; |
| 150 | + |
| 151 | + return $j( '<' + tagName + ' />' ) |
| 152 | + // Add the special nativeEmbedPlayer to avoid any rewrites of of this video tag. |
| 153 | + .addClass( 'nativeEmbedPlayerPid' ) |
| 154 | + .attr( playerAttribtues ) |
| 155 | + .css( cssSet ) |
| 156 | + }, |
| 157 | + |
| 158 | + /** |
| 159 | + * Post element javascript, binds event listeners and starts monitor |
| 160 | + */ |
| 161 | + postEmbedJS: function() { |
| 162 | + var _this = this; |
| 163 | + mw.log( "f:native:postEmbedJS:" ); |
| 164 | + |
| 165 | + // Setup local pointer: |
| 166 | + var vid = this.getPlayerElement(); |
| 167 | + // Apply media element bindings: |
| 168 | + this.applyMediaElementBindings(); |
| 169 | + |
| 170 | + // Check for load flag |
| 171 | + if ( this.onlyLoadFlag ) { |
| 172 | + vid.pause(); |
| 173 | + vid.load(); |
| 174 | + } else { |
| 175 | + // Issue play request |
| 176 | + vid.play(); |
| 177 | + } |
| 178 | + |
| 179 | + setTimeout( function() { |
| 180 | + _this.monitor(); |
| 181 | + }, 100 ); |
| 182 | + }, |
| 183 | + |
| 184 | + /** |
| 185 | + * Apply media element bindings |
| 186 | + */ |
| 187 | + applyMediaElementBindings: function(){ |
| 188 | + var _this = this; |
| 189 | + var vid = this.getPlayerElement(); |
| 190 | + if( ! vid ){ |
| 191 | + mw.log( " Error: applyMediaElementBindings without player elemnet"); |
| 192 | + return ; |
| 193 | + } |
| 194 | + $j.each( _this.nativeEvents, function( inx, eventName ){ |
| 195 | + $j( vid ).bind( eventName , function(){ |
| 196 | + if( _this._propagateEvents ){ |
| 197 | + var argArray = $j.makeArray( arguments ); |
| 198 | + // Check if there is local handler: |
| 199 | + if( _this['on' + eventName ] ){ |
| 200 | + _this['on' + eventName ].apply( _this, argArray); |
| 201 | + } else { |
| 202 | + // No local handler directly propagate the event to the abstract object: |
| 203 | + $j( _this ).trigger( eventName, argArray ); |
| 204 | + } |
| 205 | + } |
| 206 | + }) |
| 207 | + }); |
| 208 | + }, |
| 209 | + |
| 210 | + // basic monitor function to update buffer |
| 211 | + monitor: function(){ |
| 212 | + var _this = this; |
| 213 | + var vid = _this.getPlayerElement(); |
| 214 | + |
| 215 | + // Update duration |
| 216 | + if( vid && vid.duration ){ |
| 217 | + this.duration = vid.duration; |
| 218 | + } |
| 219 | + // Update the bufferedPercent |
| 220 | + if( vid && vid.buffered && vid.buffered.end && vid.duration ) { |
| 221 | + this.bufferedPercent = ( vid.buffered.end(0) / vid.duration ); |
| 222 | + } |
| 223 | + _this.parent_monitor(); |
| 224 | + }, |
| 225 | + |
| 226 | + |
| 227 | + /** |
| 228 | + * Issue a seeking request. |
| 229 | + * |
| 230 | + * @param {Float} percentage |
| 231 | + */ |
| 232 | + doSeek: function( percentage ) { |
| 233 | + mw.log( 'Native::doSeek p: ' + percentage + ' : ' + this.supportsURLTimeEncoding() + ' dur: ' + this.getDuration() + ' sts:' + this.seek_time_sec ); |
| 234 | + this.seeking = true; |
| 235 | + |
| 236 | + // Run the onSeeking interface update |
| 237 | + this.controlBuilder.onSeek(); |
| 238 | + |
| 239 | + // @@todo check if the clip is loaded here (if so we can do a local seek) |
| 240 | + if ( this.supportsURLTimeEncoding() ) { |
| 241 | + // Make sure we could not do a local seek instead: |
| 242 | + if ( percentage < this.bufferedPercent && this.playerElement.duration && !this.didSeekJump ) { |
| 243 | + mw.log( "do local seek " + percentage + ' is already buffered < ' + this.bufferedPercent ); |
| 244 | + this.doNativeSeek( percentage ); |
| 245 | + } else { |
| 246 | + // We support URLTimeEncoding call parent seek: |
| 247 | + this.parent_doSeek( percentage ); |
| 248 | + } |
| 249 | + } else if ( this.playerElement && this.playerElement.duration ) { |
| 250 | + // (could also check bufferedPercent > percentage seek (and issue oggz_chop request or not) |
| 251 | + this.doNativeSeek( percentage ); |
| 252 | + } else { |
| 253 | + // try to do a play then seek: |
| 254 | + this.doPlayThenSeek( percentage ) |
| 255 | + } |
| 256 | + }, |
| 257 | + |
| 258 | + /** |
| 259 | + * Do a native seek by updating the currentTime |
| 260 | + * @param {float} percentage |
| 261 | + * Percent to seek to of full time |
| 262 | + */ |
| 263 | + doNativeSeek: function( percentage ) { |
| 264 | + var _this = this; |
| 265 | + mw.log( 'native::doNativeSeek::' + percentage ); |
| 266 | + this.seeking = true; |
| 267 | + this.seek_time_sec = 0; |
| 268 | + this.setCurrentTime( ( percentage * this.duration ) , function(){ |
| 269 | + _this.seeking = false; |
| 270 | + _this.monitor(); |
| 271 | + }) |
| 272 | + }, |
| 273 | + |
| 274 | + /** |
| 275 | + * Seek in a existing stream |
| 276 | + * |
| 277 | + * @param {Float} percentage |
| 278 | + * Percentage of the stream to seek to between 0 and 1 |
| 279 | + */ |
| 280 | + doPlayThenSeek: function( percentage ) { |
| 281 | + mw.log( 'native::doPlayThenSeek::' ); |
| 282 | + var _this = this; |
| 283 | + this.play(); |
| 284 | + var retryCount = 0; |
| 285 | + var readyForSeek = function() { |
| 286 | + _this.getPlayerElement(); |
| 287 | + // If we have duration then we are ready to do the seek |
| 288 | + if ( _this.playerElement && _this.playerElement.duration ) { |
| 289 | + _this.doNativeSeek( percentage ); |
| 290 | + } else { |
| 291 | + // Try to get player for 40 seconds: |
| 292 | + // (it would be nice if the onmetadata type callbacks where fired consistently) |
| 293 | + if ( retryCount < 800 ) { |
| 294 | + setTimeout( readyForSeek, 50 ); |
| 295 | + retryCount++; |
| 296 | + } else { |
| 297 | + mw.log( 'error:doPlayThenSeek failed' ); |
| 298 | + } |
| 299 | + } |
| 300 | + } |
| 301 | + readyForSeek(); |
| 302 | + }, |
| 303 | + |
| 304 | + /** |
| 305 | + * Set the current time with a callback |
| 306 | + * |
| 307 | + * @param {Float} position |
| 308 | + * Seconds to set the time to |
| 309 | + * @param {Function} callback |
| 310 | + * Function called once time has been set. |
| 311 | + */ |
| 312 | + setCurrentTime: function( time , callback, callbackCount ) { |
| 313 | + var _this = this; |
| 314 | + if( !callbackCount ) |
| 315 | + callbackCount = 0; |
| 316 | + this.getPlayerElement(); |
| 317 | + if( _this.playerElement.readyState >= 1 ){ |
| 318 | + if( _this.playerElement.currentTime == time ){ |
| 319 | + callback(); |
| 320 | + return; |
| 321 | + } |
| 322 | + var once = function( event ) { |
| 323 | + if( callback ){ |
| 324 | + callback(); |
| 325 | + } |
| 326 | + _this.playerElement.removeEventListener( 'seeked', once, false ); |
| 327 | + }; |
| 328 | + // Assume we will get to add the Listener before the seek is done |
| 329 | + _this.playerElement.addEventListener( 'seeked', once, false ); |
| 330 | + try { |
| 331 | + _this.playerElement.currentTime = time; |
| 332 | + } catch (e) { |
| 333 | + mw.log("Could not seek to this point. Unbuffered point."); |
| 334 | + callback(); |
| 335 | + return; |
| 336 | + } |
| 337 | + } else { |
| 338 | + if( callbackCount >= 300 ){ |
| 339 | + mw.log("Error with seek request, media never in ready state"); |
| 340 | + return ; |
| 341 | + } |
| 342 | + setTimeout( function(){ |
| 343 | + _this.setCurrentTime( time, callback , callbackCount++); |
| 344 | + }, 10 ); |
| 345 | + } |
| 346 | + }, |
| 347 | + |
| 348 | + /** |
| 349 | + * Get the embed player time |
| 350 | + */ |
| 351 | + getPlayerElementTime: function() { |
| 352 | + var _this = this; |
| 353 | + // Make sure we have .vid obj |
| 354 | + this.getPlayerElement(); |
| 355 | + |
| 356 | + if ( !this.playerElement ) { |
| 357 | + mw.log( 'mwEmbedPlayer::getPlayerElementTime: ' + this.id + ' not in dom ( stop monitor)' ); |
| 358 | + return false; |
| 359 | + } |
| 360 | + // Return the playerElement currentTime |
| 361 | + return this.playerElement.currentTime; |
| 362 | + }, |
| 363 | + |
| 364 | + // Update the poster src ( updates the native object if in dom ) |
| 365 | + updatePosterSrc: function( src ){ |
| 366 | + if( this.getPlayerElement() ){ |
| 367 | + this.getPlayerElement().poster = src; |
| 368 | + } |
| 369 | + // Also update the embedPlayer poster |
| 370 | + this.parent_updatePosterSrc( src ); |
| 371 | + }, |
| 372 | + |
| 373 | + /** |
| 374 | + * switchPlaySrc switches the player source working around a few bugs in browsers |
| 375 | + * |
| 376 | + * @param {string} |
| 377 | + * src Video url Source to switch to. |
| 378 | + * @param {function} |
| 379 | + * switchCallback Function to call once the source has been switched |
| 380 | + * @param {function} |
| 381 | + * doneCallback Function to call once the clip has completed playback |
| 382 | + */ |
| 383 | + switchPlaySrc: function( src, switchCallback, doneCallback ){ |
| 384 | + var _this = this; |
| 385 | + mw.log( 'EmbedPlayerNative:: switchPlaySrc:' + src + ' native time: ' + this.getPlayerElement().currentTime ); |
| 386 | + // Update some parent embedPlayer vars: |
| 387 | + this.duration = 0; |
| 388 | + this.currentTime = 0; |
| 389 | + this.previousTime = 0; |
| 390 | + var vid = this.getPlayerElement(); |
| 391 | + if ( vid ) { |
| 392 | + try { |
| 393 | + // issue a play request on the source |
| 394 | + vid.play(); |
| 395 | + setTimeout(function(){ |
| 396 | + // Remove all native player bindings |
| 397 | + $j(vid).unbind(); |
| 398 | + vid.pause(); |
| 399 | + var orginalControlsState = vid.controls; |
| 400 | + // Hide controls ( to not display native play button while switching sources ) |
| 401 | + vid.removeAttribute('controls'); |
| 402 | + |
| 403 | + // Local scope update source and play function to work around google chrome bug |
| 404 | + var updateSrcAndPlay = function() { |
| 405 | + var vid = _this.getPlayerElement(); |
| 406 | + if (!vid){ |
| 407 | + mw.log( 'Error: switchPlaySrc no vid'); |
| 408 | + return ; |
| 409 | + } |
| 410 | + vid.src = src; |
| 411 | + // Give iOS 50ms to figure out the src got updated ( iPad OS 4.0 ) |
| 412 | + setTimeout(function() { |
| 413 | + var vid = _this.getPlayerElement(); |
| 414 | + if (!vid){ |
| 415 | + mw.log( 'Error: switchPlaySrc no vid'); |
| 416 | + return ; |
| 417 | + } |
| 418 | + vid.load(); |
| 419 | + vid.play(); |
| 420 | + // Wait another 100ms then bind the end event and any custom events |
| 421 | + // for the switchCallback |
| 422 | + setTimeout(function() { |
| 423 | + var vid = _this.getPlayerElement(); |
| 424 | + // restore controls |
| 425 | + vid.controls = orginalControlsState; |
| 426 | + // add the end binding: |
| 427 | + $j(vid).bind('ended', function( event ) { |
| 428 | + if(typeof doneCallback == 'function' ){ |
| 429 | + doneCallback(); |
| 430 | + } |
| 431 | + return false; |
| 432 | + }); |
| 433 | + if (typeof switchCallback == 'function') { |
| 434 | + switchCallback(vid); |
| 435 | + } |
| 436 | + }, 100); |
| 437 | + }, 100); |
| 438 | + }; |
| 439 | + if (navigator.userAgent.toLowerCase().indexOf('chrome') != -1) { |
| 440 | + // Null the src and wait 50ms ( helps unload video without crashing |
| 441 | + // google chrome 7.x ) |
| 442 | + vid.src = ''; |
| 443 | + setTimeout(updateSrcAndPlay, 100); |
| 444 | + } else { |
| 445 | + updateSrcAndPlay(); |
| 446 | + } |
| 447 | + }, 100 ); |
| 448 | + } catch (e) { |
| 449 | + mw.log("Error: Error in swiching source playback"); |
| 450 | + } |
| 451 | + } |
| 452 | + }, |
| 453 | + |
| 454 | + /** |
| 455 | + * Pause the video playback |
| 456 | + * calls parent_pause to update the interface |
| 457 | + */ |
| 458 | + pause: function( ) { |
| 459 | + this.getPlayerElement(); |
| 460 | + this.parent_pause(); // update interface |
| 461 | + if ( this.playerElement ) { // update player |
| 462 | + if( !this.playerElement.paused ){ |
| 463 | + this.playerElement.pause(); |
| 464 | + } |
| 465 | + } |
| 466 | + }, |
| 467 | + |
| 468 | + /** |
| 469 | + * Play back the video stream |
| 470 | + * calls parent_play to update the interface |
| 471 | + */ |
| 472 | + play: function( ) { |
| 473 | + this.getPlayerElement(); |
| 474 | + this.parent_play(); // update interface |
| 475 | + if ( this.playerElement && this.playerElement.play ) { |
| 476 | + // issue a play request if the media is paused: |
| 477 | + if( this.playerElement.paused ){ |
| 478 | + this.playerElement.play(); |
| 479 | + } |
| 480 | + // re-start the monitor: |
| 481 | + this.monitor(); |
| 482 | + } |
| 483 | + }, |
| 484 | + /** |
| 485 | + * Stop the player ( end all listeners ) |
| 486 | + */ |
| 487 | + stop:function(){ |
| 488 | + if( this.playerElement ){ |
| 489 | + $j( this.playerElement ).unbind(); |
| 490 | + } |
| 491 | + this.parent_stop(); |
| 492 | + }, |
| 493 | + |
| 494 | + /** |
| 495 | + * Toggle the Mute |
| 496 | + * calls parent_toggleMute to update the interface |
| 497 | + */ |
| 498 | + toggleMute: function() { |
| 499 | + this.parent_toggleMute(); |
| 500 | + this.getPlayerElement(); |
| 501 | + if ( this.playerElement ) |
| 502 | + this.playerElement.muted = this.muted; |
| 503 | + }, |
| 504 | + |
| 505 | + /** |
| 506 | + * Update Volume |
| 507 | + * |
| 508 | + * @param {Float} percentage Value between 0 and 1 to set audio volume |
| 509 | + */ |
| 510 | + setPlayerElementVolume : function( percentage ) { |
| 511 | + if ( this.getPlayerElement() ) { |
| 512 | + // Disable mute if positive volume |
| 513 | + if( percentage != 0 ) { |
| 514 | + this.playerElement.muted = false; |
| 515 | + } |
| 516 | + this.playerElement.volume = percentage; |
| 517 | + } |
| 518 | + }, |
| 519 | + |
| 520 | + /** |
| 521 | + * get Volume |
| 522 | + * |
| 523 | + * @return {Float} |
| 524 | + * Audio volume between 0 and 1. |
| 525 | + */ |
| 526 | + getPlayerElementVolume: function() { |
| 527 | + if ( this.getPlayerElement() ) { |
| 528 | + return this.playerElement.volume; |
| 529 | + } |
| 530 | + }, |
| 531 | + /** |
| 532 | + * get the native muted state |
| 533 | + */ |
| 534 | + getPlayerElementMuted: function(){ |
| 535 | + if ( this.getPlayerElement() ) { |
| 536 | + return this.playerElement.muted; |
| 537 | + } |
| 538 | + }, |
| 539 | + |
| 540 | + /** |
| 541 | + * Get the native media duration |
| 542 | + */ |
| 543 | + getNativeDuration: function() { |
| 544 | + if ( this.playerElement ) { |
| 545 | + return this.playerElement.duration; |
| 546 | + } |
| 547 | + }, |
| 548 | + |
| 549 | + /** |
| 550 | + * load the video stream with a callback fired once the video is "loaded" |
| 551 | + * |
| 552 | + * @parma {Function} callbcak Function called once video is loaded |
| 553 | + */ |
| 554 | + load: function( callback ) { |
| 555 | + this.getPlayerElement(); |
| 556 | + if ( !this.playerElement ) { |
| 557 | + // No vid loaded |
| 558 | + mw.log( 'native::load() ... doEmbed' ); |
| 559 | + this.onlyLoadFlag = true; |
| 560 | + this.doEmbedHTML(); |
| 561 | + this.onLoadedCallback = callback; |
| 562 | + } else { |
| 563 | + // Should not happen offten |
| 564 | + this.playerElement.load(); |
| 565 | + if( callback) |
| 566 | + callback(); |
| 567 | + } |
| 568 | + }, |
| 569 | + |
| 570 | + /** |
| 571 | + * Get /update the playerElement value |
| 572 | + */ |
| 573 | + getPlayerElement: function () { |
| 574 | + this.playerElement = $j( '#' + this.pid ).get( 0 ); |
| 575 | + return this.playerElement; |
| 576 | + }, |
| 577 | + |
| 578 | + /** |
| 579 | + * Bindings for the Video Element Events |
| 580 | + */ |
| 581 | + |
| 582 | + /** |
| 583 | + * Local method for seeking event |
| 584 | + * fired when "seeking" |
| 585 | + */ |
| 586 | + onseeking: function() { |
| 587 | + mw.log( "native:onSeeking"); |
| 588 | + // Trigger the html5 seeking event |
| 589 | + //( if not already set from interface ) |
| 590 | + if( !this.seeking ) { |
| 591 | + this.seeking = true; |
| 592 | + |
| 593 | + // Run the onSeeking interface update |
| 594 | + this.controlBuilder.onSeek(); |
| 595 | + |
| 596 | + // Trigger the html5 "seeking" trigger |
| 597 | + mw.log("native:seeking:trigger:: " + this.seeking); |
| 598 | + $j( this ).trigger( 'seeking' ); |
| 599 | + } |
| 600 | + }, |
| 601 | + |
| 602 | + /** |
| 603 | + * Local method for seeked event |
| 604 | + * fired when done seeking |
| 605 | + */ |
| 606 | + onseeked: function() { |
| 607 | + mw.log("native:onSeeked"); |
| 608 | + // Trigger the html5 action on the parent |
| 609 | + if( this.seeking ){ |
| 610 | + this.seeking = false; |
| 611 | + $j( this ).trigger( 'seeked' ); |
| 612 | + } |
| 613 | + this.seeking = false; |
| 614 | + }, |
| 615 | + |
| 616 | + /** |
| 617 | + * Handle the native paused event |
| 618 | + */ |
| 619 | + onpause: function(){ |
| 620 | + mw.log( "EmbedPlayer:native: OnPaused:: " + this._propagateEvents ); |
| 621 | + if( this._propagateEvents ){ |
| 622 | + this.parent_pause(); |
| 623 | + } |
| 624 | + }, |
| 625 | + |
| 626 | + /** |
| 627 | + * Handle the native play event |
| 628 | + */ |
| 629 | + onplay: function(){ |
| 630 | + mw.log("EmbedPlayer:native:: OnPlay::" + this._propagateEvents ); |
| 631 | + // Update the interface ( if paused ) |
| 632 | + if( this._propagateEvents ){ |
| 633 | + this.parent_play(); |
| 634 | + } |
| 635 | + }, |
| 636 | + |
| 637 | + /** |
| 638 | + * Local method for metadata ready |
| 639 | + * fired when metadata becomes available |
| 640 | + * |
| 641 | + * Used to update the media duration to |
| 642 | + * accurately reflect the src duration |
| 643 | + */ |
| 644 | + onloadedmetadata: function() { |
| 645 | + this.getPlayerElement(); |
| 646 | + if ( this.playerElement && ! isNaN( this.playerElement.duration ) ) { |
| 647 | + mw.log( 'f:onloadedmetadata metadata ready Update duration:' + this.playerElement.duration + ' old dur: ' + this.getDuration() ); |
| 648 | + this.duration = this.playerElement.duration; |
| 649 | + } |
| 650 | + |
| 651 | + //Fire "onLoaded" flags if set |
| 652 | + if( typeof this.onLoadedCallback == 'function' ) { |
| 653 | + this.onLoadedCallback(); |
| 654 | + } |
| 655 | + |
| 656 | + // Trigger "media loaded" |
| 657 | + if( ! this.mediaLoadedFlag ){ |
| 658 | + $j( this ).trigger( 'mediaLoaded' ); |
| 659 | + this.mediaLoadedFlag = true; |
| 660 | + } |
| 661 | + }, |
| 662 | + |
| 663 | + /** |
| 664 | + * Local method for progress event |
| 665 | + * fired as the video is downloaded / buffered |
| 666 | + * |
| 667 | + * Used to update the bufferedPercent |
| 668 | + * |
| 669 | + * Note: this way of updating buffer was only supported in Firefox 3.x and |
| 670 | + * not supported in Firefox 4.x |
| 671 | + */ |
| 672 | + onprogress: function( event ) { |
| 673 | + var e = event.originalEvent; |
| 674 | + if( e && e.loaded && e.total ) { |
| 675 | + this.bufferedPercent = e.loaded / e.total; |
| 676 | + this.progressEventData = e.loaded; |
| 677 | + } |
| 678 | + }, |
| 679 | + |
| 680 | + /** |
| 681 | + * Local method for progress event |
| 682 | + * fired as the video is downloaded / buffered |
| 683 | + * |
| 684 | + * Used to update the bufferedPercent |
| 685 | + */ |
| 686 | + onended: function() { |
| 687 | + var _this = this; |
| 688 | + mw.log( 'EmbedPlayer:native: onended:' + this.playerElement.currentTime + ' real dur:' + this.getDuration() + ' ended ' + this._propagateEvents ); |
| 689 | + if( this._propagateEvents ){ |
| 690 | + this.onClipDone(); |
| 691 | + } |
| 692 | + } |
| 693 | +}; |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerNative.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 694 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/nativeEmbed.js:r51646 |
2 | 695 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/nativeEmbed.js:r58211-58321 |
Added: svn:eol-style |
3 | 696 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerKplayer.js |
— | — | @@ -0,0 +1,1249 @@ |
| 2 | +/* |
| 3 | + * The "kaltura player" embedPlayer interface for fallback h.264 and flv video format support |
| 4 | + */ |
| 5 | + |
| 6 | +// Called from the kdp.swf |
| 7 | +function jsInterfaceReadyFunc() { |
| 8 | + return true; |
| 9 | +} |
| 10 | + |
| 11 | +mw.EmbedPlayerKplayer = { |
| 12 | + |
| 13 | + // Instance name: |
| 14 | + instanceOf : 'Kplayer', |
| 15 | + |
| 16 | + // List of supported features: |
| 17 | + supports : { |
| 18 | + 'playHead' : true, |
| 19 | + 'pause' : true, |
| 20 | + 'stop' : true, |
| 21 | + 'timeDisplay' : true, |
| 22 | + 'volumeControl' : true, |
| 23 | + 'overlays' : true, |
| 24 | + 'fullscreen' : true |
| 25 | + }, |
| 26 | + |
| 27 | + // Stores the current time as set from flash |
| 28 | + flashCurrentTime : 0, |
| 29 | + |
| 30 | + /* |
| 31 | + * Write the Embed html to the target |
| 32 | + */ |
| 33 | + doEmbedHTML : function() { |
| 34 | + var _this = this; |
| 35 | + |
| 36 | + mw.log("kPlayer:: embed src::" + _this.getSrc()); |
| 37 | + var flashvars = {}; |
| 38 | + flashvars.autoPlay = "true"; |
| 39 | + var playerPath = mw.getMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/kaltura-player'; |
| 40 | + flashvars.entryId = mw.absoluteUrl(_this.getSrc()); |
| 41 | + |
| 42 | + // Use a relative url if the protocal is file:// |
| 43 | + if (mw.parseUri(document.URL).protocol == 'file') { |
| 44 | + playerPath = mw.getRelativeMwEmbedPath() + 'modules/EmbedPlayer/binPlayers/kaltura-player'; |
| 45 | + flashvars.entryId = _this.getSrc(); |
| 46 | + } |
| 47 | + |
| 48 | + flashvars.debugMode = "true"; |
| 49 | + flashvars.fileSystemMode = "true"; |
| 50 | + flashvars.widgetId = "_7463"; |
| 51 | + flashvars.partnerId = "7463"; |
| 52 | + flashvars.pluginDomain = "kdp3/plugins/"; |
| 53 | + flashvars.kml = "local"; |
| 54 | + flashvars.kmlPath = playerPath + '/config.xml'; |
| 55 | + flashvars.sourceType = "url"; |
| 56 | + |
| 57 | + // flashvars.host = "www.kaltura.com"; |
| 58 | + flashvars.externalInterfaceDisabled = 'false'; |
| 59 | + flashvars.skinPath = playerPath + '/skin.swf'; |
| 60 | + |
| 61 | + flashvars["full.skinPath"] = playerPath + '/LightDoodleskin.swf'; |
| 62 | + |
| 63 | + var params = {}; |
| 64 | + params.quality = "best"; |
| 65 | + params.wmode = "opaque"; |
| 66 | + params.allowfullscreen = "true"; |
| 67 | + params.allowscriptaccess = "always"; |
| 68 | + |
| 69 | + var attributes = {}; |
| 70 | + attributes.id = this.pid; |
| 71 | + attributes.name = this.pid; |
| 72 | + |
| 73 | + mw.log(" about to add the pid container"); |
| 74 | + $j(this).html($j('<div />').attr('id', this.pid + '_container')); |
| 75 | + // Call swm dom loaded function: |
| 76 | + swfobject.callDomLoadFunctions(); |
| 77 | + // Do the flash embedding with embedSWF |
| 78 | + swfobject.embedSWF(playerPath + "/kdp3.swf", this.pid + '_container', |
| 79 | + '100%', '100%', "10.0.0", playerPath + "/expressInstall.swf", |
| 80 | + flashvars, params, attributes); |
| 81 | + |
| 82 | + // Direct object embed |
| 83 | + /* |
| 84 | + * $j( this ).html( '<object |
| 85 | + * classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="780" |
| 86 | + * height="420">'+ '<param name="movie" value="myContent.swf" />'+ '<!--[if |
| 87 | + * !IE]>-->'+ '<object type="application/x-shockwave-flash" |
| 88 | + * data="myContent.swf" width="780" height="420">'+ '<!--<![endif]-->'+ '<p> |
| 89 | + * error with flash embed</p>' '<!--[if !IE]>-->'+ '</object>'+ '<!--<![endif]-->'+ '</object>' ) |
| 90 | + */ |
| 91 | + |
| 92 | + setTimeout(function() { |
| 93 | + _this.postEmbedJS(); |
| 94 | + }, 100); |
| 95 | + |
| 96 | + // Flash player loses its bindings once it changes sizes:: |
| 97 | + $j(_this).bind('onOpenFullScreen', function() { |
| 98 | + _this.postEmbedJS(); |
| 99 | + }); |
| 100 | + $j(_this).bind('onCloseFullScreen', function() { |
| 101 | + _this.postEmbedJS(); |
| 102 | + }); |
| 103 | + }, |
| 104 | + |
| 105 | + /** |
| 106 | + * javascript run post player embedding |
| 107 | + */ |
| 108 | + postEmbedJS : function() { |
| 109 | + var _this = this; |
| 110 | + this.getPlayerElement(); |
| 111 | + |
| 112 | + var bindEventMap = { |
| 113 | + 'doPause' : 'onPause', |
| 114 | + 'doPlay' : 'onPlay', |
| 115 | + 'durationChange' : 'onDurationChange', |
| 116 | + 'playerPlayEnd' : 'onClipDone', |
| 117 | + 'playerUpdatePlayhead' : 'onUpdatePlayhead', |
| 118 | + 'bytesTotalChange' : 'onBytesTotalChange', |
| 119 | + 'bytesDownloadedChange' : 'onBytesDownloadedChange' |
| 120 | + } |
| 121 | + |
| 122 | + if (this.playerElement && this.playerElement.addJsListener) { |
| 123 | + $j.each( bindEventMap, function( bindName, localMethod ) { |
| 124 | + _this.bindPlayerFunction(bindName, localMethod); |
| 125 | + } ); |
| 126 | + // Start the monitor |
| 127 | + this.monitor(); |
| 128 | + } else { |
| 129 | + // Keep trying to get the player element |
| 130 | + // mw.log('insert media: not defined:' + typeof |
| 131 | + // this.playerElement.insertMedia ); |
| 132 | + setTimeout(function() { |
| 133 | + _this.postEmbedJS(); |
| 134 | + }, 250); |
| 135 | + } |
| 136 | + }, |
| 137 | + |
| 138 | + /** |
| 139 | + * Bind a Player Function, |
| 140 | + * |
| 141 | + * Does some tricker to bind to "this" player instance: |
| 142 | + * |
| 143 | + * @param {String} |
| 144 | + * flash binding name |
| 145 | + * @param {String} |
| 146 | + * function callback name |
| 147 | + */ |
| 148 | + bindPlayerFunction : function(bindName, methodName) { |
| 149 | + // The kaltura kdp can only call a global function by given name |
| 150 | + var gKdpCallbackName = methodName + '_cb_' + this.id; |
| 151 | + |
| 152 | + // Create an anonymous function with local player scope |
| 153 | + var createGlobalCB = function(cName, embedPlayer) { |
| 154 | + window[ cName ] = function(data) { |
| 155 | + if ( embedPlayer._propagateEvents ) { |
| 156 | + embedPlayer[methodName](data); |
| 157 | + } |
| 158 | + }; |
| 159 | + }(gKdpCallbackName, this); |
| 160 | + |
| 161 | + // Add the listener to the KDP flash player: |
| 162 | + this.playerElement.addJsListener(bindName, gKdpCallbackName); |
| 163 | + }, |
| 164 | + |
| 165 | + /** |
| 166 | + * on Pause callback from the kaltura flash player calls parent_pause to |
| 167 | + * update the interface |
| 168 | + */ |
| 169 | + onPause : function() { |
| 170 | + this.parent_pause(); |
| 171 | + }, |
| 172 | + |
| 173 | + /** |
| 174 | + * onPlay function callback from the kaltura flash player directly call the |
| 175 | + * parent_play |
| 176 | + */ |
| 177 | + onPlay : function() { |
| 178 | + this.parent_play(); |
| 179 | + }, |
| 180 | + |
| 181 | + onDurationChange : function(data, id) { |
| 182 | + mw.log(" onDurationChange: " + data.newValue); |
| 183 | + // update the duration: |
| 184 | + this.duration = data.newValue; |
| 185 | + }, |
| 186 | + |
| 187 | + /** |
| 188 | + * play method calls parent_play to update the interface |
| 189 | + */ |
| 190 | + play : function() { |
| 191 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 192 | + this.playerElement.sendNotification('doPlay'); |
| 193 | + } |
| 194 | + this.parent_play(); |
| 195 | + }, |
| 196 | + |
| 197 | + /** |
| 198 | + * pause method calls parent_pause to update the interface |
| 199 | + */ |
| 200 | + pause : function() { |
| 201 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 202 | + this.playerElement.sendNotification('doPause'); |
| 203 | + } |
| 204 | + this.parent_pause(); |
| 205 | + }, |
| 206 | + |
| 207 | + /** |
| 208 | + * Issues a seek to the playerElement |
| 209 | + * |
| 210 | + * @param {Float} |
| 211 | + * percentage Percentage of total stream length to seek to |
| 212 | + */ |
| 213 | + doSeek : function(percentage) { |
| 214 | + var _this = this; |
| 215 | + var seekTime = percentage * this.getDuration(); |
| 216 | + mw.log( 'EmbedPlayerKalturaKplayer:: doSeek: ' + percentage + ' time:' + seekTime ); |
| 217 | + if (this.supportsURLTimeEncoding()) { |
| 218 | + |
| 219 | + // Make sure we could not do a local seek instead: |
| 220 | + if (!(percentage < this.bufferedPercent |
| 221 | + && this.playerElement.duration && !this.didSeekJump)) { |
| 222 | + // We support URLTimeEncoding call parent seek: |
| 223 | + this.parent_doSeek( percentage ); |
| 224 | + return; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + if (this.playerElement) { |
| 229 | + // Issue the seek to the flash player: |
| 230 | + this.playerElement.sendNotification('doSeek', seekTime); |
| 231 | + |
| 232 | + // Kdp is missing seek done callback |
| 233 | + setTimeout(function() { |
| 234 | + _this.seeking = false; |
| 235 | + }, 500); |
| 236 | + } else { |
| 237 | + // try to do a play then seek: |
| 238 | + this.doPlayThenSeek(percentage) |
| 239 | + } |
| 240 | + // Run the onSeeking interface update |
| 241 | + this.controlBuilder.onSeek(); |
| 242 | + }, |
| 243 | + |
| 244 | + /** |
| 245 | + * Seek in a existing stream |
| 246 | + * |
| 247 | + * @param {Float} |
| 248 | + * percentage Percentage of the stream to seek to between 0 and 1 |
| 249 | + */ |
| 250 | + doPlayThenSeek : function(percentage) { |
| 251 | + mw.log('flash::doPlayThenSeek::'); |
| 252 | + var _this = this; |
| 253 | + // issue the play request |
| 254 | + this.play(); |
| 255 | + |
| 256 | + // let the player know we are seeking |
| 257 | + _this.seeking = true; |
| 258 | + |
| 259 | + var getPlayerCount = 0; |
| 260 | + var readyForSeek = function() { |
| 261 | + _this.getPlayerElement(); |
| 262 | + // if we have duration then we are ready to do the seek ( flash can't |
| 263 | + // seek untill there is some buffer ) |
| 264 | + if (_this.playerElement && _this.playerElement.sendNotification |
| 265 | + && _this.getDuration() && _this.bufferedPercent) { |
| 266 | + var seekTime = percentage * _this.getDuration(); |
| 267 | + // Issue the seek to the flash player: |
| 268 | + _this.playerElement.sendNotification('doSeek', seekTime); |
| 269 | + } else { |
| 270 | + // Try to get player for 20 seconds: |
| 271 | + if (getPlayerCount < 400) { |
| 272 | + setTimeout(readyForSeek, 50); |
| 273 | + getPlayerCount++; |
| 274 | + } else { |
| 275 | + mw.log('Error:doPlayThenSeek failed'); |
| 276 | + } |
| 277 | + } |
| 278 | + } |
| 279 | + readyForSeek(); |
| 280 | +}, |
| 281 | + |
| 282 | +/** |
| 283 | + * Issues a volume update to the playerElement |
| 284 | + * |
| 285 | + * @param {Float} |
| 286 | + * percentage Percentage to update volume to |
| 287 | + */ |
| 288 | +setPlayerElementVolume : function(percentage) { |
| 289 | + if (this.playerElement && this.playerElement.sendNotification) { |
| 290 | + this.playerElement.sendNotification('changeVolume', percentage); |
| 291 | + } |
| 292 | +}, |
| 293 | + |
| 294 | +/** |
| 295 | + * function called by flash at set interval to update the playhead. |
| 296 | + */ |
| 297 | +onUpdatePlayhead : function(playheadValue) { |
| 298 | + this.flashCurrentTime = playheadValue; |
| 299 | +}, |
| 300 | + |
| 301 | +/** |
| 302 | + * function called by flash when the total media size changes |
| 303 | + */ |
| 304 | +onBytesTotalChange : function(data, id) { |
| 305 | + this.bytesTotal = data.newValue; |
| 306 | +}, |
| 307 | + |
| 308 | +/** |
| 309 | + * function called by flash applet when download bytes changes |
| 310 | + */ |
| 311 | +onBytesDownloadedChange : function(data, id) { |
| 312 | + mw.log('onBytesDownloadedChange'); |
| 313 | + this.bytesLoaded = data.newValue; |
| 314 | + this.bufferedPercent = this.bytesLoaded / this.bytesTotal; |
| 315 | + |
| 316 | + // Fire the parent html5 action |
| 317 | + $j(this).trigger('progress', { |
| 318 | + 'loaded' : this.bytesLoaded, |
| 319 | + 'total' : this.bytesTotal |
| 320 | + }); |
| 321 | +}, |
| 322 | + |
| 323 | +/** |
| 324 | + * Get the embed player time |
| 325 | + */ |
| 326 | +getPlayerElementTime : function() { |
| 327 | + // update currentTime |
| 328 | + return this.flashCurrentTime; |
| 329 | +}, |
| 330 | + |
| 331 | +/** |
| 332 | + * Get the embed fla object player Element |
| 333 | + */ |
| 334 | +getPlayerElement : function() { |
| 335 | + this.playerElement = document.getElementById(this.pid); |
| 336 | + return this.playerElement; |
| 337 | +} |
| 338 | +} |
| 339 | + |
| 340 | +/** |
| 341 | + * function called once player is ready. |
| 342 | + * |
| 343 | + * NOTE: playerID is not always passed so we can't use this: |
| 344 | + */ |
| 345 | +function onKdpReady(playerId) { |
| 346 | + mw.log("player is ready::" + playerId); |
| 347 | +} |
| 348 | + |
| 349 | +/* |
| 350 | + * ! SWFObject v2.2 <http://code.google.com/p/swfobject/> is released under the |
| 351 | + * MIT License <http://www.opensource.org/licenses/mit-license.php> |
| 352 | + */ |
| 353 | + |
| 354 | +var swfobject = function() { |
| 355 | + |
| 356 | + 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", |
| 357 | + |
| 358 | + win = window, doc = document, nav = navigator, |
| 359 | + |
| 360 | + plugin = false, domLoadFnArr = [ main ], regObjArr = [], objIdArr = [], listenersArr = [], storedAltContent, storedAltContentId, storedCallbackFn, storedCallbackObj, isDomLoaded = false, isExpressInstallActive = false, dynamicStylesheet, dynamicStylesheetMedia, autoHideShow = true, |
| 361 | + |
| 362 | + /* |
| 363 | + * Centralized function for browser feature detection - User agent string |
| 364 | + * detection is only used when no good alternative is possible - Is executed |
| 365 | + * directly for optimal performance |
| 366 | + */ |
| 367 | + ua = function() { |
| 368 | + var w3cdom = typeof doc.getElementById != UNDEF |
| 369 | + && typeof doc.getElementsByTagName != UNDEF |
| 370 | + && typeof doc.createElement != UNDEF, u = nav.userAgent |
| 371 | + .toLowerCase(), p = nav.platform.toLowerCase(), windows = p ? /win/ |
| 372 | + .test(p) |
| 373 | + : /win/.test(u), mac = p ? /mac/.test(p) : /mac/.test(u), webkit = /webkit/ |
| 374 | + .test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, |
| 375 | + "$1")) : false, // returns either the webkit version or false if |
| 376 | + // not webkit |
| 377 | + ie = !+"\v1", // feature detection based on Andrea Giammarchi's |
| 378 | + // solution: |
| 379 | + // http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html |
| 380 | + playerVersion = [ 0, 0, 0 ], d = null; |
| 381 | + if (typeof nav.plugins != UNDEF |
| 382 | + && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { |
| 383 | + d = nav.plugins[SHOCKWAVE_FLASH].description; |
| 384 | + if (d |
| 385 | + && !(typeof nav.mimeTypes != UNDEF |
| 386 | + && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin |
| 387 | + // indicates |
| 388 | + // whether |
| 389 | + // plug-ins |
| 390 | + // are |
| 391 | + // enabled |
| 392 | + // or |
| 393 | + // disabled |
| 394 | + // in |
| 395 | + // Safari |
| 396 | + // 3+ |
| 397 | + plugin = true; |
| 398 | + ie = false; // cascaded feature detection for Internet Explorer |
| 399 | + d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); |
| 400 | + playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); |
| 401 | + playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), |
| 402 | + 10); |
| 403 | + playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace( |
| 404 | + /^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; |
| 405 | + } |
| 406 | + } else if (typeof win.ActiveXObject != UNDEF) { |
| 407 | + try { |
| 408 | + var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); |
| 409 | + if (a) { // a will return null when ActiveX is disabled |
| 410 | + d = a.GetVariable("$version"); |
| 411 | + if (d) { |
| 412 | + ie = true; // cascaded feature detection for Internet |
| 413 | + // Explorer |
| 414 | + d = d.split(" ")[1].split(","); |
| 415 | + playerVersion = [ parseInt(d[0], 10), |
| 416 | + parseInt(d[1], 10), parseInt(d[2], 10) ]; |
| 417 | + } |
| 418 | + } |
| 419 | + } catch (e) { |
| 420 | + } |
| 421 | + } |
| 422 | + return { |
| 423 | + w3 : w3cdom, |
| 424 | + pv : playerVersion, |
| 425 | + wk : webkit, |
| 426 | + ie : ie, |
| 427 | + win : windows, |
| 428 | + mac : mac |
| 429 | + }; |
| 430 | + }(); |
| 431 | + |
| 432 | + function callDomLoadFunctions() { |
| 433 | + if (isDomLoaded) { |
| 434 | + return; |
| 435 | + } |
| 436 | + try { // test if we can really add/remove elements to/from the DOM; we |
| 437 | + // don't want to fire it too early |
| 438 | + var t = doc.getElementsByTagName("body")[0] |
| 439 | + .appendChild(createElement("span")); |
| 440 | + t.parentNode.removeChild(t); |
| 441 | + } catch (e) { |
| 442 | + return; |
| 443 | + } |
| 444 | + isDomLoaded = true; |
| 445 | + var dl = domLoadFnArr.length; |
| 446 | + for ( var i = 0; i < dl; i++) { |
| 447 | + domLoadFnArr[i](); |
| 448 | + } |
| 449 | + } |
| 450 | + |
| 451 | + function addDomLoadEvent(fn) { |
| 452 | + if (isDomLoaded) { |
| 453 | + fn(); |
| 454 | + } else { |
| 455 | + domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only |
| 456 | + // available in IE5.5+ |
| 457 | + } |
| 458 | + } |
| 459 | + |
| 460 | + /* |
| 461 | + * Cross-browser onload - Based on James Edwards' solution: |
| 462 | + * http://brothercake.com/site/resources/scripts/onload/ - Will fire an |
| 463 | + * event as soon as a web page including all of its assets are loaded |
| 464 | + */ |
| 465 | + function addLoadEvent(fn) { |
| 466 | + if (typeof win.addEventListener != UNDEF) { |
| 467 | + win.addEventListener("load", fn, false); |
| 468 | + } else if (typeof doc.addEventListener != UNDEF) { |
| 469 | + doc.addEventListener("load", fn, false); |
| 470 | + } else if (typeof win.attachEvent != UNDEF) { |
| 471 | + addListener(win, "onload", fn); |
| 472 | + } else if (typeof win.onload == "function") { |
| 473 | + var fnOld = win.onload; |
| 474 | + win.onload = function() { |
| 475 | + fnOld(); |
| 476 | + fn(); |
| 477 | + }; |
| 478 | + } else { |
| 479 | + win.onload = fn; |
| 480 | + } |
| 481 | + } |
| 482 | + |
| 483 | + /* |
| 484 | + * Main function - Will preferably execute onDomLoad, otherwise onload (as a |
| 485 | + * fallback) |
| 486 | + */ |
| 487 | + function main() { |
| 488 | + if (plugin) { |
| 489 | + testPlayerVersion(); |
| 490 | + } else { |
| 491 | + matchVersions(); |
| 492 | + } |
| 493 | + } |
| 494 | + |
| 495 | + /* |
| 496 | + * Detect the Flash Player version for non-Internet Explorer browsers - |
| 497 | + * Detecting the plug-in version via the object element is more precise than |
| 498 | + * using the plugins collection item's description: a. Both release and |
| 499 | + * build numbers can be detected b. Avoid wrong descriptions by corrupt |
| 500 | + * installers provided by Adobe c. Avoid wrong descriptions by multiple |
| 501 | + * Flash Player entries in the plugin Array, caused by incorrect browser |
| 502 | + * imports - Disadvantage of this method is that it depends on the |
| 503 | + * availability of the DOM, while the plugins collection is immediately |
| 504 | + * available |
| 505 | + */ |
| 506 | + function testPlayerVersion() { |
| 507 | + var b = doc.getElementsByTagName("body")[0]; |
| 508 | + var o = createElement(OBJECT); |
| 509 | + o.setAttribute("type", FLASH_MIME_TYPE); |
| 510 | + var t = b.appendChild(o); |
| 511 | + if (t) { |
| 512 | + var counter = 0; |
| 513 | + (function() { |
| 514 | + if (typeof t.GetVariable != UNDEF) { |
| 515 | + try { |
| 516 | + var d = t.GetVariable("$version"); |
| 517 | + if (d) { |
| 518 | + d = d.split(" ")[1].split(","); |
| 519 | + ua.pv = [ parseInt(d[0], 10), parseInt(d[1], 10), |
| 520 | + parseInt(d[2], 10) ]; |
| 521 | + } |
| 522 | + } catch (e) { |
| 523 | + // error in grabbing flash version |
| 524 | + } |
| 525 | + } else if (counter < 10) { |
| 526 | + counter++; |
| 527 | + setTimeout(arguments.callee, 10); |
| 528 | + return; |
| 529 | + } |
| 530 | + b.removeChild(o); |
| 531 | + t = null; |
| 532 | + matchVersions(); |
| 533 | + })(); |
| 534 | + } else { |
| 535 | + matchVersions(); |
| 536 | + } |
| 537 | + } |
| 538 | + |
| 539 | + /* |
| 540 | + * Perform Flash Player and SWF version matching; static publishing only |
| 541 | + */ |
| 542 | + function matchVersions() { |
| 543 | + var rl = regObjArr.length; |
| 544 | + if (rl > 0) { |
| 545 | + for ( var i = 0; i < rl; i++) { // for each registered object |
| 546 | + // element |
| 547 | + var id = regObjArr[i].id; |
| 548 | + var cb = regObjArr[i].callbackFn; |
| 549 | + var cbObj = { |
| 550 | + success : false, |
| 551 | + id : id |
| 552 | + }; |
| 553 | + if (ua.pv[0] > 0) { |
| 554 | + var obj = getElementById(id); |
| 555 | + if (obj) { |
| 556 | + if (hasPlayerVersion(regObjArr[i].swfVersion) |
| 557 | + && !(ua.wk && ua.wk < 312)) { // Flash Player |
| 558 | + // version >= |
| 559 | + // published SWF |
| 560 | + // version: |
| 561 | + // Houston, we |
| 562 | + // have a match! |
| 563 | + setVisibility(id, true); |
| 564 | + if (cb) { |
| 565 | + cbObj.success = true; |
| 566 | + cbObj.ref = getObjectById(id); |
| 567 | + cb(cbObj); |
| 568 | + } |
| 569 | + } else if (regObjArr[i].expressInstall |
| 570 | + && canExpressInstall()) { // show the Adobe |
| 571 | + // Express Install |
| 572 | + // dialog if set by |
| 573 | + // the web page |
| 574 | + // author and if |
| 575 | + // supported |
| 576 | + var att = {}; |
| 577 | + att.data = regObjArr[i].expressInstall; |
| 578 | + att.width = obj.getAttribute("width") || "0"; |
| 579 | + att.height = obj.getAttribute("height") || "0"; |
| 580 | + if (obj.getAttribute("class")) { |
| 581 | + att.styleclass = obj.getAttribute("class"); |
| 582 | + } |
| 583 | + if (obj.getAttribute("align")) { |
| 584 | + att.align = obj.getAttribute("align"); |
| 585 | + } |
| 586 | + // parse HTML object param element's name-value |
| 587 | + // pairs |
| 588 | + var par = {}; |
| 589 | + var p = obj.getElementsByTagName("param"); |
| 590 | + var pl = p.length; |
| 591 | + for ( var j = 0; j < pl; j++) { |
| 592 | + if (p[j].getAttribute("name").toLowerCase() != "movie") { |
| 593 | + par[p[j].getAttribute("name")] = p[j] |
| 594 | + .getAttribute("value"); |
| 595 | + } |
| 596 | + } |
| 597 | + showExpressInstall(att, par, id, cb); |
| 598 | + } else { // Flash Player and SWF version mismatch or |
| 599 | + // an older Webkit engine that ignores the |
| 600 | + // HTML object element's nested param |
| 601 | + // elements: display alternative content |
| 602 | + // instead of SWF |
| 603 | + displayAltContent(obj); |
| 604 | + if (cb) { |
| 605 | + cb(cbObj); |
| 606 | + } |
| 607 | + } |
| 608 | + } |
| 609 | + } else { // if no Flash Player is installed or the fp version |
| 610 | + // cannot be detected we let the HTML object element |
| 611 | + // do its job (either show a SWF or alternative |
| 612 | + // content) |
| 613 | + setVisibility(id, true); |
| 614 | + if (cb) { |
| 615 | + var o = getObjectById(id); // test whether there is an |
| 616 | + // HTML object element or |
| 617 | + // not |
| 618 | + if (o && typeof o.SetVariable != UNDEF) { |
| 619 | + cbObj.success = true; |
| 620 | + cbObj.ref = o; |
| 621 | + } |
| 622 | + cb(cbObj); |
| 623 | + } |
| 624 | + } |
| 625 | + } |
| 626 | + } |
| 627 | + } |
| 628 | + |
| 629 | + function getObjectById(objectIdStr) { |
| 630 | + var r = null; |
| 631 | + var o = getElementById(objectIdStr); |
| 632 | + if (o && o.nodeName == "OBJECT") { |
| 633 | + if (typeof o.SetVariable != UNDEF) { |
| 634 | + r = o; |
| 635 | + } else { |
| 636 | + var n = o.getElementsByTagName(OBJECT)[0]; |
| 637 | + if (n) { |
| 638 | + r = n; |
| 639 | + } |
| 640 | + } |
| 641 | + } |
| 642 | + return r; |
| 643 | + } |
| 644 | + |
| 645 | + /* |
| 646 | + * Requirements for Adobe Express Install - only one instance can be active |
| 647 | + * at a time - fp 6.0.65 or higher - Win/Mac OS only - no Webkit engines |
| 648 | + * older than version 312 |
| 649 | + */ |
| 650 | + function canExpressInstall() { |
| 651 | + return !isExpressInstallActive && hasPlayerVersion("6.0.65") |
| 652 | + && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); |
| 653 | + } |
| 654 | + |
| 655 | + /* |
| 656 | + * Show the Adobe Express Install dialog - Reference: |
| 657 | + * http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 |
| 658 | + */ |
| 659 | + function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { |
| 660 | + isExpressInstallActive = true; |
| 661 | + storedCallbackFn = callbackFn || null; |
| 662 | + storedCallbackObj = { |
| 663 | + success : false, |
| 664 | + id : replaceElemIdStr |
| 665 | + }; |
| 666 | + var obj = getElementById(replaceElemIdStr); |
| 667 | + if (obj) { |
| 668 | + if (obj.nodeName == "OBJECT") { // static publishing |
| 669 | + storedAltContent = abstractAltContent(obj); |
| 670 | + storedAltContentId = null; |
| 671 | + } else { // dynamic publishing |
| 672 | + storedAltContent = obj; |
| 673 | + storedAltContentId = replaceElemIdStr; |
| 674 | + } |
| 675 | + att.id = EXPRESS_INSTALL_ID; |
| 676 | + if (typeof att.width == UNDEF |
| 677 | + || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { |
| 678 | + att.width = "310"; |
| 679 | + } |
| 680 | + if (typeof att.height == UNDEF |
| 681 | + || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { |
| 682 | + att.height = "137"; |
| 683 | + } |
| 684 | + doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; |
| 685 | + var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", fv = "MMredirectURL=" |
| 686 | + + win.location.toString().replace(/&/g, "%26") |
| 687 | + + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; |
| 688 | + if (typeof par.flashvars != UNDEF) { |
| 689 | + par.flashvars += "&" + fv; |
| 690 | + } else { |
| 691 | + par.flashvars = fv; |
| 692 | + } |
| 693 | + // IE only: when a SWF is loading (AND: not available in cache) wait |
| 694 | + // for the readyState of the object element to become 4 before |
| 695 | + // removing it, |
| 696 | + // because you cannot properly cancel a loading SWF file without |
| 697 | + // breaking browser load references, also obj.onreadystatechange |
| 698 | + // doesn't work |
| 699 | + if (ua.ie && ua.win && obj.readyState != 4) { |
| 700 | + var newObj = createElement("div"); |
| 701 | + replaceElemIdStr += "SWFObjectNew"; |
| 702 | + newObj.setAttribute("id", replaceElemIdStr); |
| 703 | + obj.parentNode.insertBefore(newObj, obj); // insert |
| 704 | + // placeholder div |
| 705 | + // that will be |
| 706 | + // replaced by the |
| 707 | + // object element |
| 708 | + // that loads |
| 709 | + // expressinstall.swf |
| 710 | + obj.style.display = "none"; |
| 711 | + (function() { |
| 712 | + if (obj.readyState == 4) { |
| 713 | + obj.parentNode.removeChild(obj); |
| 714 | + } else { |
| 715 | + setTimeout(arguments.callee, 10); |
| 716 | + } |
| 717 | + })(); |
| 718 | + } |
| 719 | + createSWF(att, par, replaceElemIdStr); |
| 720 | + } |
| 721 | + } |
| 722 | + |
| 723 | + /* |
| 724 | + * Functions to abstract and display alternative content |
| 725 | + */ |
| 726 | + function displayAltContent(obj) { |
| 727 | + if (ua.ie && ua.win && obj.readyState != 4) { |
| 728 | + // IE only: when a SWF is loading (AND: not available in cache) wait |
| 729 | + // for the readyState of the object element to become 4 before |
| 730 | + // removing it, |
| 731 | + // because you cannot properly cancel a loading SWF file without |
| 732 | + // breaking browser load references, also obj.onreadystatechange |
| 733 | + // doesn't work |
| 734 | + var el = createElement("div"); |
| 735 | + obj.parentNode.insertBefore(el, obj); // insert placeholder div |
| 736 | + // that will be replaced by |
| 737 | + // the alternative content |
| 738 | + el.parentNode.replaceChild(abstractAltContent(obj), el); |
| 739 | + obj.style.display = "none"; |
| 740 | + (function() { |
| 741 | + if (obj.readyState == 4) { |
| 742 | + obj.parentNode.removeChild(obj); |
| 743 | + } else { |
| 744 | + setTimeout(arguments.callee, 10); |
| 745 | + } |
| 746 | + })(); |
| 747 | + } else { |
| 748 | + obj.parentNode.replaceChild(abstractAltContent(obj), obj); |
| 749 | + } |
| 750 | + } |
| 751 | + |
| 752 | + function abstractAltContent(obj) { |
| 753 | + var ac = createElement("div"); |
| 754 | + if (ua.win && ua.ie) { |
| 755 | + ac.innerHTML = obj.innerHTML; |
| 756 | + } else { |
| 757 | + var nestedObj = obj.getElementsByTagName(OBJECT)[0]; |
| 758 | + if (nestedObj) { |
| 759 | + var c = nestedObj.childNodes; |
| 760 | + if (c) { |
| 761 | + var cl = c.length; |
| 762 | + for ( var i = 0; i < cl; i++) { |
| 763 | + if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") |
| 764 | + && !(c[i].nodeType == 8)) { |
| 765 | + ac.appendChild(c[i].cloneNode(true)); |
| 766 | + } |
| 767 | + } |
| 768 | + } |
| 769 | + } |
| 770 | + } |
| 771 | + return ac; |
| 772 | + } |
| 773 | + |
| 774 | + /* |
| 775 | + * Cross-browser dynamic SWF creation |
| 776 | + */ |
| 777 | + function createSWF(attObj, parObj, id) { |
| 778 | + var r, el = getElementById(id); |
| 779 | + if (ua.wk && ua.wk < 312) { |
| 780 | + return r; |
| 781 | + } |
| 782 | + if (el) { |
| 783 | + if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the |
| 784 | + // object element, it will |
| 785 | + // inherit the 'id' from the |
| 786 | + // alternative content |
| 787 | + attObj.id = id; |
| 788 | + } |
| 789 | + if (ua.ie && ua.win) { // Internet Explorer + the HTML object |
| 790 | + // element + W3C DOM methods do not combine: |
| 791 | + // fall back to outerHTML |
| 792 | + var att = ""; |
| 793 | + for ( var i in attObj) { |
| 794 | + if (attObj[i] != Object.prototype[i]) { // filter out |
| 795 | + // prototype |
| 796 | + // additions from |
| 797 | + // other potential |
| 798 | + // libraries |
| 799 | + if (i.toLowerCase() == "data") { |
| 800 | + parObj.movie = attObj[i]; |
| 801 | + } else if (i.toLowerCase() == "styleclass") { // 'class' |
| 802 | + // is an |
| 803 | + // ECMA4 |
| 804 | + // reserved |
| 805 | + // keyword |
| 806 | + att += ' class="' + attObj[i] + '"'; |
| 807 | + } else if (i.toLowerCase() != "classid") { |
| 808 | + att += ' ' + i + '="' + attObj[i] + '"'; |
| 809 | + } |
| 810 | + } |
| 811 | + } |
| 812 | + var par = ""; |
| 813 | + for ( var j in parObj) { |
| 814 | + if (parObj[j] != Object.prototype[j]) { // filter out |
| 815 | + // prototype |
| 816 | + // additions from |
| 817 | + // other potential |
| 818 | + // libraries |
| 819 | + par += '<param name="' + j + '" value="' + parObj[j] |
| 820 | + + '" />'; |
| 821 | + } |
| 822 | + } |
| 823 | + el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' |
| 824 | + + att + '>' + par + '</object>'; |
| 825 | + objIdArr[objIdArr.length] = attObj.id; // stored to fix object |
| 826 | + // 'leaks' on unload |
| 827 | + // (dynamic publishing |
| 828 | + // only) |
| 829 | + r = getElementById(attObj.id); |
| 830 | + } else { // well-behaving browsers |
| 831 | + var o = createElement(OBJECT); |
| 832 | + o.setAttribute("type", FLASH_MIME_TYPE); |
| 833 | + for ( var m in attObj) { |
| 834 | + if (attObj[m] != Object.prototype[m]) { // filter out |
| 835 | + // prototype |
| 836 | + // additions from |
| 837 | + // other potential |
| 838 | + // libraries |
| 839 | + if (m.toLowerCase() == "styleclass") { // 'class' is an |
| 840 | + // ECMA4 |
| 841 | + // reserved |
| 842 | + // keyword |
| 843 | + o.setAttribute("class", attObj[m]); |
| 844 | + } else if (m.toLowerCase() != "classid") { // filter |
| 845 | + // out IE |
| 846 | + // specific |
| 847 | + // attribute |
| 848 | + o.setAttribute(m, attObj[m]); |
| 849 | + } |
| 850 | + } |
| 851 | + } |
| 852 | + for ( var n in parObj) { |
| 853 | + if (parObj[n] != Object.prototype[n] |
| 854 | + && n.toLowerCase() != "movie") { // filter out |
| 855 | + // prototype |
| 856 | + // additions |
| 857 | + // from other |
| 858 | + // potential |
| 859 | + // libraries and |
| 860 | + // IE specific |
| 861 | + // param element |
| 862 | + createObjParam(o, n, parObj[n]); |
| 863 | + } |
| 864 | + } |
| 865 | + el.parentNode.replaceChild(o, el); |
| 866 | + r = o; |
| 867 | + } |
| 868 | + } |
| 869 | + return r; |
| 870 | + } |
| 871 | + |
| 872 | + function createObjParam(el, pName, pValue) { |
| 873 | + var p = createElement("param"); |
| 874 | + p.setAttribute("name", pName); |
| 875 | + p.setAttribute("value", pValue); |
| 876 | + el.appendChild(p); |
| 877 | + } |
| 878 | + |
| 879 | + /* |
| 880 | + * Cross-browser SWF removal - Especially needed to safely and completely |
| 881 | + * remove a SWF in Internet Explorer |
| 882 | + */ |
| 883 | + function removeSWF(id) { |
| 884 | + var obj = getElementById(id); |
| 885 | + if (obj && obj.nodeName == "OBJECT") { |
| 886 | + if (ua.ie && ua.win) { |
| 887 | + obj.style.display = "none"; |
| 888 | + (function() { |
| 889 | + if (obj.readyState == 4) { |
| 890 | + removeObjectInIE(id); |
| 891 | + } else { |
| 892 | + setTimeout(arguments.callee, 10); |
| 893 | + } |
| 894 | + })(); |
| 895 | + } else { |
| 896 | + obj.parentNode.removeChild(obj); |
| 897 | + } |
| 898 | + } |
| 899 | + } |
| 900 | + |
| 901 | + function removeObjectInIE(id) { |
| 902 | + var obj = getElementById(id); |
| 903 | + if (obj) { |
| 904 | + for ( var i in obj) { |
| 905 | + if (typeof obj[i] == "function") { |
| 906 | + obj[i] = null; |
| 907 | + } |
| 908 | + } |
| 909 | + obj.parentNode.removeChild(obj); |
| 910 | + } |
| 911 | + } |
| 912 | + |
| 913 | + /* |
| 914 | + * Functions to optimize JavaScript compression |
| 915 | + */ |
| 916 | + function getElementById(id) { |
| 917 | + var el = null; |
| 918 | + try { |
| 919 | + el = doc.getElementById(id); |
| 920 | + } catch (e) { |
| 921 | + } |
| 922 | + return el; |
| 923 | + } |
| 924 | + |
| 925 | + function createElement(el) { |
| 926 | + return doc.createElement(el); |
| 927 | + } |
| 928 | + |
| 929 | + /* |
| 930 | + * Updated attachEvent function for Internet Explorer - Stores attachEvent |
| 931 | + * information in an Array, so on unload the detachEvent functions can be |
| 932 | + * called to avoid memory leaks |
| 933 | + */ |
| 934 | + function addListener(target, eventType, fn) { |
| 935 | + target.attachEvent(eventType, fn); |
| 936 | + listenersArr[listenersArr.length] = [ target, eventType, fn ]; |
| 937 | + } |
| 938 | + |
| 939 | + /* |
| 940 | + * Flash Player and SWF content version matching |
| 941 | + */ |
| 942 | + function hasPlayerVersion(rv) { |
| 943 | + var pv = ua.pv, v = rv.split("."); |
| 944 | + v[0] = parseInt(v[0], 10); |
| 945 | + v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" |
| 946 | + // instead of "9.0.0" |
| 947 | + v[2] = parseInt(v[2], 10) || 0; |
| 948 | + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] |
| 949 | + && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; |
| 950 | + } |
| 951 | + |
| 952 | + /* |
| 953 | + * Cross-browser dynamic CSS creation - Based on Bobby van der Sluis' |
| 954 | + * solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php |
| 955 | + */ |
| 956 | + function createCSS(sel, decl, media, newStyle) { |
| 957 | + if (ua.ie && ua.mac) { |
| 958 | + return; |
| 959 | + } |
| 960 | + var h = doc.getElementsByTagName("head")[0]; |
| 961 | + if (!h) { |
| 962 | + return; |
| 963 | + } // to also support badly authored HTML pages that lack a head |
| 964 | + // element |
| 965 | + var m = (media && typeof media == "string") ? media : "screen"; |
| 966 | + if (newStyle) { |
| 967 | + dynamicStylesheet = null; |
| 968 | + dynamicStylesheetMedia = null; |
| 969 | + } |
| 970 | + if (!dynamicStylesheet || dynamicStylesheetMedia != m) { |
| 971 | + // create dynamic stylesheet + get a global reference to it |
| 972 | + var s = createElement("style"); |
| 973 | + s.setAttribute("type", "text/css"); |
| 974 | + s.setAttribute("media", m); |
| 975 | + dynamicStylesheet = h.appendChild(s); |
| 976 | + if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF |
| 977 | + && doc.styleSheets.length > 0) { |
| 978 | + dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; |
| 979 | + } |
| 980 | + dynamicStylesheetMedia = m; |
| 981 | + } |
| 982 | + // add style rule |
| 983 | + if (ua.ie && ua.win) { |
| 984 | + if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { |
| 985 | + dynamicStylesheet.addRule(sel, decl); |
| 986 | + } |
| 987 | + } else { |
| 988 | + if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { |
| 989 | + dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" |
| 990 | + + decl + "}")); |
| 991 | + } |
| 992 | + } |
| 993 | + } |
| 994 | + |
| 995 | + function setVisibility(id, isVisible) { |
| 996 | + if (!autoHideShow) { |
| 997 | + return; |
| 998 | + } |
| 999 | + var v = isVisible ? "visible" : "hidden"; |
| 1000 | + if (isDomLoaded && getElementById(id)) { |
| 1001 | + getElementById(id).style.visibility = v; |
| 1002 | + } else { |
| 1003 | + createCSS("#" + id, "visibility:" + v); |
| 1004 | + } |
| 1005 | + } |
| 1006 | + |
| 1007 | + /* |
| 1008 | + * Filter to avoid XSS attacks |
| 1009 | + */ |
| 1010 | + function urlEncodeIfNecessary(s) { |
| 1011 | + var regex = /[\\\"<>\.;]/; |
| 1012 | + var hasBadChars = regex.exec(s) != null; |
| 1013 | + return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) |
| 1014 | + : s; |
| 1015 | + } |
| 1016 | + |
| 1017 | + /* |
| 1018 | + * Release memory to avoid memory leaks caused by closures, fix hanging |
| 1019 | + * audio/video threads and force open sockets/NetConnections to disconnect |
| 1020 | + * (Internet Explorer only) |
| 1021 | + */ |
| 1022 | + var cleanup = function() { |
| 1023 | + if (ua.ie && ua.win) { |
| 1024 | + window.attachEvent("onunload", function() { |
| 1025 | + // remove listeners to avoid memory leaks |
| 1026 | + var ll = listenersArr.length; |
| 1027 | + for ( var i = 0; i < ll; i++) { |
| 1028 | + listenersArr[i][0].detachEvent(listenersArr[i][1], |
| 1029 | + listenersArr[i][2]); |
| 1030 | + } |
| 1031 | + // cleanup dynamically embedded objects to fix audio/video |
| 1032 | + // threads and force open sockets and NetConnections to |
| 1033 | + // disconnect |
| 1034 | + var il = objIdArr.length; |
| 1035 | + for ( var j = 0; j < il; j++) { |
| 1036 | + removeSWF(objIdArr[j]); |
| 1037 | + } |
| 1038 | + // cleanup library's main closures to avoid memory leaks |
| 1039 | + for ( var k in ua) { |
| 1040 | + ua[k] = null; |
| 1041 | + } |
| 1042 | + ua = null; |
| 1043 | + for ( var l in swfobject) { |
| 1044 | + swfobject[l] = null; |
| 1045 | + } |
| 1046 | + swfobject = null; |
| 1047 | + }); |
| 1048 | + } |
| 1049 | + }(); |
| 1050 | + |
| 1051 | + return { |
| 1052 | + /* |
| 1053 | + * Public API - Reference: |
| 1054 | + * http://code.google.com/p/swfobject/wiki/documentation |
| 1055 | + */ |
| 1056 | + registerObject : function(objectIdStr, swfVersionStr, xiSwfUrlStr, |
| 1057 | + callbackFn) { |
| 1058 | + if (ua.w3 && objectIdStr && swfVersionStr) { |
| 1059 | + var regObj = {}; |
| 1060 | + regObj.id = objectIdStr; |
| 1061 | + regObj.swfVersion = swfVersionStr; |
| 1062 | + regObj.expressInstall = xiSwfUrlStr; |
| 1063 | + regObj.callbackFn = callbackFn; |
| 1064 | + regObjArr[regObjArr.length] = regObj; |
| 1065 | + setVisibility(objectIdStr, false); |
| 1066 | + } else if (callbackFn) { |
| 1067 | + callbackFn( { |
| 1068 | + success : false, |
| 1069 | + id : objectIdStr |
| 1070 | + }); |
| 1071 | + } |
| 1072 | + }, |
| 1073 | + |
| 1074 | + getObjectById : function(objectIdStr) { |
| 1075 | + if (ua.w3) { |
| 1076 | + return getObjectById(objectIdStr); |
| 1077 | + } |
| 1078 | + }, |
| 1079 | + // XXX added by mdale ( since we know the dom is ready, |
| 1080 | + // this works better. |
| 1081 | + callDomLoadFunctions : function() { |
| 1082 | + callDomLoadFunctions(); |
| 1083 | + }, |
| 1084 | + |
| 1085 | + embedSWF : function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, |
| 1086 | + swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, |
| 1087 | + callbackFn) { |
| 1088 | + var callbackObj = { |
| 1089 | + success : false, |
| 1090 | + id : replaceElemIdStr |
| 1091 | + }; |
| 1092 | + if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr |
| 1093 | + && replaceElemIdStr && widthStr && heightStr |
| 1094 | + && swfVersionStr) { |
| 1095 | + setVisibility(replaceElemIdStr, false); |
| 1096 | + addDomLoadEvent(function() { |
| 1097 | + widthStr += ""; // auto-convert to string |
| 1098 | + heightStr += ""; |
| 1099 | + var att = {}; |
| 1100 | + if (attObj && typeof attObj === OBJECT) { |
| 1101 | + for ( var i in attObj) { // copy object to avoid the |
| 1102 | + // use of references, |
| 1103 | + // because web authors often |
| 1104 | + // reuse attObj for multiple |
| 1105 | + // SWFs |
| 1106 | + att[i] = attObj[i]; |
| 1107 | + } |
| 1108 | + } |
| 1109 | + att.data = swfUrlStr; |
| 1110 | + att.width = widthStr; |
| 1111 | + att.height = heightStr; |
| 1112 | + var par = {}; |
| 1113 | + if (parObj && typeof parObj === OBJECT) { |
| 1114 | + for ( var j in parObj) { // copy object to avoid the |
| 1115 | + // use of references, |
| 1116 | + // because web authors often |
| 1117 | + // reuse parObj for multiple |
| 1118 | + // SWFs |
| 1119 | + par[j] = parObj[j]; |
| 1120 | + } |
| 1121 | + } |
| 1122 | + if (flashvarsObj && typeof flashvarsObj === OBJECT) { |
| 1123 | + for ( var k in flashvarsObj) { // copy object to avoid |
| 1124 | + // the use of |
| 1125 | + // references, because |
| 1126 | + // web authors often |
| 1127 | + // reuse flashvarsObj |
| 1128 | + // for multiple SWFs |
| 1129 | + if (typeof par.flashvars != UNDEF) { |
| 1130 | + par.flashvars += "&" + k + "=" |
| 1131 | + + flashvarsObj[k]; |
| 1132 | + } else { |
| 1133 | + par.flashvars = k + "=" + flashvarsObj[k]; |
| 1134 | + } |
| 1135 | + } |
| 1136 | + } |
| 1137 | + if (hasPlayerVersion(swfVersionStr)) { // create SWF |
| 1138 | + var obj = createSWF(att, par, replaceElemIdStr); |
| 1139 | + if (att.id == replaceElemIdStr) { |
| 1140 | + setVisibility(replaceElemIdStr, true); |
| 1141 | + } |
| 1142 | + callbackObj.success = true; |
| 1143 | + callbackObj.ref = obj; |
| 1144 | + } else if (xiSwfUrlStr && canExpressInstall()) { // show |
| 1145 | + // Adobe |
| 1146 | + // Express |
| 1147 | + // Install |
| 1148 | + att.data = xiSwfUrlStr; |
| 1149 | + showExpressInstall(att, par, replaceElemIdStr, |
| 1150 | + callbackFn); |
| 1151 | + return; |
| 1152 | + } else { // show alternative content |
| 1153 | + setVisibility(replaceElemIdStr, true); |
| 1154 | + } |
| 1155 | + if (callbackFn) { |
| 1156 | + callbackFn(callbackObj); |
| 1157 | + } |
| 1158 | + }); |
| 1159 | + } else if (callbackFn) { |
| 1160 | + callbackFn(callbackObj); |
| 1161 | + } |
| 1162 | + }, |
| 1163 | + |
| 1164 | + switchOffAutoHideShow : function() { |
| 1165 | + autoHideShow = false; |
| 1166 | + }, |
| 1167 | + |
| 1168 | + ua : ua, |
| 1169 | + |
| 1170 | + getFlashPlayerVersion : function() { |
| 1171 | + return { |
| 1172 | + major : ua.pv[0], |
| 1173 | + minor : ua.pv[1], |
| 1174 | + release : ua.pv[2] |
| 1175 | + }; |
| 1176 | + }, |
| 1177 | + |
| 1178 | + hasFlashPlayerVersion : hasPlayerVersion, |
| 1179 | + |
| 1180 | + createSWF : function(attObj, parObj, replaceElemIdStr) { |
| 1181 | + if (ua.w3) { |
| 1182 | + return createSWF(attObj, parObj, replaceElemIdStr); |
| 1183 | + } else { |
| 1184 | + return undefined; |
| 1185 | + } |
| 1186 | + }, |
| 1187 | + |
| 1188 | + showExpressInstall : function(att, par, replaceElemIdStr, callbackFn) { |
| 1189 | + if (ua.w3 && canExpressInstall()) { |
| 1190 | + showExpressInstall(att, par, replaceElemIdStr, callbackFn); |
| 1191 | + } |
| 1192 | + }, |
| 1193 | + |
| 1194 | + removeSWF : function(objElemIdStr) { |
| 1195 | + if (ua.w3) { |
| 1196 | + removeSWF(objElemIdStr); |
| 1197 | + } |
| 1198 | + }, |
| 1199 | + |
| 1200 | + createCSS : function(selStr, declStr, mediaStr, newStyleBoolean) { |
| 1201 | + if (ua.w3) { |
| 1202 | + createCSS(selStr, declStr, mediaStr, newStyleBoolean); |
| 1203 | + } |
| 1204 | + }, |
| 1205 | + |
| 1206 | + addDomLoadEvent : addDomLoadEvent, |
| 1207 | + |
| 1208 | + addLoadEvent : addLoadEvent, |
| 1209 | + |
| 1210 | + getQueryParamValue : function(param) { |
| 1211 | + var q = doc.location.search || doc.location.hash; |
| 1212 | + if (q) { |
| 1213 | + if (/\?/.test(q)) { |
| 1214 | + q = q.split("?")[1]; |
| 1215 | + } // strip question mark |
| 1216 | + if (param == null) { |
| 1217 | + return urlEncodeIfNecessary(q); |
| 1218 | + } |
| 1219 | + var pairs = q.split("&"); |
| 1220 | + for ( var i = 0; i < pairs.length; i++) { |
| 1221 | + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { |
| 1222 | + return urlEncodeIfNecessary(pairs[i].substring((pairs[i] |
| 1223 | + .indexOf("=") + 1))); |
| 1224 | + } |
| 1225 | + } |
| 1226 | + } |
| 1227 | + return ""; |
| 1228 | +}, |
| 1229 | + |
| 1230 | +// For internal usage only |
| 1231 | + expressInstallCallback : function() { |
| 1232 | + if (isExpressInstallActive) { |
| 1233 | + var obj = getElementById(EXPRESS_INSTALL_ID); |
| 1234 | + if (obj && storedAltContent) { |
| 1235 | + obj.parentNode.replaceChild(storedAltContent, obj); |
| 1236 | + if (storedAltContentId) { |
| 1237 | + setVisibility(storedAltContentId, true); |
| 1238 | + if (ua.ie && ua.win) { |
| 1239 | + storedAltContent.style.display = "block"; |
| 1240 | + } |
| 1241 | + } |
| 1242 | + if (storedCallbackFn) { |
| 1243 | + storedCallbackFn(storedCallbackObj); |
| 1244 | + } |
| 1245 | + } |
| 1246 | + isExpressInstallActive = false; |
| 1247 | + } |
| 1248 | + } |
| 1249 | + }; |
| 1250 | +}(); |
\ No newline at end of file |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/players/mw.EmbedPlayerKplayer.js |
___________________________________________________________________ |
Added: svn:mergeinfo |
1 | 1251 | Merged /branches/REL1_15/phase3/js2/mwEmbed/libEmbedVideo/kplayerEmbed.js:r51646 |
2 | 1252 | Merged /branches/sqlite/js2/mwEmbed/libEmbedVideo/kplayerEmbed.js:r58211-58321 |
Added: svn:eol-style |
3 | 1253 | + native |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/EmbedPlayer.config.php |
— | — | @@ -4,7 +4,7 @@ |
5 | 5 | * $wgMwEmbedModuleConfig[ {ModuleName} ][ {configuration name} ] = value; format |
6 | 6 | */ |
7 | 7 | |
8 | | - return array( |
| 8 | + return array ( |
9 | 9 | // If the player controls should be overlaid on top of the video ( if supported by playback method) |
10 | 10 | // can be set to false per embed player via overlayControls attribute |
11 | 11 | 'EmbedPlayer.OverlayControls' => true, |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/EmbedPlayer.loader.js |
— | — | @@ -10,10 +10,10 @@ |
11 | 11 | */ |
12 | 12 | $( mw ).bind( 'SetupInterface', function( event, callback ){ |
13 | 13 | |
14 | | - // Allow modules to do tag rewrites as well: |
| 14 | + // Allow modules to extend EmbedPlayerRewritePlayerTags rewrites as well: |
15 | 15 | var doModuleTagRewrites = function(){ |
16 | | - $(mw).triggerQueueCallback( 'LoadeRewritePlayerTags', callback ); |
17 | | - } |
| 16 | + $( mw ).triggerQueueCallback( 'EmbedPlayerRewritePlayerTags', callback ); |
| 17 | + }; |
18 | 18 | // Check if we have tags to rewrite: |
19 | 19 | if( $( mw.getConfig( 'EmbedPlayer.RewriteTags' ) ).length ) { |
20 | 20 | var rewriteElementCount = 0; |
— | — | @@ -70,14 +70,14 @@ |
71 | 71 | mw.getConfig( 'EmbedPlayer.IframeParentUrl' ) |
72 | 72 | ){ |
73 | 73 | dependencySet.push('mw.EmbedPlayerNative'); |
74 | | - dependencySet.push('$.postMessage'); |
| 74 | + dependencySet.push('jquery.postMessage'); |
75 | 75 | dependencySet.push('mw.IFramePlayerApiServer'); |
76 | 76 | } |
77 | 77 | |
78 | 78 | // Allow modules to update the set of dependencies: |
79 | 79 | var rewriteElementCount = 0; |
80 | | - $.each( playerSelect, function(inx, playerElement){ |
81 | | - |
| 80 | + $( playerSelect).each( function(inx, playerElement){ |
| 81 | + |
82 | 82 | // Assign an the element an ID ( if its missing one ) |
83 | 83 | if ( $( playerElement ).attr( "id" ) == '' ) { |
84 | 84 | $( playerElement ).attr( "id", 'v' + ( rewriteElementCount++ ) ); |
— | — | @@ -86,7 +86,7 @@ |
87 | 87 | // Add an overlay loader |
88 | 88 | $( playerElement ) |
89 | 89 | .getAbsoluteOverlaySpinner() |
90 | | - .attr('id', 'loadingSpinner_' + $( element ).attr('id') ) |
| 90 | + .attr('id', 'loadingSpinner_' + $( playerElement ).attr('id') ) |
91 | 91 | .addClass( 'playerLoadingSpinner' ); |
92 | 92 | |
93 | 93 | // Add core "skin/interface" loader |
— | — | @@ -107,7 +107,12 @@ |
108 | 108 | |
109 | 109 | // Do the request and process the playerElements with updated dependency set |
110 | 110 | mediaWiki.loader.using( dependencySet, function(){ |
| 111 | + setTimeout( function(){ |
| 112 | + var cat = mw.processEmbedPlayers; |
| 113 | + debugger; |
| 114 | + }, 300); |
111 | 115 | // EmbedPlayer should be ready: |
| 116 | + //mw.processEmbedPlayers( playerSelect, readyCallback ); |
112 | 117 | }); |
113 | 118 | }; |
114 | 119 | |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/iframeApi/mw.IFramePlayerApiClient.js |
— | — | @@ -0,0 +1,192 @@ |
| 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 | + 'exportedMethods': [ |
| 15 | + 'play', |
| 16 | + 'pause' |
| 17 | + ], |
| 18 | + // Local store of the previous sate of player proxy |
| 19 | + '_prevPlayerProxy': {}, |
| 20 | + |
| 21 | + // Stores the current playerProxy ( can be updated by user js ) |
| 22 | + 'init': function( iframe , playerProxy, options ){ |
| 23 | + this.iframe = iframe; |
| 24 | + this.playerProxy = playerProxy; |
| 25 | + // Set the iframe server |
| 26 | + var srcParts = mw.parseUri( mw.absoluteUrl( $j(this.iframe).attr('src') ) ); |
| 27 | + this.iframeServer = srcParts.protocol + '://' + srcParts.authority; |
| 28 | + |
| 29 | + this.addPlayerSendApi(); |
| 30 | + this.addPlayerReciveApi(); |
| 31 | + |
| 32 | + this.addIframeFullscreenBinding(); |
| 33 | + }, |
| 34 | + 'addPlayerSendApi': function(){ |
| 35 | + var _this = this; |
| 36 | + |
| 37 | + // Allow modules to extend the list of iframeExported bindings |
| 38 | + $j( mw ).trigger( 'AddIframePlayerMethods', [ this.exportedMethods ]); |
| 39 | + |
| 40 | + $j.each( this.exportedMethods, function(na, method){ |
| 41 | + _this.playerProxy[ method ] = function(){ |
| 42 | + _this.postMessage( { |
| 43 | + 'method' : method, |
| 44 | + 'args' : $j.makeArray( arguments ) |
| 45 | + } ); |
| 46 | + }; |
| 47 | + }); |
| 48 | + }, |
| 49 | + 'addPlayerReciveApi': function(){ |
| 50 | + var _this = this; |
| 51 | + $j.receiveMessage( function( event ){ |
| 52 | + _this.hanldeReciveMsg( event ) |
| 53 | + }, this.iframeServer); |
| 54 | + }, |
| 55 | + 'addIframeFullscreenBinding': function(){ |
| 56 | + var _this = this; |
| 57 | + parentsAbsoluteList = []; |
| 58 | + var fullscreenMode = false; |
| 59 | + var orgSize = { |
| 60 | + 'width' : $j( _this.iframe ).width(), |
| 61 | + 'height' : $j( _this.iframe ).height(), |
| 62 | + 'position' : null |
| 63 | + } |
| 64 | + |
| 65 | + var doFullscreen = function(){ |
| 66 | + // Make the iframe fullscreen |
| 67 | + $j( _this.iframe ).css({ |
| 68 | + 'z-index': mw.getConfig( 'EmbedPlayer.fullScreenZIndex' ) + 1, |
| 69 | + 'position': 'absolute', |
| 70 | + 'top' : 0, |
| 71 | + 'left' : 0, |
| 72 | + 'width' : $j(window).width(), |
| 73 | + 'height' : $j(window).height() |
| 74 | + }) |
| 75 | + |
| 76 | + // Remove absolute css of the interface parents |
| 77 | + $j( _this.iframe ).parents().each( function() { |
| 78 | + //mw.log(' parent : ' + $j( this ).attr('id' ) + ' class: ' + $j( this ).attr('class') + ' pos: ' + $j( this ).css( 'position' ) ); |
| 79 | + if( $j( this ).css( 'position' ) == 'absolute' ) { |
| 80 | + parentsAbsoluteList.push( $j( this ) ); |
| 81 | + $j( this ).css( 'position', null ); |
| 82 | + } |
| 83 | + } ); |
| 84 | + } |
| 85 | + var restoreWindowMode = function(){ |
| 86 | + $j( _this.iframe ).css( orgSize ); |
| 87 | + // restore any parent absolute pos: |
| 88 | + $j(parentsAbsoluteList).each( function() { |
| 89 | + $j( this ).css( 'position', 'absolute' ); |
| 90 | + } ); |
| 91 | + }; |
| 92 | + |
| 93 | + $j( this.playerProxy ).bind( 'onOpenFullScreen', doFullscreen); |
| 94 | + $j( this.playerProxy ).bind( 'onCloseFullScreen', restoreWindowMode); |
| 95 | + |
| 96 | + }, |
| 97 | + /** |
| 98 | + * Handle received events |
| 99 | + */ |
| 100 | + 'hanldeReciveMsg': function( event ){ |
| 101 | + var _this = this; |
| 102 | + |
| 103 | + // Decode the message |
| 104 | + var msgObject = JSON.parse( event.data ); |
| 105 | + var playerAttributes = mw.getConfig( 'EmbedPlayer.Attributes' ); |
| 106 | + |
| 107 | + // Before we update local attributes check that the object has not been updated by user js |
| 108 | + for( var attrName in playerAttributes ){ |
| 109 | + if( attrName != 'id' ){ |
| 110 | + if( _this._prevPlayerProxy[ attrName ] != _this.playerProxy[ attrName ] ){ |
| 111 | + //mw.log( "IFramePlayerApiClient:: User js update:" + attrName + ' set to: ' + this.playerProxy[ attrName ] + ' != old: ' + _this._prevPlayerProxy[ attrName ] ); |
| 112 | + // Send the updated attribute back to the iframe: |
| 113 | + _this.postMessage({ |
| 114 | + 'attrName' : attrName, |
| 115 | + 'attrValue' : _this.playerProxy[ attrName ] |
| 116 | + }); |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + // Update any attributes |
| 121 | + if( msgObject.attributes ){ |
| 122 | + for( var i in msgObject.attributes ){ |
| 123 | + if( i != 'id' && i != 'class' && i != 'style' ){ |
| 124 | + try{ |
| 125 | + this.playerProxy[ i ] = msgObject.attributes[i]; |
| 126 | + this._prevPlayerProxy[i] = msgObject.attributes[i]; |
| 127 | + } catch( e ){ |
| 128 | + mw.log("Error could not set:" + i ); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + // Trigger any binding events |
| 134 | + if( typeof msgObject.triggerName != 'undefined' && msgObject.triggerArgs != 'undefined') { |
| 135 | + //mw.log('IFramePlayerApiClient:: trigger: ' + msgObject.triggerName ); |
| 136 | + $j( _this.playerProxy ).trigger( msgObject.triggerName, msgObject.triggerArgs ); |
| 137 | + } |
| 138 | + }, |
| 139 | + 'postMessage': function( msgObject ){ |
| 140 | + /*mw.log( "IFramePlayerApiClient:: postMessage(): " + JSON.stringify( msgObject ) + |
| 141 | + ' iframe: ' + this.iframe + ' cw:' + this.iframe.contentWindow + |
| 142 | + ' src: ' + mw.absoluteUrl( $j( this.iframe ).attr('src') ) );*/ |
| 143 | + $j.postMessage( |
| 144 | + JSON.stringify( msgObject ), |
| 145 | + mw.absoluteUrl( $j( this.iframe ).attr('src') ), |
| 146 | + this.iframe.contentWindow |
| 147 | + ); |
| 148 | + } |
| 149 | +}; |
| 150 | + |
| 151 | +//Add the jQuery binding |
| 152 | +( function( $ ) { |
| 153 | + $.fn.iFramePlayer = function( readyCallback ){ |
| 154 | + if( ! this.selector ){ |
| 155 | + this.selector = $j( this ).get(0); |
| 156 | + } |
| 157 | + // Append '_ifp' ( iframe player ) to id of real iframe so that 'id', and 'src' attributes don't conflict |
| 158 | + var originalIframeId = ( $( this.selector ).attr( 'id' ) )? $( this.selector ).attr( 'id' ) : Math.floor( 9999999 * Math.random() ); |
| 159 | + |
| 160 | + var iframePlayerId = originalIframeId + '_ifp' ; |
| 161 | + |
| 162 | + // Append the div element proxy after the iframe |
| 163 | + $j( this.selector ) |
| 164 | + .attr('id', iframePlayerId) |
| 165 | + .after( |
| 166 | + $('<div />') |
| 167 | + .attr( 'id', originalIframeId ) |
| 168 | + ); |
| 169 | + |
| 170 | + var playerProxy = $j( '#' + originalIframeId ).get(0); |
| 171 | + var iframe = $j('#' + iframePlayerId).get(0); |
| 172 | + if(!iframe){ |
| 173 | + mw.log("Error invalide iFramePlayer request"); |
| 174 | + return false; |
| 175 | + } |
| 176 | + if( !iframe['playerApi'] ){ |
| 177 | + iframe['playerApi'] = new mw.IFramePlayerApiClient( iframe, playerProxy ); |
| 178 | + } |
| 179 | + |
| 180 | + // Allow modules to extend the 'iframe' based player |
| 181 | + $j( mw ).trigger( 'newIframePlayerClientSide', [ playerProxy ]); |
| 182 | + |
| 183 | + // Bind the iFrame player ready callback |
| 184 | + if( readyCallback ){ |
| 185 | + $j( playerProxy ).bind( 'playerReady', readyCallback ) |
| 186 | + }; |
| 187 | + |
| 188 | + // Return the player proxy for chaining player events / attributes |
| 189 | + return $j( playerProxy ); |
| 190 | + }; |
| 191 | +} )( jQuery ); |
| 192 | + |
| 193 | +} )( window.mw ); |
\ No newline at end of file |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/iframeApi/mw.IFramePlayerApiClient.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 194 | + text/plain |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/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 newEmbedPlayers: |
| 22 | +$j( mw ).bind( 'newEmbedPlayerEvent', 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 | + $j( mw ).trigger( 'AddIframePlayerBindings', [ this.exportedBindings ]); |
| 59 | + |
| 60 | + this.addIframeListener(); |
| 61 | + this.addIframeSender(); |
| 62 | + $j( 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 | + $j( this.embedPlayer ).bind( 'playerReady', function(){ |
| 93 | + _this.sendPlayerAttributes(); |
| 94 | + }); |
| 95 | + // On monitor event package the attributes for cross domain delivery: |
| 96 | + $j( this.embedPlayer ).bind( 'monitorEvent', function(){ |
| 97 | + _this.sendPlayerAttributes(); |
| 98 | + }) |
| 99 | + |
| 100 | + $j.each( this.exportedBindings, function( inx, bindName ){ |
| 101 | + $j( _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 | + $j( 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: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/EmbedPlayer/iframeApi/mw.IFramePlayerApiServer.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 229 | + text/plain |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/MwEmbedSupport/jquery/jquery.mwEmbedUtil.js |
— | — | @@ -0,0 +1,145 @@ |
| 2 | +/** |
| 3 | + * mwEmbed jQuery utility functions that are too small for their own file |
| 4 | + */ |
| 5 | + |
| 6 | +( function( $ ) { |
| 7 | + |
| 8 | + /** |
| 9 | + * Set a given selector html to the loading spinner: |
| 10 | + */ |
| 11 | + $.fn.loadingSpinner = function( ) { |
| 12 | + if ( this ) { |
| 13 | + $j( this ).html( |
| 14 | + $j( '<div />' ) |
| 15 | + .addClass( "loadingSpinner" ) |
| 16 | + ); |
| 17 | + } |
| 18 | + return this; |
| 19 | + }; |
| 20 | + /** |
| 21 | + * Add an absolute overlay spinner useful for cases where the |
| 22 | + * element does not display child elements, ( images, video ) |
| 23 | + */ |
| 24 | + $.fn.getAbsoluteOverlaySpinner = function(){ |
| 25 | + var pos = $j( this ).offset(); |
| 26 | + var posLeft = ( $j( this ).width() ) ? |
| 27 | + parseInt( pos.left + ( .5 * $j( this ).width() ) -16 ) : |
| 28 | + pos.left + 30; |
| 29 | + |
| 30 | + var posTop = ( $j( this ).height() ) ? |
| 31 | + parseInt( pos.top + ( .5 * $j( this ).height() ) -16 ) : |
| 32 | + pos.top + 30; |
| 33 | + |
| 34 | + var $spinner = $j('<div />') |
| 35 | + .loadingSpinner() |
| 36 | + .css({ |
| 37 | + 'width' : 32, |
| 38 | + 'height' : 32, |
| 39 | + 'position': 'absolute', |
| 40 | + 'top' : posTop + 'px', |
| 41 | + 'left' : posLeft + 'px' |
| 42 | + }); |
| 43 | + $j('body').append( $spinner ); |
| 44 | + return $spinner; |
| 45 | + }; |
| 46 | + |
| 47 | + |
| 48 | + /** |
| 49 | + * Shortcut to a themed button Should be depreciated for $.button |
| 50 | + * bellow |
| 51 | + */ |
| 52 | + $.btnHtml = function( msg, styleClass, iconId, opt ) { |
| 53 | + if ( !opt ) |
| 54 | + opt = { }; |
| 55 | + var href = ( opt.href ) ? opt.href : '#'; |
| 56 | + var target_attr = ( opt.target ) ? ' target="' + opt.target + '" ' : ''; |
| 57 | + var style_attr = ( opt.style ) ? ' style="' + opt.style + '" ' : ''; |
| 58 | + return '<a href="' + href + '" ' + target_attr + style_attr + |
| 59 | + ' class="ui-state-default ui-corner-all ui-icon_link ' + |
| 60 | + styleClass + '"><span class="ui-icon ui-icon-' + iconId + '" ></span>' + |
| 61 | + '<span class="btnText">' + msg + '</span></a>'; |
| 62 | + }; |
| 63 | + |
| 64 | + // Shortcut to generate a jQuery button |
| 65 | + var mw_default_button_options = { |
| 66 | + // The class name for the button link |
| 67 | + 'class' : '', |
| 68 | + |
| 69 | + // The style properties for the button link |
| 70 | + 'style' : { }, |
| 71 | + |
| 72 | + // The text of the button link |
| 73 | + 'text' : '', |
| 74 | + |
| 75 | + // The icon id that precedes the button link: |
| 76 | + 'icon' : 'carat-1-n' |
| 77 | + }; |
| 78 | + |
| 79 | + $.button = function( options ) { |
| 80 | + var options = $j.extend( {}, mw_default_button_options, options); |
| 81 | + |
| 82 | + // Button: |
| 83 | + var $button = $j('<a />') |
| 84 | + .attr('href', '#') |
| 85 | + .addClass( 'ui-state-default ui-corner-all ui-icon_link' ); |
| 86 | + // Add css if set: |
| 87 | + if( options.css ) { |
| 88 | + $button.css( options.css ); |
| 89 | + } |
| 90 | + |
| 91 | + if( options['class'] ) { |
| 92 | + $button.addClass( options['class'] ); |
| 93 | + } |
| 94 | + |
| 95 | + |
| 96 | + // return the button: |
| 97 | + $button.append( |
| 98 | + $j('<span />').addClass( 'ui-icon ui-icon-' + options.icon ), |
| 99 | + $j('<span />').addClass( 'btnText' ) |
| 100 | + .text( options.text ) |
| 101 | + ) |
| 102 | + .buttonHover(); // add buttonHover binding; |
| 103 | + if( !options.text ){ |
| 104 | + $button.css('padding', '1em'); |
| 105 | + } |
| 106 | + return $button; |
| 107 | + }; |
| 108 | + |
| 109 | + // Shortcut to bind hover state |
| 110 | + $.fn.buttonHover = function() { |
| 111 | + $j( this ).hover( |
| 112 | + function() { |
| 113 | + $j( this ).addClass( 'ui-state-hover' ); |
| 114 | + }, |
| 115 | + function() { |
| 116 | + $j( this ).removeClass( 'ui-state-hover' ); |
| 117 | + } |
| 118 | + ); |
| 119 | + return this; |
| 120 | + }; |
| 121 | + |
| 122 | + /** |
| 123 | + * Resize a dialog to fit the window |
| 124 | + * |
| 125 | + * @param {Object} |
| 126 | + * options horizontal and vertical space ( default 50 ) |
| 127 | + */ |
| 128 | + $.fn.dialogFitWindow = function( options ) { |
| 129 | + var opt_default = { 'hspace':50, 'vspace':50 }; |
| 130 | + if ( !options ) |
| 131 | + var options = { }; |
| 132 | + options = $j.extend( opt_default, options ); |
| 133 | + $j( this.selector ).dialog( 'option', 'width', $j( window ).width() - options.hspace ); |
| 134 | + $j( this.selector ).dialog( 'option', 'height', $j( window ).height() - options.vspace ); |
| 135 | + $j( this.selector ).dialog( 'option', 'position', 'center' ); |
| 136 | + // update the child position: (some of this should be pushed |
| 137 | + // up-stream via dialog config options |
| 138 | + $j( this.selector + '~ .ui-dialog-buttonpane' ).css( { |
| 139 | + 'position':'absolute', |
| 140 | + 'left':'0px', |
| 141 | + 'right':'0px', |
| 142 | + 'bottom':'0px' |
| 143 | + } ); |
| 144 | + }; |
| 145 | + |
| 146 | +} )( jQuery ); |
\ No newline at end of file |
Property changes on: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/MwEmbedSupport/jquery/jquery.mwEmbedUtil.js |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 147 | + text/plain |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/MwEmbedSupport/jquery/jquery.triggerQueueCallback.js |
— | — | @@ -69,4 +69,4 @@ |
70 | 70 | $( this ).trigger( triggerName, [ doCallbackCheck ] ); |
71 | 71 | } |
72 | 72 | }; |
73 | | -} )( window.mw ); |
\ No newline at end of file |
| 73 | +} )( jQuery ); |
\ No newline at end of file |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/MwEmbedSupport/MwEmbedSupport.php |
— | — | @@ -3,11 +3,12 @@ |
4 | 4 | return array( |
5 | 5 | "mwEmbedSupport" => array( |
6 | 6 | 'scripts' => array( |
7 | | - "mwEmbedSupport.js", |
| 7 | + "mwEmbedSupport.js", |
8 | 8 | ), |
9 | 9 | 'dependencies' => array( |
10 | | - // jQuery dependencies: |
11 | | - 'jquery.triggerQueueCallback', |
| 10 | + // jQuery dependencies: |
| 11 | + 'jquery.triggerQueueCallback', |
| 12 | + 'jquery.mwEmbedUtil', |
12 | 13 | ), |
13 | 14 | 'messageFile' => 'MwEmbedSupport.i18n.php', |
14 | 15 | ), |
— | — | @@ -16,6 +17,7 @@ |
17 | 18 | 'styles' => 'jquery.menu/jquery.menu.css' |
18 | 19 | ), |
19 | 20 | "jquery.triggerQueueCallback" => array( 'scripts'=> "jquery/jquery.triggerQueueCallback.js" ), |
| 21 | + "jquery.mwEmbedUtil" => array( 'scripts' => "jquery/jquery.mwEmbedUtil.js" ), |
20 | 22 | ) |
21 | 23 | |
22 | 24 | ?> |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/modules/MwEmbedSupport/mwEmbedSupport.js |
— | — | @@ -17,9 +17,8 @@ |
18 | 18 | * |
19 | 19 | * Libraries used include code license in headers |
20 | 20 | * |
21 | | - * @dependency |
| 21 | + * @dependencies |
22 | 22 | */ |
23 | | -alert('wtf'); |
24 | 23 | |
25 | 24 | ( function( mw, $ ) { |
26 | 25 | |
— | — | @@ -38,17 +37,19 @@ |
39 | 38 | } |
40 | 39 | |
41 | 40 | /** |
42 | | - * Enables javascript to target a "interfaces ready" state. |
| 41 | + * Enables javascript modules and pages to target a "interfaces ready" state. |
43 | 42 | * |
44 | 43 | * mw.ready is equivalent to calling: |
45 | 44 | * $j(mw).bind( 'InterfacesReady', callback ); |
46 | 45 | * |
47 | 46 | * This is different from jQuery(document).ready() ( jQuery ready is not |
48 | 47 | * friendly with dynamic includes and not friendly with core interface |
49 | | - * asynchronous build out. ) This allows an interface to do async calls and be globally ready |
| 48 | + * asynchronous build out. ) This allows core interface components to do async conditional |
| 49 | + * load calls, and trigger a ready event once the javascript interface build out is complete |
50 | 50 | * |
51 | 51 | * For example making <video> tags on the page have a video api even if the browser |
52 | | - * does not support html5. |
| 52 | + * does not support html5 requires dynamic loading that can only happen once the page dom is |
| 53 | + * ready |
53 | 54 | * |
54 | 55 | * @param {Function} |
55 | 56 | * callback Function to run once DOM and jQuery are ready |
— | — | @@ -87,7 +88,7 @@ |
88 | 89 | mediaWiki.config.set( name, value ); |
89 | 90 | }; |
90 | 91 | mw.getConfig = function( name, value ){ |
91 | | - mediaWiki.config.get( name, value ); |
| 92 | + return mediaWiki.config.get( name, value ); |
92 | 93 | }; |
93 | 94 | mw.setDefaultConfig = function( name, value ){ |
94 | 95 | if( ! mediaWiki.config.get( name ) ){ |
— | — | @@ -98,6 +99,13 @@ |
99 | 100 | mediaWiki.using( resources, callback, function(){ |
100 | 101 | // failed to load |
101 | 102 | }); |
| 103 | + }; |
| 104 | + |
| 105 | + /** |
| 106 | + * legacy support to get the mwEmbed resource path: |
| 107 | + */ |
| 108 | + mw.getMwEmbedPath = function(){ |
| 109 | + return mediaWiki.config.get( 'wgLoadScript' ).replace('load.php', ''); |
102 | 110 | }; |
103 | 111 | |
104 | 112 | /** |
— | — | @@ -110,23 +118,25 @@ |
111 | 119 | }); |
112 | 120 | return ; |
113 | 121 | } |
114 | | - // Check if we should "merge" the config |
115 | | - if( typeof value == 'object' && typeof mw.getConfig( name ) == 'object' ) { |
116 | | - if ( value.constructor.toString().indexOf("Array") != -1 && |
117 | | - mw.getConfig( name ).constructor.toString().indexOf("Array") != -1 |
118 | | - ){ |
119 | | - // merge in the array |
120 | | - mw.setConfig( name, $j.merge( mw.getConfig( name ), value ) ); |
121 | | - } else { |
122 | | - mw.setConfig( name, value ); |
123 | | - } |
| 122 | + if( !mediaWiki.config.get( name )){ |
| 123 | + mw.setConfig( name, value ); |
124 | 124 | return ; |
125 | 125 | } |
126 | | - // else do a normal setConfig |
127 | | - mw.setConfig( name, value ); |
| 126 | + if( typeof mediaWiki.config.get( name ) == 'object' ){ |
| 127 | + mw.setConfig( name, $.extend( mediaWiki.config.get( name ), value ) ); |
| 128 | + } |
128 | 129 | }; |
129 | 130 | |
| 131 | + |
130 | 132 | /** |
| 133 | + * gM ( get Message ) in js2 conflated jQuery return type with string return type |
| 134 | + * Do a legacy check for input parameters and 'do the right thing' |
| 135 | + */ |
| 136 | + window.gM = function( key, parameters ){ |
| 137 | + |
| 138 | + }; |
| 139 | + |
| 140 | + /** |
131 | 141 | * Utility Functions |
132 | 142 | * |
133 | 143 | * TOOD some of these utility functions are used in the upload Wizard, break them out into |
Index: branches/MwEmbedStandAloneRL1_17/MwEmbedStandAlone/resources/mediawiki/mediawiki.js |
— | — | @@ -576,8 +576,8 @@ |
577 | 577 | var jobs = []; |
578 | 578 | // Flag indicating that requests should be suspended |
579 | 579 | var suspended = true; |
580 | | - // Flag inidicating that document ready has occured |
581 | | - var ready = false; |
| 580 | + // Flag inidicating that document is ready |
| 581 | + var documentReady = false; |
582 | 582 | // Marker element for adding dynamic styles |
583 | 583 | var $marker = $( 'head meta[name=ResourceLoaderDynamicStyles]' ); |
584 | 584 | |
— | — | @@ -614,7 +614,7 @@ |
615 | 615 | pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z' |
616 | 616 | ].join( '' ); |
617 | 617 | } |
618 | | - |
| 618 | + |
619 | 619 | /** |
620 | 620 | * Recursively resolves dependencies and detects circular references |
621 | 621 | */ |
— | — | @@ -853,6 +853,47 @@ |
854 | 854 | } |
855 | 855 | return sorted; |
856 | 856 | } |
| 857 | + |
| 858 | + /** |
| 859 | + * Appends a set of scripts to the dom |
| 860 | + * @param url {Mixed} Array or single url string |
| 861 | + */ |
| 862 | + function appendScripts( url, callback, error ){ |
| 863 | + if( ! url ) |
| 864 | + return ; |
| 865 | + if( $.isArray( url ) ){ |
| 866 | + var requestCount = 0; |
| 867 | + $.each( url, function(inx, singleUrl ){ |
| 868 | + requestCount++; |
| 869 | + appendScripts( singleUrl, function(){ |
| 870 | + requestCount--; |
| 871 | + if( requestCount == 0 && typeof callback == 'function') |
| 872 | + callback(); |
| 873 | + }); |
| 874 | + }); |
| 875 | + return ; |
| 876 | + } |
| 877 | + |
| 878 | + // Load asynchronously after document ready |
| 879 | + if ( documentReady ) { |
| 880 | + // Load without jQuery to avoid $.globalEval issue |
| 881 | + var script = document.createElement( "script" ); |
| 882 | + script.setAttribute( 'src', url ); |
| 883 | + script.setAttribute( 'type', 'text/javascript' ); |
| 884 | + if( callback ){ |
| 885 | + script.onload = callback; |
| 886 | + } |
| 887 | + document.getElementsByTagName("body")[ 0 ].appendChild( script ); |
| 888 | + } else { |
| 889 | + var script = mediaWiki.html.element( 'script', |
| 890 | + { type: 'text/javascript', src: url }, '' ); |
| 891 | + document.write( script ); |
| 892 | + // document.write blocks script execution so we can directly run the callback: |
| 893 | + if( callback ){ |
| 894 | + callback(); |
| 895 | + } |
| 896 | + } |
| 897 | + } |
857 | 898 | |
858 | 899 | /* Public Methods */ |
859 | 900 | |
— | — | @@ -917,23 +958,16 @@ |
918 | 959 | // include modules which are already loaded |
919 | 960 | batch = []; |
920 | 961 | // Asynchronously append a script tag to the end of the body |
921 | | - function request() { |
922 | | - var html = ''; |
| 962 | + function getRequestUrs() { |
| 963 | + var urls = []; |
923 | 964 | for ( var r = 0; r < requests.length; r++ ) { |
924 | 965 | requests[r] = sortQuery( requests[r] ); |
925 | 966 | // Build out the HTML |
926 | | - var src = mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] ); |
927 | | - html += mediaWiki.html.element( 'script', |
928 | | - { type: 'text/javascript', src: src }, '' ); |
| 967 | + urls.push( mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] ) ); |
929 | 968 | } |
930 | | - return html; |
| 969 | + return urls; |
931 | 970 | } |
932 | | - // Load asynchronously after doumument ready |
933 | | - if ( ready ) { |
934 | | - setTimeout( function() { $( 'body' ).append( request() ); }, 0 ); |
935 | | - } else { |
936 | | - document.write( request() ); |
937 | | - } |
| 971 | + appendScripts( getRequestUrs() ); |
938 | 972 | } |
939 | 973 | }; |
940 | 974 | |
— | — | @@ -1097,13 +1131,7 @@ |
1098 | 1132 | .attr( 'href', modules ) ); |
1099 | 1133 | return true; |
1100 | 1134 | } else if ( type === 'text/javascript' || typeof type === 'undefined' ) { |
1101 | | - var script = mediaWiki.html.element( 'script', |
1102 | | - { type: 'text/javascript', src: modules }, '' ); |
1103 | | - if ( ready ) { |
1104 | | - $( 'body' ).append( script ); |
1105 | | - } else { |
1106 | | - document.write( script ); |
1107 | | - } |
| 1135 | + appendScripts( modules ); |
1108 | 1136 | return true; |
1109 | 1137 | } |
1110 | 1138 | // Unknown type |
— | — | @@ -1170,7 +1198,7 @@ |
1171 | 1199 | |
1172 | 1200 | /* Cache document ready status */ |
1173 | 1201 | |
1174 | | - $(document).ready( function() { ready = true; } ); |
| 1202 | + $(document).ready( function() { documentReady = true; } ); |
1175 | 1203 | } )(); |
1176 | 1204 | |
1177 | 1205 | /** HTML construction helper functions */ |