summaryrefslogtreecommitdiff
path: root/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js')
-rw-r--r--extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js2760
1 files changed, 2760 insertions, 0 deletions
diff --git a/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js
new file mode 100644
index 00000000..131302a2
--- /dev/null
+++ b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js
@@ -0,0 +1,2760 @@
+/**
+* embedPlayer is the base class for html5 video tag javascript abstraction library
+* embedPlayer include a few subclasses:
+*
+* mediaPlayer Media player embed system ie: java, vlc or native.
+* mediaElement Represents source media elements
+* mw.PlayerControlBuilder Handles skinning of the player controls
+*/
+( function( mw, $ ) {"use strict";
+ /**
+ * Merge in the default video attributes supported by embedPlayer:
+ */
+ mw.mergeConfig('EmbedPlayer.Attributes', {
+ /*
+ * Base html element attributes:
+ */
+
+ // id: Auto-populated if unset
+ "id" : null,
+
+ // Width: alternate to "style" to set player width
+ "width" : null,
+
+ // Height: alternative to "style" to set player height
+ "height" : null,
+
+ /*
+ * Base html5 video element attributes / states also see:
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
+ */
+
+ // Media src URI, can be relative or absolute URI
+ "src" : null,
+
+ // Poster attribute for displaying a place holder image before loading
+ // or playing the video
+ "poster" : null,
+
+ // Autoplay if the media should start playing
+ "autoplay" : false,
+
+ // Loop attribute if the media should repeat on complete
+ "loop" : false,
+
+ // If the player controls should be displayed
+ "controls" : true,
+
+ // Video starts "paused"
+ "paused" : true,
+
+ // ReadyState an attribute informs clients of video loading state:
+ // see: http://www.whatwg.org/specs/web-apps/current-work/#readystate
+ "readyState" : 0,
+
+ // Loading state of the video element
+ "networkState" : 0,
+
+ // Current playback position
+ "currentTime" : 0,
+
+ // Previous player set time
+ // Lets javascript use $('#videoId')[0].currentTime = newTime;
+ "previousTime" : 0,
+
+ // Previous player set volume
+ // Lets javascript use $('#videoId')[0].volume = newVolume;
+ "previousVolume" : 1,
+
+ // Initial player volume:
+ "volume" : 0.75,
+
+ // Caches the volume before a mute toggle
+ "preMuteVolume" : 0.75,
+
+ // Media duration: Value is populated via
+ // custom data-durationhint attribute or via the media file once its played
+ "duration" : null,
+
+ // A hint to the duration of the media file so that duration
+ // can be displayed in the player without loading the media file
+ 'data-durationhint': null,
+
+ // to disable menu or timedText for a given embed
+ 'data-disablecontrols': null,
+
+ // Also support direct durationHint attribute ( backwards compatibly )
+ // @deprecated please use data-durationhint instead.
+ 'durationHint' : null,
+
+ // Mute state
+ "muted" : false,
+
+ /**
+ * Custom attributes for embedPlayer player: (not part of the html5
+ * video spec)
+ */
+
+ // Default video aspect ratio
+ 'videoAspect' : '4:3',
+
+ // Start time of the clip
+ "start" : 0,
+
+ // End time of the clip
+ "end" : null,
+
+ // If the player controls should be overlaid
+ // ( Global default via config EmbedPlayer.OverlayControls in module
+ // loader.js)
+ "overlaycontrols" : true,
+
+ // Attribute to use 'native' controls
+ "usenativecontrols" : false,
+
+ // If the player should include an attribution button:
+ 'attributionbutton' : true,
+
+ // A player error object (Includes title and message)
+ // * Used to display an error instead of a play button
+ // * The full player api available
+ 'playerError' : {},
+
+ // A flag to hide the player gui and disable autoplay
+ // * Used for empty players or a player where you want to dynamically set sources, then play.
+ // * The player API remains active.
+ 'data-blockPlayerDisplay': null,
+
+ // If serving an ogg_chop segment use this to offset the presentation time
+ // ( for some plugins that use ogg page time rather than presentation time )
+ "startOffset" : 0,
+
+ // If the download link should be shown
+ "downloadLink" : true,
+
+ // Content type of the media
+ "type" : null
+
+ } );
+
+
+ /**
+ * The base source attribute checks also see:
+ * http://dev.w3.org/html5/spec/Overview.html#the-source-element
+ */
+ mw.mergeConfig( 'EmbedPlayer.SourceAttributes', [
+ // source id
+ 'id',
+
+ // media url
+ 'src',
+
+ // Title string for the source asset
+ 'title',
+
+ // The html5 spec uses label instead of 'title' for naming sources
+ 'label',
+
+ // boolean if we support temporal url requests on the source media
+ 'URLTimeEncoding',
+
+ // Media has a startOffset ( used for plugins that
+ // display ogg page time rather than presentation time
+ 'startOffset',
+
+ // Media start time
+ 'start',
+
+ // Media end time
+ 'end',
+
+ // If the source is the default source
+ 'default',
+
+ // Title of the source
+ 'title',
+
+ // titleKey ( used for api lookups TODO move into mediaWiki specific support
+ 'titleKey'
+ ] );
+
+
+ /**
+ * Base embedPlayer object
+ *
+ * @param {Element}
+ * element, the element used for initialization.
+ * @constructor
+ */
+ mw.EmbedPlayer = function( element ) {
+ return this.init( element );
+ };
+
+ mw.EmbedPlayer.prototype = {
+
+ // The mediaElement object containing all mediaSource objects
+ 'mediaElement' : null,
+
+ // Object that describes the supported feature set of the underling plugin /
+ // Support list is described in PlayerControlBuilder components
+ 'supports': { },
+
+ // If the player is done loading ( does not guarantee playability )
+ // for example if there is an error playerReadyFlag is still set to true once
+ // no more loading is to be done
+ 'playerReadyFlag' : false,
+
+ // Stores the loading errors
+ 'loadError' : false,
+
+ // Thumbnail updating flag ( to avoid rewriting an thumbnail thats already
+ // being updated)
+ 'thumbnailUpdatingFlag' : false,
+
+ // Stopped state flag
+ 'stopped' : true,
+
+ // Local variable to hold CMML meeta data about the current clip
+ // for more on CMML see: http://wiki.xiph.org/CMML
+ 'cmmlData': null,
+
+ // Stores the seek time request, Updated by the seek function
+ 'serverSeekTime' : 0,
+
+ // If the embedPlayer is current 'seeking'
+ 'seeking' : false,
+
+ // Percent of the clip buffered:
+ 'bufferedPercent' : 0,
+
+ // Holds the timer interval function
+ 'monitorTimerId' : null,
+
+ // Buffer flags
+ 'bufferStartFlag' : false,
+ 'bufferEndFlag' : false,
+
+ // For supporting media fragments stores the play end time
+ 'pauseTime' : null,
+
+ // On done playing
+ 'donePlayingCount' : 0
+ ,
+ // if player events should be Propagated
+ '_propagateEvents': true,
+
+ // If the onDone interface should be displayed
+ 'onDoneInterfaceFlag': true,
+
+ // if we should check for a loading spinner in the monitor function:
+ '_checkHideSpinner' : false,
+
+ // If pause play controls click controls should be active:
+ '_playContorls' : true,
+
+ // If player should be displayed (in some caused like audio, we don't need the player to be visible
+ 'displayPlayer': true,
+
+ // Widget loaded should only fire once
+ 'widgetLoaded': false,
+
+ /**
+ * embedPlayer
+ *
+ * @constructor
+ *
+ * @param {Element}
+ * element DOM element that we are building the player interface for.
+ */
+ init: function( element ) {
+ var _this = this;
+ mw.log('EmbedPlayer: initEmbedPlayer: ' + $(element).width() );
+
+ var playerAttributes = mw.config.get( 'EmbedPlayer.Attributes' );
+
+ // Store the rewrite element tag type
+ this.rewriteElementTagName = element.tagName.toLowerCase();
+
+ this.noPlayerFallbackHTML = $( element ).html();
+
+ // Setup the player Interface from supported attributes:
+ for ( var attr in playerAttributes ) {
+ // We can't use $(element).attr( attr ) because we have to check for boolean attributes:
+ if ( element.getAttribute( attr ) != null ) {
+ // boolean attributes
+ if( element.getAttribute( attr ) == '' ){
+ this[ attr ] = true;
+ } else {
+ this[ attr ] = element.getAttribute( attr );
+ }
+ } else {
+ this[attr] = playerAttributes[attr];
+ }
+ // string -> boolean
+ if( this[ attr ] == "false" ) this[attr] = false;
+ if( this[ attr ] == "true" ) this[attr] = true;
+ }
+
+ // Hide "controls" if using native player controls:
+ if( this.useNativePlayerControls() ){
+ _this.controls = true;
+ }
+ // Set the skin name from the class
+ var sn = $(element).attr( 'class' );
+
+ if ( sn && sn != '' ) {
+ var skinList = mw.config.get('EmbedPlayer.SkinList');
+ for ( var n = 0; n < skinList.length; n++ ) {
+ if ( sn.indexOf( skinList[n].toLowerCase() ) !== -1 ) {
+ this.skinName = skinList[ n ];
+ }
+ }
+ }
+ // Set the default skin if unset:
+ if ( !this.skinName ) {
+ this.skinName = mw.config.get( 'EmbedPlayer.DefaultSkin' );
+ }
+
+ // Support custom monitorRate Attribute ( if not use default )
+ if( !this.monitorRate ){
+ this.monitorRate = mw.config.get( 'EmbedPlayer.MonitorRate' );
+ }
+
+ // Make sure startOffset is cast as an float:
+ if ( this.startOffset && this.startOffset.split( ':' ).length >= 2 ) {
+ this.startOffset = parseFloat( mw.npt2seconds( this.startOffset ) );
+ }
+
+ // Make sure offset is in float:
+ this.startOffset = parseFloat( this.startOffset );
+
+ // Set the source duration
+ if ( $( element ).attr( 'duration' ) ) {
+ _this.duration = $( element ).attr( 'duration' );
+ }
+ // Add durationHint property form data-durationhint:
+ if( _this['data-durationhint']){
+ _this.durationHint = _this['data-durationhint'];
+ }
+ // Update duration from provided durationHint
+ if ( _this.durationHint && ! _this.duration){
+ _this.duration = mw.npt2seconds( _this.durationHint );
+ }
+
+ // Make sure duration is a float:
+ this.duration = parseFloat( this.duration );
+ mw.log( 'EmbedPlayer::init:' + this.id + " duration is: " + this.duration );
+
+ // Add disablecontrols property form data-disablecontrols:
+ if( _this['data-disablecontrols'] ){
+ _this.disablecontrols = _this['data-disablecontrols'];
+ }
+
+ // Set the playerElementId id
+ this.pid = 'pid_' + this.id;
+
+ // Add the mediaElement object with the elements sources:
+ this.mediaElement = new mw.MediaElement( element );
+
+ this.bindHelper( 'updateLayout', function() {
+ _this.updateLayout();
+ });
+ },
+ /**
+ * Bind helpers to help iOS retain bind context
+ *
+ * Yes, iOS will fail when you run $( embedPlayer ).bind()
+ * but "work" when you run embedPlayer.bind() if the script urls are from diffrent "resources"
+ */
+ bindHelper: function( name, callback ){
+ $( this ).bind( name, callback );
+ return this;
+ },
+ unbindHelper: function( bindName ){
+ if( bindName ) {
+ $( this ).unbind( bindName );
+ }
+ return this;
+ },
+ triggerQueueCallback: function( name, callback ){
+ $( this ).triggerQueueCallback( name, callback );
+ },
+ triggerHelper: function( name, obj ){
+ try{
+ $( this ).trigger( name, obj );
+ } catch( e ){
+ // ignore try catch calls
+ // mw.log( "EmbedPlayer:: possible error in trgger: " + name + " " + e.toString() );
+ }
+ },
+ /**
+ * Stop events from Propagation and blocks interface updates and trigger events.
+ * @return
+ */
+ stopEventPropagation: function(){
+ mw.log("EmbedPlayer:: stopEventPropagation");
+ this.stopMonitor();
+ this._propagateEvents = false;
+ },
+
+ /**
+ * Restores event propagation
+ * @return
+ */
+ restoreEventPropagation: function(){
+ mw.log("EmbedPlayer:: restoreEventPropagation");
+ this._propagateEvents = true;
+ this.startMonitor();
+ },
+
+ /**
+ * Enables the play controls ( for example when an ad is done )
+ */
+ enablePlayControls: function(){
+ mw.log("EmbedPlayer:: enablePlayControls" );
+ if( this.useNativePlayerControls() ){
+ return ;
+ }
+ this._playContorls = true;
+ // re-enable hover:
+ this.getInterface().find( '.play-btn' )
+ .buttonHover()
+ .css('cursor', 'pointer' );
+
+ this.controlBuilder.enableSeekBar();
+ /*
+ * We should pass an array with enabled components, and the controlBuilder will listen
+ * to this event and handle the layout changes. we should not call to this.controlBuilder inside embedPlayer.
+ * [ 'playButton', 'seekBar' ]
+ */
+ $( this ).trigger( 'onEnableInterfaceComponents');
+ },
+
+ /**
+ * Disables play controls, for example when an ad is playing back
+ */
+ disablePlayControls: function(){
+ if( this.useNativePlayerControls() ){
+ return ;
+ }
+ this._playContorls = false;
+ // turn off hover:
+ this.getInterface().find( '.play-btn' )
+ .unbind('mouseenter mouseleave')
+ .css('cursor', 'default' );
+
+ this.controlBuilder.disableSeekBar();
+ /**
+ * We should pass an array with disabled components, and the controlBuilder will listen
+ * to this event and handle the layout changes. we should not call to this.controlBuilder inside embedPlayer.
+ * [ 'playButton', 'seekBar' ]
+ */
+ $( this ).trigger( 'onDisableInterfaceComponents');
+ },
+
+ /**
+ * For plugin-players to update supported features
+ */
+ updateFeatureSupport: function(){
+ $( this ).trigger('updateFeatureSupportEvent', this.supports );
+ return ;
+ },
+ /**
+ * Apply Intrinsic Aspect ratio of a given image to a poster image layout
+ */
+ applyIntrinsicAspect: function(){
+ var $this = $( this );
+ // Check if a image thumbnail is present:
+ if( this.getInterface().find('.playerPoster').length ){
+ var img = this.getInterface().find('.playerPoster')[0];
+ var pHeight = $this.height();
+ // Check for intrinsic width and maintain aspect ratio
+ if( img.naturalWidth && img.naturalHeight ){
+ var pWidth = parseInt( img.naturalWidth / img.naturalHeight * pHeight);
+ if( pWidth > $this.width() ){
+ pWidth = $this.width();
+ pHeight = parseInt( img.naturalHeight / img.naturalWidth * pWidth );
+ }
+ $( img ).css({
+ 'height' : pHeight + 'px',
+ 'width': pWidth + 'px',
+ 'left': ( ( $this.width() - pWidth ) * .5 ) + 'px',
+ 'top': ( ( $this.height() - pHeight ) * .5 ) + 'px',
+ 'position' : 'absolute'
+ });
+ }
+ }
+ },
+ /**
+ * Set the width & height from css style attribute, element attribute, or by
+ * default value if no css or attribute is provided set a callback to
+ * resize.
+ *
+ * Updates this.width & this.height
+ *
+ * @param {Element}
+ * element Source element to grab size from
+ */
+ loadPlayerSize: function( element ) {
+ // check for direct element attribute:
+ this.height = element.height > 0 ? element.height + '' : $(element).css( 'height' );
+ this.width = element.width > 0 ? element.width + '' : $(element).css( 'width' );
+
+ // Special check for chrome 100% with re-mapping to 32px
+ // Video embed at 32x32 will have to wait for intrinsic video size later on
+ if( this.height == '32px' || this.height =='32px' ){
+ this.width = '100%';
+ this.height = '100%';
+ }
+ mw.log('EmbedPlayer::loadPlayerSize: css size:' + this.width + ' h: ' + this.height);
+
+ // Set to parent size ( resize events will cause player size updates)
+ if( this.height.indexOf('100%') != -1 || this.width.indexOf('100%') != -1 ){
+ var $relativeParent = $(element).parents().filter(function() {
+ // reduce to only relative position or "body" elements
+ return $( this ).is('body') || $( this ).css('position') == 'relative';
+ }).slice(0,1); // grab only the "first"
+ this.width = $relativeParent.width();
+ this.height = $relativeParent.height();
+ }
+ // Make sure height and width are a number
+ this.height = parseInt( this.height );
+ this.width = parseInt( this.width );
+
+ // Set via attribute if CSS is zero or NaN and we have an attribute value:
+ this.height = ( this.height==0 || isNaN( this.height )
+ && $(element).attr( 'height' ) ) ?
+ parseInt( $(element).attr( 'height' ) ): this.height;
+ this.width = ( this.width == 0 || isNaN( this.width )
+ && $(element).attr( 'width' ) )?
+ parseInt( $(element).attr( 'width' ) ): this.width;
+
+
+ // Special case for audio
+
+ // Firefox sets audio height to "0px" while webkit uses 32px .. force zero:
+ if( this.isAudio() && this.height == '32' ) {
+ this.height = 20;
+ }
+
+ // Use default aspect ration to get height or width ( if rewriting a non-audio player )
+ if( this.isAudio() && this.videoAspect ) {
+ var aspect = this.videoAspect.split( ':' );
+ if( this.height && !this.width ) {
+ this.width = parseInt( this.height * ( aspect[0] / aspect[1] ) );
+ }
+ if( this.width && !this.height ) {
+ var apectRatio = ( aspect[1] / aspect[0] );
+ this.height = parseInt( this.width * ( aspect[1] / aspect[0] ) );
+ }
+ }
+
+ // On load sometimes attr is temporally -1 as we don't have video metadata yet.
+ // or in IE we get NaN for width height
+ //
+ // NOTE: browsers that do support height width should set "waitForMeta" flag in addElement
+ if( ( isNaN( this.height )|| isNaN( this.width ) ) ||
+ ( this.height == -1 || this.width == -1 ) ||
+ // Check for firefox defaults
+ // Note: ideally firefox would not do random guesses at css
+ // values
+ ( (this.height == 150 || this.height == 64 ) && this.width == 300 )
+ ) {
+ var defaultSize = mw.config.get( 'EmbedPlayer.DefaultSize' ).split( 'x' );
+ if( isNaN( this.width ) ){
+ this.width = defaultSize[0];
+ }
+
+ // Special height default for audio tag ( if not set )
+ if( this.isAudio() ) {
+ this.height = 20;
+ }else{
+ this.height = defaultSize[1];
+ }
+ }
+ },
+
+ /**
+ * Get the player pixel width not including controls
+ *
+ * @return {Number} pixel height of the video
+ */
+ getPlayerWidth: function() {
+ var profile = $.client.profile();
+
+ if ( profile.name === 'firefox' && profile.versionNumber < 2 ) {
+ return ( $( this ).parent().parent().width() );
+ }
+ return $( this ).width();
+ },
+
+ /**
+ * Get the player pixel height not including controls
+ *
+ * @return {Number} pixel height of the video
+ */
+ getPlayerHeight: function() {
+ return $( this ).height();
+ },
+
+ /**
+ * Check player for sources. If we need to get media sources form an
+ * external file that request is issued here
+ */
+ checkPlayerSources: function() {
+ mw.log( 'EmbedPlayer::checkPlayerSources: ' + this.id );
+ var _this = this;
+ // Allow plugins to listen to a preCheckPlayerSources ( for registering the source loading point )
+ $( _this ).trigger( 'preCheckPlayerSources' );
+
+ // Allow plugins to block on sources lookup ( cases where we just have an api key for example )
+ $( _this ).triggerQueueCallback( 'checkPlayerSourcesEvent', function(){
+ _this.setupSourcePlayer();
+ });
+ },
+
+ /**
+ * Get text tracks from the mediaElement
+ */
+ getTextTracks: function(){
+ if( !this.mediaElement ){
+ return [];
+ }
+ return this.mediaElement.getTextTracks();
+ },
+ /**
+ * Empty the player sources
+ */
+ emptySources: function(){
+ if( this.mediaElement ){
+ this.mediaElement.sources = [];
+ this.mediaElement.selectedSource = null;
+ }
+ // setup pointer to old source:
+ this.prevPlayer = this.selectedPlayer;
+ // don't null out the selected player on empty sources
+ //this.selectedPlayer =null;
+ },
+
+ /**
+ * Switch and play a video source
+ *
+ * Checks if the target source is the same playback mode and does player switch if needed.
+ * and calls playerSwitchSource
+ */
+ switchPlaySource: function( source, switchCallback, doneCallback ){
+ var _this = this;
+ var targetPlayer = mw.EmbedTypes.getMediaPlayers().defaultPlayer( source.mimeType ) ;
+ if( targetPlayer.library != this.selectedPlayer.library ){
+ this.selectedPlayer = targetPlayer;
+ this.updatePlaybackInterface( function(){
+ _this.playerSwitchSource( source, switchCallback, doneCallback );
+ });
+ } else {
+ // Call the player switch directly:
+ _this.playerSwitchSource( source, switchCallback, doneCallback );
+ }
+ },
+ /**
+ * abstract function player interface must support actual source switch
+ */
+ playerSwitchSource: function( source, switchCallback, doneCallback ){
+ mw.log( "Error player interface must support actual source switch");
+ },
+
+ /**
+ * Set up the select source player
+ *
+ * issues autoSelectSource call
+ *
+ * Sets load error if no source is playable
+ */
+ setupSourcePlayer: function() {
+ var _this = this;
+ mw.log("EmbedPlayer::setupSourcePlayer: " + this.id + ' sources: ' + this.mediaElement.sources.length );
+
+ // Check for source replace configuration:
+ if( mw.config.get('EmbedPlayer.ReplaceSources' ) ){
+ this.emptySources();
+ $.each( mw.config.get('EmbedPlayer.ReplaceSources' ), function( inx, source ){
+ _this.mediaElement.tryAddSource( source );
+ });
+ }
+
+ // Autoseletct the media source
+ this.mediaElement.autoSelectSource();
+
+ // Auto select player based on default order
+ if( this.mediaElement.selectedSource ){
+ this.selectedPlayer = mw.EmbedTypes.getMediaPlayers().defaultPlayer( this.mediaElement.selectedSource.mimeType );
+ // Check if we need to switch player rendering libraries:
+ if ( this.selectedPlayer && ( !this.prevPlayer || this.prevPlayer.library != this.selectedPlayer.library ) ) {
+ // Inherit the playback system of the selected player:
+ this.updatePlaybackInterface();
+ return ;
+ }
+ }
+
+ // Check if no player is selected
+ if( !this.selectedPlayer || !this.mediaElement.selectedSource ){
+ this.showPlayerError();
+ mw.log( "EmbedPlayer:: setupSourcePlayer > player ready ( but with errors ) ");
+ } else {
+ // Trigger layout ready event
+ $( this ).trigger( 'layoutReady' );
+ // Show the interface:
+ this.getInterface().find( '.control-bar').show();
+ this.addLargePlayBtn();
+ }
+ // We still do the playerReady sequence on errors to provide an api
+ // and player error events
+ this.playerReadyFlag = true;
+ // trigger the player ready event;
+ $( this ).trigger( 'playerReady' );
+ this.triggerWidgetLoaded();
+ },
+
+ /**
+ * Updates the player interface
+ *
+ * Loads and inherit methods from the selected player interface.
+ *
+ * @param {Function}
+ * callback Function to be called once playback-system has been
+ * inherited
+ */
+ updatePlaybackInterface: function( callback ) {
+ var _this = this;
+ mw.log( "EmbedPlayer::updatePlaybackInterface: duration is: " + this.getDuration() + ' playerId: ' + this.id );
+ // Clear out any non-base embedObj methods:
+ if ( this.instanceOf ) {
+ // Update the prev instance var used for swiching interfaces to know the previous instance.
+ $( this ).data( 'previousInstanceOf', this.instanceOf );
+ var tmpObj = window['mw.EmbedPlayer' + this.instanceOf ];
+ for ( var i in tmpObj ) {
+ // Restore parent into local location
+ if ( typeof this[ 'parent_' + i ] != 'undefined' ) {
+ this[i] = this[ 'parent_' + i];
+ } else {
+ this[i] = null;
+ }
+ }
+ }
+ // Set up the new embedObj
+ mw.log( 'EmbedPlayer::updatePlaybackInterface: embedding with ' + this.selectedPlayer.library );
+ this.selectedPlayer.load( function() {
+ _this.updateLoadedPlayerInterface( callback );
+ });
+ },
+ /**
+ * Update a loaded player interface by setting local methods to the
+ * updated player prototype methods
+ *
+ * @parma {function}
+ * callback function called once player has been loaded
+ */
+ updateLoadedPlayerInterface: function( callback ){
+ var _this = this;
+ mw.log( 'EmbedPlayer::updateLoadedPlayerInterface ' + _this.selectedPlayer.library + " player loaded for " + _this.id );
+
+ // Get embed library player Interface
+ var playerInterface = mw[ 'EmbedPlayer' + _this.selectedPlayer.library ];
+
+ // Build the player interface ( if the interface includes an init )
+ if( playerInterface.init ){
+ playerInterface.init();
+ }
+
+ for ( var method in playerInterface ) {
+ if ( typeof _this[method] != 'undefined' && !_this['parent_' + method] ) {
+ _this['parent_' + method] = _this[method];
+ }
+ _this[ method ] = playerInterface[ method ];
+ }
+ // Update feature support
+ _this.updateFeatureSupport();
+ // Update duration
+ _this.getDuration();
+ // show player inline
+ _this.showPlayer();
+ // Run the callback if provided
+ if ( callback && $.isFunction( callback ) ){
+ callback();
+ }
+ },
+
+ /**
+ * Select a player playback system
+ *
+ * @param {Object}
+ * player Player playback system to be selected player playback
+ * system include vlc, native, java etc.
+ */
+ selectPlayer: function( player ) {
+ mw.log("EmbedPlayer:: selectPlayer " + player.id );
+ var _this = this;
+ if ( this.selectedPlayer.id != player.id ) {
+ this.selectedPlayer = player;
+ this.updatePlaybackInterface( function(){
+ // Hide / remove track container
+ _this.getInterface().find( '.track' ).remove();
+ // We have to re-bind hoverIntent ( has to happen in this scope )
+ if( !_this.useNativePlayerControls() && _this.controls && _this.controlBuilder.isOverlayControls() ){
+ _this.controlBuilder.showControlBar();
+ _this.getInterface().hoverIntent({
+ 'sensitivity': 4,
+ 'timeout' : 2000,
+ 'over' : function(){
+ _this.controlBuilder.showControlBar();
+ },
+ 'out' : function(){
+ _this.controlBuilder.hideControlBar();
+ }
+ });
+ }
+ });
+ }
+ },
+
+ /**
+ * Get a time range from the media start and end time
+ *
+ * @return startNpt and endNpt time if present
+ */
+ getTimeRange: function() {
+ var end_time = ( this.controlBuilder.longTimeDisp )? '/' + mw.seconds2npt( this.getDuration() ) : '';
+ var defaultTimeRange = '0:00' + end_time;
+ if ( !this.mediaElement ){
+ return defaultTimeRange;
+ }
+ if ( !this.mediaElement.selectedSource ){
+ return defaultTimeRange;
+ }
+ if ( !this.mediaElement.selectedSource.endNpt ){
+ return defaultTimeRange;
+ }
+ return this.mediaElement.selectedSource.startNpt + this.mediaElement.selectedSource.endNpt;
+ },
+
+ /**
+ * Get the duration of the embed player
+ */
+ getDuration: function() {
+ if ( isNaN(this.duration) && this.mediaElement && this.mediaElement.selectedSource &&
+ typeof this.mediaElement.selectedSource.durationHint != 'undefined' ){
+ this.duration = this.mediaElement.selectedSource.durationHint;
+ }
+ return this.duration;
+ },
+
+ /**
+ * Get the player height
+ */
+ getHeight: function() {
+ return this.getInterface().height();
+ },
+
+ /**
+ * Get the player width
+ */
+ getWidth: function(){
+ return this.getInterface().width();
+ },
+
+ /**
+ * Check if the selected source is an audio element:
+ */
+ isAudio: function(){
+ return ( this.rewriteElementTagName == 'audio'
+ ||
+ ( this.mediaElement && this.mediaElement.selectedSource && this.mediaElement.selectedSource.mimeType.indexOf('audio/') !== -1 )
+ );
+ },
+
+ /**
+ * Get the plugin embed html ( should be implemented by embed player interface )
+ */
+ embedPlayerHTML: function() {
+ return 'Error: function embedPlayerHTML should be implemented by embed player interface ';
+ },
+
+ /**
+ * Seek function ( should be implemented by embedPlayer interface
+ * playerNative, playerKplayer etc. ) embedPlayer seek only handles URL
+ * time seeks
+ * @param {Float}
+ * percent of the video total length to seek to
+ */
+ seek: function( percent ) {
+ var _this = this;
+ this.seeking = true;
+ // Trigger preSeek event for plugins that want to store pre seek conditions.
+ $( this ).trigger( 'preSeek', percent );
+
+ // Do argument checking:
+ if( percent < 0 ){
+ percent = 0;
+ }
+
+ if( percent > 1 ){
+ percent = 1;
+ }
+ // set the playhead to the target position
+ this.updatePlayHead( percent );
+
+ // See if we should do a server side seek ( player independent )
+ if ( this.supportsURLTimeEncoding() ) {
+ mw.log( 'EmbedPlayer::seek:: updated serverSeekTime: ' + mw.seconds2npt ( this.serverSeekTime ) +
+ ' currentTime: ' + _this.currentTime );
+ // make sure we need to seek:
+ if( _this.currentTime == _this.serverSeekTime ){
+ return ;
+ }
+
+ this.stop();
+ this.didSeekJump = true;
+ // Make sure this.serverSeekTime is up-to-date:
+ this.serverSeekTime = mw.npt2seconds( this.startNpt ) + parseFloat( percent * this.getDuration() );
+ }
+ // Run the onSeeking interface update
+ // NOTE controlBuilder should really bind to html5 events rather
+ // than explicitly calling it or inheriting stuff.
+ this.controlBuilder.onSeek();
+ },
+
+ /**
+ * Seeks to the requested time and issues a callback when ready (should be
+ * overwritten by client that supports frame serving)
+ */
+ setCurrentTime: function( time, callback ) {
+ mw.log( 'Error: EmbedPlayer, setCurrentTime not overriden' );
+ if( $.isFunction( callback ) ){
+ callback();
+ }
+ },
+
+ /**
+ * On clip done action. Called once a clip is done playing
+ * TODO clean up end sequence flow
+ */
+ triggeredEndDone: false,
+ postSequence: false,
+ onClipDone: function() {
+ var _this = this;
+ // Don't run onclipdone if _propagateEvents is off
+ if( !_this._propagateEvents ){
+ return ;
+ }
+ mw.log( 'EmbedPlayer::onClipDone: propagate:' + _this._propagateEvents + ' id:' + this.id + ' doneCount:' + this.donePlayingCount + ' stop state:' +this.isStopped() );
+ // Only run stopped once:
+ if( !this.isStopped() ){
+ // set the "stopped" flag:
+ this.stopped = true;
+
+ // Show the control bar:
+ this.controlBuilder.showControlBar();
+
+ // TOOD we should improve the end event flow
+ // First end event for ads or current clip ended bindings
+ if( ! this.onDoneInterfaceFlag ){
+ this.stopEventPropagation();
+ }
+
+ mw.log("EmbedPlayer:: trigger: ended ( inteface continue pre-check: " + this.onDoneInterfaceFlag + ' )' );
+ $( this ).trigger( 'ended' );
+ mw.log("EmbedPlayer::onClipDone:Trigged ended, continue? " + this.onDoneInterfaceFlag);
+
+
+ if( ! this.onDoneInterfaceFlag ){
+ // Restore events if we are not running the interface done actions
+ this.restoreEventPropagation();
+ return ;
+ }
+
+ // A secondary end event for playlist and clip sequence endings
+ if( this.onDoneInterfaceFlag ){
+ // We trigger two end events to match KDP and ensure playbackComplete always comes before playerPlayEnd
+ // in content ends.
+ mw.log("EmbedPlayer:: trigger: playbackComplete");
+ $( this ).trigger( 'playbackComplete' );
+ // now trigger postEnd for( playerPlayEnd )
+ mw.log("EmbedPlayer:: trigger: postEnded");
+ $( this ).trigger( 'postEnded' );
+ }
+ // if the ended event did not trigger more timeline actions run the actual stop:
+ if( this.onDoneInterfaceFlag ){
+ mw.log("EmbedPlayer::onDoneInterfaceFlag=true do interface done");
+ // Prevent the native "onPlay" event from propagating that happens when we rewind:
+ this.stopEventPropagation();
+
+ // Update the clip done playing count ( for keeping track of replays )
+ _this.donePlayingCount ++;
+
+ // Rewind the player to the start:
+ // NOTE: Setting to 0 causes lags on iPad when replaying, thus setting to 0.01
+ this.setCurrentTime(0.01, function(){
+
+ // Set to stopped state:
+ _this.stop();
+
+ // Restore events after we rewind the player
+ _this.restoreEventPropagation();
+
+ // Check if we have the "loop" property set
+ if( _this.loop ) {
+ _this.stopped = false;
+ _this.play();
+ return;
+ } else {
+ // make sure we are in a paused state.
+ _this.pause();
+ }
+ // Check if have a force display of the large play button
+ if( mw.config.get('EmbedPlayer.ForceLargeReplayButton') === true ){
+ _this.addLargePlayBtn();
+ } else{
+ // Check if we should hide the large play button on end:
+ if( $( _this ).data( 'hideEndPlayButton' ) || !_this.useLargePlayBtn() ){
+ _this.hideLargePlayBtn();
+ } else {
+ _this.addLargePlayBtn();
+ }
+ }
+ // An event for once the all ended events are done.
+ mw.log("EmbedPlayer:: trigger: onEndedDone");
+ if ( !_this.triggeredEndDone ){
+ _this.triggeredEndDone = true;
+ $( _this ).trigger( 'onEndedDone', [_this.id] );
+ }
+ })
+ }
+ }
+ },
+
+
+ /**
+ * Shows the video Thumbnail, updates pause state
+ */
+ showThumbnail: function() {
+ var _this = this;
+ mw.log( 'EmbedPlayer::showThumbnail::' + this.stopped );
+
+ // Close Menu Overlay:
+ this.controlBuilder.closeMenuOverlay();
+
+ // update the thumbnail html:
+ this.updatePosterHTML();
+
+ this.paused = true;
+ this.stopped = true;
+
+ // Once the thumbnail is shown run the mediaReady trigger (if not using native controls)
+ if( !this.useNativePlayerControls() ){
+ mw.log("mediaLoaded");
+ $( this ).trigger( 'mediaLoaded' );
+ }
+ },
+
+ /**
+ * Show the player
+ */
+ showPlayer: function () {
+ mw.log( 'EmbedPlayer:: showPlayer: ' + this.id + ' interface: w:' + this.width + ' h:' + this.height );
+ var _this = this;
+
+ // Remove the player loader spinner if it exists
+ this.hideSpinnerAndPlayBtn();
+ // If a isPersistentNativePlayer ( overlay the controls )
+ if( !this.useNativePlayerControls() && this.isPersistentNativePlayer() ){
+ $( this ).show();
+ }
+ // Add controls if enabled:
+ if ( this.controls ) {
+ if( this.useNativePlayerControls() ){
+ if( this.getPlayerElement() ){
+ $( this.getPlayerElement() ).attr('controls', "true");
+ }
+ } else {
+ this.controlBuilder.addControls();
+ }
+ }
+
+ // Update Thumbnail for the "player"
+ this.updatePosterHTML();
+
+ // Update temporal url if present
+ this.updateTemporalUrl();
+
+ // Do we need to show the player?
+ if( this.displayPlayer === false ) {
+ _this.getVideoHolder().hide();
+ _this.getInterface().height( _this.getComponentsHeight() );
+ _this.triggerHelper('updateLayout');
+ }
+
+ // Update layout
+ this.updateLayout();
+
+ // Make sure we have a play btn:
+ this.addLargePlayBtn();
+
+ // Update the playerReady flag
+ this.playerReadyFlag = true;
+ mw.log("EmbedPlayer:: Trigger: playerReady");
+ // trigger the player ready event;
+ $( this ).trigger( 'playerReady' );
+ this.triggerWidgetLoaded();
+
+ // Check if we want to block the player display
+ if( this['data-blockPlayerDisplay'] ){
+ this.blockPlayerDisplay();
+ return ;
+ }
+
+ // Check if there are any errors to be displayed:
+ if( this.getError() ){
+ this.showErrorMsg( this.getError() );
+ return ;
+ }
+ // Auto play stopped ( no playerReady has already started playback ) and if not on an iPad with iOS > 3
+ if ( this.isStopped() && this.autoplay && (!mw.isIOS() || mw.isIpad3() ) ) {
+ mw.log( 'EmbedPlayer::showPlayer::Do autoPlay' );
+ _this.play();
+ }
+ },
+
+ getComponentsHeight: function() {
+ var height = 0;
+
+ // Go over all playerContainer direct children with .block class
+ this.getInterface().find('.block').each(function() {
+ height += $( this ).outerHeight( true );
+ });
+
+ // FIXME embedPlayer should know nothing about playlist layout
+ /* If we're in vertical playlist mode, and not in fullscreen add playlist height
+ if( $('#container').hasClass('vertical') && ! this.controlBuilder.isInFullScreen() && this.displayPlayer ) {
+ height += $('#playlistContainer').outerHeight( true );
+ }
+ */
+
+ //
+ var offset = (mw.isIOS()) ? 5 : 0;
+
+ return height + offset;
+ },
+ updateLayout: function() {
+ // update image layout:
+ this.applyIntrinsicAspect();
+ if( !mw.config.get('EmbedPlayer.IsIframeServer' ) ){
+ // Use intrensic container size
+ return ;
+ }
+ // Set window height if in iframe:
+ var windowHeight;
+ if( mw.isIOS() && ! this.controlBuilder.isInFullScreen() ) {
+ windowHeight = $( window.parent.document.getElementById( this.id ) ).height();
+ } else {
+ windowHeight = window.innerHeight;
+ }
+
+ var newHeight = windowHeight - this.getComponentsHeight();
+ var currentHeight = this.getVideoHolder().height();
+ // Always update videoHolder height
+ if( currentHeight !== newHeight ) {
+ mw.log('EmbedPlayer: updateLayout:: window: ' + windowHeight + ', components: ' + this.getComponentsHeight() + ', videoHolder old height: ' + currentHeight + ', new height: ' + newHeight );
+ this.getVideoHolder().height( newHeight );
+ }
+ },
+ /**
+ * Gets a refrence to the main player interface, builds if not avaliable
+ */
+ getInterface: function(){
+ if( !this.$interface ){
+ // init the control builder
+ this.controlBuilder = new mw.PlayerControlBuilder( this );
+
+ // build the interface wrapper
+ this.$interface = $( this ).wrap(
+ $('<div />')
+ .addClass( 'mwPlayerContainer ' + this.controlBuilder.playerClass )
+ .append(
+ $('<div />').addClass( 'videoHolder' )
+ )
+ ).parent().parent();
+
+ // pass along any inhereted style:
+ if( this.style.cssText ){
+ this.$interface[0].style.cssText = this.style.cssText;
+ }
+ // clear out base style
+ this.style.cssText = '';
+
+ // if not displayiung a play button, ( pass through to native player )
+ if( ! this.useLargePlayBtn() ){
+ this.$interface.css('pointer-events', 'none');
+ }
+ }
+ return this.$interface;
+ },
+
+ /**
+ * Media fragments handler based on:
+ * http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#fragment-dimensions
+ *
+ * We support seconds and npt ( normal play time )
+ *
+ * Updates the player per fragment url info if present
+ *
+ */
+ updateTemporalUrl: function(){
+ var sourceHash = /[^\#]+$/.exec( this.getSrc() ).toString();
+ if( sourceHash.indexOf('t=') === 0 ){
+ // parse the times
+ var times = sourceHash.substr(2).split(',');
+ if( times[0] ){
+ // update the current time
+ this.currentTime = mw.npt2seconds( times[0].toString() );
+ }
+ if( times[1] ){
+ this.pauseTime = mw.npt2seconds( times[1].toString() );
+ // ignore invalid ranges:
+ if( this.pauseTime < this.currentTime ){
+ this.pauseTime = null;
+ }
+ }
+ // Update the play head
+ this.updatePlayHead( this.currentTime / this.duration );
+ // Update status:
+ this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) );
+ }
+ },
+ /**
+ * Sets an error message on the player
+ *
+ * @param {string}
+ * errorMsg
+ */
+ setError: function( errorObj ){
+ var _this = this;
+ if ( typeof errorObj == 'string' ) {
+ this.playerError = {
+ 'title' : _this.getKalturaMsg( 'ks-GENERIC_ERROR_TITLE' ),
+ 'message' : errorObj
+ }
+ return ;
+
+ }
+ this.playerError = errorObj;
+ },
+ /**
+ * Gets the current player error
+ */
+ getError: function() {
+ if ( !$.isEmptyObject( this.playerError ) ) {
+ return this.playerError;
+ }
+ return null;
+ },
+
+ /**
+ * Show an error message on the player
+ *
+ * @param {object}
+ * errorObj
+ */
+ showErrorMsg: function( errorObj ){
+ // Remove a loading spinner
+ this.hideSpinnerAndPlayBtn();
+ if( this.controlBuilder ) {
+ if( mw.config.get("EmbedPlayer.ShowPlayerAlerts") ) {
+ var alertObj = $.extend( errorObj, {
+ 'isModal': true,
+ 'keepOverlay': true,
+ 'noButtons': true,
+ 'isError': true
+ } );
+ this.controlBuilder.displayAlert( alertObj );
+ }
+ }
+ return ;
+ },
+
+ /**
+ * Blocks the player display by invoking an empty error msg
+ */
+ blockPlayerDisplay: function(){
+ this.showErrorMsg();
+ this.getInterface().find( '.error' ).hide();
+ },
+
+ /**
+ * Get missing plugin html (check for user included code)
+ *
+ * @param {String}
+ * [misssingType] missing type mime
+ */
+ showPlayerError: function( ) {
+ var _this = this;
+ var $this = $( this );
+ mw.log("EmbedPlayer::showPlayerError");
+ // Hide loader
+ this.hideSpinnerAndPlayBtn();
+
+ // Error in loading media ( trigger the mediaLoadError )
+ $this.trigger( 'mediaLoadError' );
+
+ // We don't distiguish between mediaError and mediaLoadError right now
+ // TODO fire mediaError only on failed to recive audio/video data.
+ $this.trigger( 'mediaError' );
+
+ // Check if we want to block the player display ( no error displayed )
+ if( this['data-blockPlayerDisplay'] ){
+ this.blockPlayerDisplay();
+ return ;
+ }
+
+ // Check if there is a more specific error:
+ if( this.getError() ){
+ this.showErrorMsg( this.getError() );
+ return ;
+ }
+
+ // If no error is given assume missing sources:
+ this.showNoInlinePlabackSupport();
+ },
+
+ /**
+ * Show player missing sources method
+ */
+ showNoInlinePlabackSupport: function(){
+ var _this = this;
+ var $this = $( this);
+
+ // Check if any sources are avaliable:
+ if( this.mediaElement.sources.length == 0
+ ||
+ !mw.config.get('EmbedPlayer.NotPlayableDownloadLink') )
+ {
+ return ;
+ }
+ // Set the isLink player flag:
+ this.isLinkPlayer= true;
+ // Update the poster and html:
+ this.updatePosterHTML();
+
+ // Make sure we have a play btn:
+ this.addLargePlayBtn();
+
+ // By default set the direct download url to the first source.
+ var downloadUrl = this.mediaElement.sources[0].getSrc();
+ // Allow plugins to update the download url ( to point to server side tools to select
+ // stream based on user agent ( i.e IE8 h.264 file, blackberry 3gp file etc )
+ this.triggerHelper( 'directDownloadLink', function( dlUrl ){
+ if( dlUrl ){
+ downloadUrl = dlUrl;
+ }
+ });
+ // Set the play button to the first available source:
+ var $pBtn = this.getInterface().find('.play-btn-large')
+ .attr( 'title', mw.msg('mwe-embedplayer-play_clip') )
+ .show()
+ .unbind( 'click' )
+ .click( function() {
+ _this.triggerHelper( 'firstPlay', [ _this.id ] ); // To send stats event for play
+ _this.triggerHelper( 'playing' );
+ return true;
+ });
+ if( !$pBtn.parent('a').length ){
+ $pBtn.wrap( $( '<a />' ).attr("target", "_blank" ) );
+ }
+ $pBtn.parent('a').attr( "href", downloadUrl );
+
+ $( this ).trigger( 'showInlineDownloadLink' );
+ },
+ /**
+ * Update the video time request via a time request string
+ *
+ * @param {String}
+ * timeRequest video time to be updated
+ */
+ updateVideoTimeReq: function( timeRequest ) {
+ mw.log( 'EmbedPlayer::updateVideoTimeReq:' + timeRequest );
+ var timeParts = timeRequest.split( '/' );
+ this.updateVideoTime( timeParts[0], timeParts[1] );
+ },
+
+ /**
+ * Update Video time from provided startNpt and endNpt values
+ *
+ * @param {String}
+ * startNpt the new start time in npt format ( hh:mm:ss.ms )
+ * @param {String}
+ * endNpt the new end time in npt format ( hh:mm:ss.ms )
+ */
+ updateVideoTime: function( startNpt, endNpt ) {
+ // update media
+ this.mediaElement.updateSourceTimes( startNpt, endNpt );
+
+ // update time
+ this.controlBuilder.setStatus( startNpt + '/' + endNpt );
+
+ // reset slider
+ this.updatePlayHead( 0 );
+
+ // Reset the serverSeekTime if urlTimeEncoding is enabled
+ if ( this.supportsURLTimeEncoding() ) {
+ this.serverSeekTime = 0;
+ } else {
+ this.serverSeekTime = mw.npt2seconds( startNpt );
+ }
+ },
+
+
+ /**
+ * Update Thumb time with npt formated time
+ *
+ * @param {String}
+ * time NPT formated time to update thumbnail
+ */
+ updateThumbTimeNPT: function( time ) {
+ this.updateThumbTime( mw.npt2seconds( time ) - parseInt( this.startOffset ) );
+ },
+
+ /**
+ * Update the thumb with a new time
+ *
+ * @param {Float}
+ * floatSeconds Time to update the thumb to
+ */
+ updateThumbTime:function( floatSeconds ) {
+ // mw.log('updateThumbTime:'+floatSeconds);
+ var _this = this;
+ if ( typeof this.orgThumSrc == 'undefined' ) {
+ this.orgThumSrc = this.poster;
+ }
+ if ( this.orgThumSrc.indexOf( 't=' ) !== -1 ) {
+ this.lastThumbUrl = mw.replaceUrlParams( this.orgThumSrc,
+ {
+ 't' : mw.seconds2npt( floatSeconds + parseInt( this.startOffset ) )
+ }
+ );
+ if ( !this.thumbnailUpdatingFlag ) {
+ this.updatePoster( this.lastThumbUrl , false );
+ this.lastThumbUrl = null;
+ }
+ }
+ },
+
+ /**
+ * Updates the displayed thumbnail via percent of the stream
+ *
+ * @param {Float}
+ * percent Percent of duration to update thumb
+ */
+ updateThumbPerc:function( percent ) {
+ return this.updateThumbTime( ( this.getDuration() * percent ) );
+ },
+
+ /**
+ * Update the poster source
+ * @param {String}
+ * posterSrc Poster src url
+ */
+ updatePosterSrc: function( posterSrc ){
+ if( ! posterSrc ) {
+ posterSrc = mw.config.get( 'EmbedPlayer.BlackPixel' );
+ }
+ this.poster = posterSrc;
+ this.updatePosterHTML();
+ this.applyIntrinsicAspect();
+ },
+
+ /**
+ * Called after sources are updated, and your ready for the player to change media
+ * @return
+ */
+ changeMedia: function( callback ){
+ var _this = this;
+ var $this = $( this );
+ mw.log( 'EmbedPlayer:: changeMedia ');
+ // Empty out embedPlayer object sources
+ this.emptySources();
+
+ // onChangeMedia triggered at the start of the change media commands
+ $this.trigger( 'onChangeMedia' );
+
+ // Reset first play to true, to count that play event
+ this.firstPlay = true;
+ // reset donePlaying count on change media.
+ this.donePlayingCount = 0;
+ this.triggeredEndDone = false;
+ this.preSequence = false;
+ this.postSequence = false;
+
+ this.setCurrentTime( 0.01 );
+ // Reset the playhead
+ this.updatePlayHead( 0 );
+ // update the status:
+ this.controlBuilder.setStatus( this.getTimeRange() );
+
+ // Add a loader to the embed player:
+ this.pauseLoading();
+
+ // Clear out any player error ( both via attr and object property ):
+ this.setError( null );
+
+ // Clear out any player display blocks
+ this['data-blockPlayerDisplay'] = null
+ $this.attr( 'data-blockPlayerDisplay', '');
+
+ // Clear out the player error div:
+ this.getInterface().find('.error').remove();
+ this.controlBuilder.closeAlert();
+ this.controlBuilder.closeMenuOverlay();
+
+ // Restore the control bar:
+ this.getInterface().find('.control-bar').show();
+ // Hide the play btn
+ this.hideLargePlayBtn();
+
+ //If we are change playing media add a ready binding:
+ var bindName = 'playerReady.changeMedia';
+ $this.unbind( bindName ).bind( bindName, function(){
+ mw.log('EmbedPlayer::changeMedia playerReady callback');
+ // hide the loading spinner:
+ _this.hideSpinnerAndPlayBtn();
+ // check for an erro on change media:
+ if( _this.getError() ){
+ _this.showErrorMsg( _this.getError() );
+ return ;
+ }
+ // Always show the control bar on switch:
+ if( _this.controlBuilder ){
+ _this.controlBuilder.showControlBar();
+ }
+ // Make sure the play button reflects the original play state
+ if( _this.autoplay ){
+ _this.hideLargePlayBtn();
+ } else {
+ _this.addLargePlayBtn();
+ }
+ var source = _this.getSource();
+ if( (_this.isPersistentNativePlayer() || _this.useNativePlayerControls()) && source ){
+ // If switching a Persistent native player update the source:
+ // ( stop and play won't refresh the source )
+ _this.switchPlaySource( source, function(){
+ _this.changeMediaStarted = false;
+ $this.trigger( 'onChangeMediaDone' );
+ if( _this.autoplay ){
+ _this.play();
+ } else {
+ // pause is need to keep pause sate, while
+ // switch source calls .play() that some browsers require.
+ // to reflect source swiches.
+ _this.pause();
+ _this.addLargePlayBtn();
+ }
+ if( callback ){
+ callback()
+ }
+ });
+ // we are handling trigger and callback asynchronously return here.
+ return ;
+ }
+
+ // Reset changeMediaStarted flag
+ _this.changeMediaStarted = false;
+
+ // Stop should unload the native player
+ _this.stop();
+
+ // reload the player
+ if( _this.autoplay ){
+ _this.play();
+ } else {
+ _this.addLargePlayBtn();
+ }
+
+ $this.trigger( 'onChangeMediaDone' );
+ if( callback ) {
+ callback();
+ }
+ });
+
+ // Load new sources per the entry id via the checkPlayerSourcesEvent hook:
+ $this.triggerQueueCallback( 'checkPlayerSourcesEvent', function(){
+ // Start player events leading to playerReady
+ _this.setupSourcePlayer();
+ });
+ },
+ /**
+ * Checks if the current player / configuration is an image play screen:
+ */
+ isImagePlayScreen:function(){
+ return ( this.useNativePlayerControls() &&
+ !this.isLinkPlayer &&
+ mw.isIphone() &&
+ mw.config.get( 'EmbedPlayer.iPhoneShowHTMLPlayScreen')
+ );
+ },
+ /**
+ * Triggers widgetLoaded event - Needs to be triggered only once, at the first time playerReady is trigerred
+ */
+ triggerWidgetLoaded: function() {
+ if ( !this.widgetLoaded ) {
+ this.widgetLoaded = true;
+ mw.log( "EmbedPlayer:: Trigger: widgetLoaded");
+ this.triggerHelper( 'widgetLoaded' );
+ }
+ },
+
+ /**
+ * Updates the poster HTML
+ */
+ updatePosterHTML: function () {
+ mw.log( 'EmbedPlayer:updatePosterHTML::' + this.id );
+
+ var _this = this,
+ thumb_html = '',
+ class_atr = '',
+ style_atr = '',
+ profile = $.client.profile();
+
+ if( this.isImagePlayScreen() ){
+ this.addPlayScreenWithNativeOffScreen();
+ return ;
+ }
+
+ // Set by default thumb value if not found
+ var posterSrc = ( this.poster ) ? this.poster :
+ mw.config.get( 'EmbedPlayer.BlackPixel' );
+
+ // Update PersistentNativePlayer poster:
+ if( this.isPersistentNativePlayer() ){
+ var $vid = $( '#' + this.pid ).show();
+ $vid.attr( 'poster', posterSrc );
+ // Add a quick timeout hide / show ( firefox 4x bug with native poster updates )
+ if ( profile.name === 'firefox' ){
+ $vid.hide();
+ setTimeout( function () {
+ $vid.show();
+ }, 0);
+ }
+ } else {
+ // hide the pid if present:
+ $( '#' + this.pid ).hide();
+ // Poster support is not very consistent in browsers use a jpg poster image:
+ $( this )
+ .html(
+ $( '<img />' )
+ .css({
+ 'position': 'absolute',
+ 'top': 0,
+ 'left': 0,
+ 'right': 0,
+ 'bottom': 0
+ })
+ .attr({
+ 'src' : posterSrc
+ })
+ .addClass( 'playerPoster' )
+ .load(function(){
+ _this.applyIntrinsicAspect();
+ })
+ ).show();
+ }
+ if ( this.useLargePlayBtn() && this.controlBuilder
+ &&
+ this.height > this.controlBuilder.getComponentHeight( 'playButtonLarge' )
+ ) {
+ this.addLargePlayBtn();
+ }
+ },
+ /**
+ * Abstract method, must be set by player inteface
+ */
+ addPlayScreenWithNativeOffScreen: function(){
+ mw.log( "Error: EmbedPlayer, Must override 'addPlayScreenWithNativeOffScreen' with player inteface" );
+ return ;
+ },
+ /**
+ * Checks if a large play button should be displayed on the
+ * otherwise native player
+ */
+ useLargePlayBtn: function(){
+ if( this.isPersistantPlayBtn() ){
+ return true;
+ }
+ // If we are using native controls return false:
+ return !this.useNativePlayerControls();
+ },
+ /**
+ * Checks if the play button should stay on screen during playback,
+ * cases where a native player is dipalyed such as iPhone.
+ */
+ isPersistantPlayBtn: function(){
+ return mw.isAndroid2() ||
+ ( mw.isIphone() && mw.config.get( 'EmbedPlayer.iPhoneShowHTMLPlayScreen' ) );
+ },
+ /**
+ * Checks if native controls should be used
+ *
+ * @returns boolean true if the mwEmbed player interface should be used
+ * false if the mwEmbed player interface should not be used
+ */
+ useNativePlayerControls: function() {
+ if( this.usenativecontrols === true ){
+ return true;
+ }
+
+ if( mw.config.get('EmbedPlayer.NativeControls') === true ) {
+ return true;
+ }
+
+ // Check for special webkit property that allows inline iPhone playback:
+ if( mw.config.get('EmbedPlayer.WebKitPlaysInline') === true && mw.isIphone() ) {
+ return false;
+ }
+
+ // Do some device detection devices that don't support overlays
+ // and go into full screen once play is clicked:
+ if( mw.isAndroid2() || mw.isIpod() || mw.isIphone() ){
+ return true;
+ }
+
+ // iPad can use html controls if its a persistantPlayer in the dom before loading )
+ // else it needs to use native controls:
+ if( mw.isIpad() ){
+ if( mw.config.get('EmbedPlayer.EnableIpadHTMLControls') === true){
+ return false;
+ } else {
+ // Set warning that your trying to do iPad controls without
+ // persistent native player:
+ return true;
+ }
+ }
+ return false;
+ },
+ /**
+ * Checks if the native player is persistent in the dom since the intial page build out.
+ */
+ isPersistentNativePlayer: function(){
+ if( this.isLinkPlayer ){
+ return false;
+ }
+ // Since we check this early on sometimes the player
+ // has not yet been updated to the pid location
+ if( $('#' + this.pid ).length == 0 ){
+ return $('#' + this.id ).hasClass('persistentNativePlayer');
+ }
+ return $('#' + this.pid ).hasClass('persistentNativePlayer');
+ },
+ //
+ isTouchDevice: function(){
+ return mw.isIpad()
+ ||
+ mw.isAndroid40()
+ ||
+ mw.isMobileChrome();
+ },
+ /**
+ * Hides the large play button
+ * TODO move to player controls
+ */
+ hideLargePlayBtn: function(){
+ if( this.getInterface() ){
+ this.getInterface().find( '.play-btn-large' ).hide();
+ }
+ },
+ /**
+ * Add a play button (if not already there )
+ */
+ addLargePlayBtn: function(){
+ // check if we are pauseLoading ( i.e switching media, seeking, etc. and don't display play btn:
+ if( this.isPauseLoading ){
+ mw.log("EmbedPlayer:: addLargePlayBtn ( skip play button, during load )");
+ return;
+ }
+ // if using native controls make sure we can click the big play button by restoring
+ // interface click events:
+ if( this.useNativePlayerControls() ){
+ this.getInterface().css('pointer-events', 'auto');
+ }
+
+ // iPhone in WebKitPlaysInline mode does not support clickable overlays as of iOS 5.0
+ if( mw.config.get( 'EmbedPlayer.WebKitPlaysInline') && mw.isIphone() ) {
+ return ;
+ }
+ if( this.getInterface().find( '.play-btn-large' ).length ){
+ this.getInterface().find( '.play-btn-large' ).show();
+ } else {
+ this.getVideoHolder().append(
+ this.controlBuilder.getComponent( 'playButtonLarge' )
+ );
+ }
+ },
+
+ getVideoHolder: function() {
+ return this.getInterface().find('.videoHolder');
+ },
+
+ /**
+ * Abstract method,
+ * Get native player html ( should be set by mw.EmbedPlayerNative )
+ */
+ getNativePlayerHtml: function(){
+ return $('<div />' )
+ .css( 'width', this.getWidth() )
+ .html( 'Error: Trying to get native html5 player without native support for codec' );
+ },
+
+ /**
+ * Should be set via native embed support
+ */
+ applyMediaElementBindings: function(){
+ mw.log("Warning applyMediaElementBindings should be implemented by player interface" );
+ return ;
+ },
+
+ /**
+ * Gets code to embed the player remotely for "share" this player links
+ */
+ getSharingEmbedCode: function() {
+ switch( mw.config.get( 'EmbedPlayer.ShareEmbedMode' ) ){
+ case 'iframe':
+ return this.getShareIframeObject();
+ break;
+ case 'videojs':
+ return this.getShareEmbedVideoJs();
+ break;
+ }
+ },
+
+ /**
+ * Gets code to embed the player in a wiki
+ */
+ getWikiEmbedCode: function() {
+ if( this.apiTitleKey) {
+ return '[[File:' + this.apiTitleKey + ']]';
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Get the iframe share code:
+ */
+ getShareIframeObject: function(){
+ // TODO move to getShareIframeSrc
+ var iframeUrl = this.getIframeSourceUrl();
+
+ // Set up embedFrame src path
+ var embedCode = '&lt;iframe src=&quot;' + mw.html.escape( iframeUrl ) + '&quot; ';
+
+ // Set width / height of embed object
+ embedCode += 'width=&quot;' + this.getPlayerWidth() +'&quot; ';
+ embedCode += 'height=&quot;' + this.getPlayerHeight() + '&quot; ';
+ embedCode += 'frameborder=&quot;0&quot; ';
+ embedCode += 'webkitAllowFullScreen mozallowfullscreen allowFullScreen';
+
+ // Close up the embedCode tag:
+ embedCode+='&gt;&lt/iframe&gt;';
+
+ // Return the embed code
+ return embedCode;
+ },
+ /**
+ * Gets the iframe source url
+ */
+ getIframeSourceUrl: function(){
+ var iframeUrl = false;
+ this.triggerHelper( 'getShareIframeSrc', [ function( localIframeSrc ){
+ if( iframeUrl){
+ mw.log("Error multiple modules binding getShareIframeSrc" );
+ }
+ iframeUrl = localIframeSrc;
+ }, this.id ]);
+ if( iframeUrl ){
+ return iframeUrl;
+ }
+ // old style embed:
+ var iframeUrl = mw.getMwEmbedPath() + 'mwEmbedFrame.php?';
+ var params = {'src[]' : []};
+
+ // Output all the video sources:
+ for( var i=0; i < this.mediaElement.sources.length; i++ ){
+ var source = this.mediaElement.sources[i];
+ if( source.src ) {
+ params['src[]'].push(mw.absoluteUrl( source.src ));
+ }
+ }
+ // Output the poster attr
+ if( this.poster ){
+ params.poster = this.poster;
+ }
+
+ // Set the skin if set to something other than default
+ if( this.skinName ){
+ params.skin = this.skinName;
+ }
+
+ if( this.duration ) {
+ params.durationHint = parseFloat( this.duration );
+ }
+ iframeUrl += $.param( params );
+ return iframeUrl;
+ },
+ /**
+ * Get the share embed Video tag html to share the embed code.
+ */
+ getShareEmbedVideoJs: function(){
+
+ // Set the embed tag type:
+ var embedtag = ( this.isAudio() )? 'audio': 'video';
+
+ // Set up the mwEmbed js include:
+ var embedCode = '&lt;script type=&quot;text/javascript&quot; ' +
+ 'src=&quot;' +
+ mw.html.escape(
+ mw.absoluteUrl(
+ mw.getMwEmbedSrc()
+ )
+ ) + '&quot;&gt;&lt;/script&gt' +
+ '&lt;' + embedtag + ' ';
+
+ if( this.poster ) {
+ embedCode += 'poster=&quot;' +
+ mw.html.escape( mw.absoluteUrl( this.poster ) ) +
+ '&quot; ';
+ }
+
+ // Set the skin if set to something other than default
+ if( this.skinName ){
+ embedCode += 'class=&quot;' +
+ mw.html.escape( this.skinName ) +
+ '&quot; ';
+ }
+
+ if( this.duration ) {
+ embedCode +='durationHint=&quot;' + parseFloat( this.duration ) + '&quot; ';
+ }
+
+ if( this.width || this.height ){
+ embedCode += 'style=&quot;';
+ embedCode += ( this.width )? 'width:' + this.width +'px;': '';
+ embedCode += ( this.height )? 'height:' + this.height +'px;': '';
+ embedCode += '&quot; ';
+ }
+
+ // Close the video attr
+ embedCode += '&gt;';
+
+ // Output all the video sources:
+ for( var i=0; i < this.mediaElement.sources.length; i++ ){
+ var source = this.mediaElement.sources[i];
+ if( source.src ) {
+ embedCode +='&lt;source src=&quot;' +
+ mw.absoluteUrl( source.src ) +
+ '&quot; &gt;&lt;/source&gt;';
+ }
+ }
+ // Close the video tag
+ embedCode += '&lt;/video&gt;';
+
+ return embedCode;
+ },
+
+
+
+ /**
+ * Base Embed Controls
+ */
+
+ /**
+ * The Play Action
+ *
+ * Handles play requests, updates relevant states:
+ * seeking =false
+ * paused =false
+ *
+ * Triggers the play event
+ *
+ * Updates pause button Starts the "monitor"
+ */
+ firstPlay : true,
+ preSequence: false,
+ inPreSequence: false,
+ replayEventCount : 0,
+ play: function() {
+ var _this = this;
+ var $this = $( this );
+ // Store the absolute play time ( to track native events that should not invoke interface updates )
+ mw.log( "EmbedPlayer:: play: " + this._propagateEvents + ' poster: ' + this.stopped );
+
+ this.absoluteStartPlayTime = new Date().getTime();
+
+ // Check if thumbnail is being displayed and embed html
+ if ( _this.isStopped() && (_this.preSequence == false || (_this.sequenceProxy && _this.sequenceProxy.isInSequence == false) )) {
+ if ( !_this.selectedPlayer ) {
+ _this.showPlayerError();
+ return false;
+ } else {
+ _this.embedPlayerHTML();
+ }
+ }
+ // playing, exit stopped state:
+ _this.stopped = false;
+
+ if( !this.preSequence ) {
+ this.preSequence = true;
+ mw.log( "EmbedPlayer:: trigger preSequence " );
+ this.triggerHelper( 'preSequence' );
+ this.playInterfaceUpdate();
+ // if we entered into ad loading return
+ if( _this.sequenceProxy && _this.sequenceProxy.isInSequence ){
+ mw.log("EmbedPlayer:: isInSequence, do NOT play content");
+ return false;
+ }
+ }
+
+ // We need first play event for analytics purpose
+ if( this.firstPlay && this._propagateEvents) {
+ this.firstPlay = false;
+ this.triggerHelper( 'firstPlay', [ _this.id ] );
+ }
+
+ if( this.paused === true ){
+ this.paused = false;
+ // Check if we should Trigger the play event
+ mw.log("EmbedPlayer:: trigger play event::" + !this.paused + ' events:' + this._propagateEvents );
+ // trigger the actual play event:
+ if( this._propagateEvents ) {
+ this.triggerHelper( 'onplay' );
+ }
+ }
+
+ // If we previously finished playing this clip run the "replay hook"
+ if( this.donePlayingCount > 0 && !this.paused && this._propagateEvents ) {
+ this.replayEventCount++;
+ // Trigger end done on replay
+ this.triggeredEndDone = false;
+ if( this.replayEventCount <= this.donePlayingCount){
+ mw.log("EmbedPlayer::play> trigger replayEvent");
+ this.triggerHelper( 'replayEvent' );
+ }
+ }
+
+ // If we have start time defined, start playing from that point
+ if( this.currentTime < this.startTime ) {
+ $this.bind('playing.startTime', function(){
+ $this.unbind('playing.startTime');
+ if( !mw.isIOS() ){
+ _this.setCurrentTime( _this.startTime );
+ _this.startTime = 0;
+ } else {
+ // iPad seeking on syncronus play event sucks
+ setTimeout( function(){
+ _this.setCurrentTime( _this.startTime, function(){
+ _this.play();
+ });
+ _this.startTime = 0;
+ }, 500 )
+ }
+ _this.startTime = 0;
+ });
+ }
+
+ this.playInterfaceUpdate();
+ // If play controls are enabled continue to video content element playback:
+ if( _this._playContorls ){
+ return true;
+ } else {
+ // return false ( Mock play event, or handled elsewhere )
+ return false;
+ }
+ },
+ /**
+ * Update the player inteface for playback
+ * TODO move to controlBuilder
+ */
+ playInterfaceUpdate: function(){
+ var _this = this;
+ mw.log( 'EmbedPlayer:: playInterfaceUpdate' );
+ // Hide any overlay:
+ if( this.controlBuilder ){
+ this.controlBuilder.closeMenuOverlay();
+ }
+ // Hide any buttons or errors if present:
+ this.getInterface().find( '.error' ).remove();
+ this.hideLargePlayBtn();
+
+ this.getInterface().find('.play-btn span')
+ .removeClass( 'ui-icon-play' )
+ .addClass( 'ui-icon-pause' );
+
+ this.hideSpinnerOncePlaying();
+
+ this.getInterface().find( '.play-btn' )
+ .unbind('click')
+ .click( function( ) {
+ if( _this._playContorls ){
+ _this.pause();
+ }
+ } )
+ .attr( 'title', mw.msg( 'mwe-embedplayer-pause_clip' ) );
+ },
+ /**
+ * Pause player, and display a loading animation
+ * @return
+ */
+ pauseLoading: function(){
+ this.pause();
+ this.addPlayerSpinner();
+ this.isPauseLoading = true;
+ },
+ /**
+ * Adds a loading spinner to the player.
+ */
+ addPlayerSpinner: function(){
+ var sId = 'loadingSpinner_' + this.id;
+ // remove any old spinner
+ $( '#' + sId ).remove();
+ // hide the play btn if present
+ this.hideLargePlayBtn();
+ // re add an absolute positioned spinner:
+ $( this ).show().getAbsoluteOverlaySpinner()
+ .attr( 'id', sId );
+ },
+ hideSpinner: function(){
+ // remove the spinner
+ $( '#loadingSpinner_' + this.id + ',.loadingSpinner' ).remove();
+ },
+ /**
+ * Hides the loading spinner
+ */
+ hideSpinnerAndPlayBtn: function(){
+ this.isPauseLoading = false;
+ this.hideSpinner();
+ // hide the play btn
+ this.hideLargePlayBtn();
+ },
+ /**
+ * Hides the loading spinner once playing.
+ */
+ hideSpinnerOncePlaying: function(){
+ this._checkHideSpinner = true;
+ },
+ /**
+ * Base embed pause Updates the play/pause button state.
+ *
+ * There is no general way to pause the video must be overwritten by embed
+ * object to support this functionality.
+ *
+ * @param {Boolean} if the event was triggered by user action or propagated by js.
+ */
+ pause: function() {
+ var _this = this;
+ // Trigger the pause event if not already paused and using native controls:
+ if( this.paused === false ){
+ this.paused = true;
+ if( this._propagateEvents ){
+ mw.log( 'EmbedPlayer:trigger pause:' + this.paused );
+ // we only trigger "onpause" to avoid event propagation to the native object method
+ // i.e in jQuery ( this ).trigger('pause') also calls: this.pause();
+ $( this ).trigger( 'onpause' );
+ }
+ }
+ _this.pauseInterfaceUpdate();
+ },
+ /**
+ * Sets the player interface to paused mode.
+ */
+ pauseInterfaceUpdate: function(){
+ var _this =this;
+ mw.log("EmbedPlayer::pauseInterfaceUpdate");
+ // Update the ctrl "paused state"
+ this.getInterface().find('.play-btn span' )
+ .removeClass( 'ui-icon-pause' )
+ .addClass( 'ui-icon-play' );
+
+ this.getInterface().find( '.play-btn' )
+ .unbind('click')
+ .click( function() {
+ if( _this._playContorls ){
+ _this.play();
+ }
+ } )
+ .attr( 'title', mw.msg( 'mwe-embedplayer-play_clip' ) );
+ },
+ /**
+ * Maps the html5 load request. There is no general way to "load" clips so
+ * underling plugin-player libs should override.
+ */
+ load: function() {
+ // should be done by child (no base way to pre-buffer video)
+ mw.log( 'Waring:: the load method should be overided by player interface' );
+ },
+
+
+ /**
+ * Base embed stop
+ *
+ * Updates the player to the stop state.
+ *
+ * Shows Thumbnail
+ * Resets Buffer
+ * Resets Playhead slider
+ * Resets Status
+ *
+ * Trigger the "doStop" event
+ */
+ stop: function() {
+ var _this = this;
+ mw.log( 'EmbedPlayer::stop:' + this.id );
+ // update the player to stopped state:
+ this.stopped = true;
+
+ // Rest the prequecne flag:
+ this.preSequence = false;
+
+ // Trigger the stop event:
+ $( this ).trigger( 'doStop' );
+
+ // no longer seeking:
+ this.didSeekJump = false;
+
+ // Reset current time and prev time and seek offset
+ this.currentTime = this.previousTime = this.serverSeekTime = 0;
+
+ this.stopMonitor();
+
+ // pause playback ( if playing )
+ if( !this.paused ){
+ this.pause();
+ }
+ // Restore the play button ( if not native controls or is android )
+ if( this.useLargePlayBtn() ){
+ this.addLargePlayBtn();
+ this.pauseInterfaceUpdate();
+ }
+
+ // Native player controls:
+ if( !this.isPersistentNativePlayer() ){
+ // Rewrite the html to thumbnail disp
+ this.showThumbnail();
+ this.bufferedPercent = 0; // reset buffer state
+ this.controlBuilder.setStatus( this.getTimeRange() );
+ }
+ // Reset the playhead
+ this.updatePlayHead( 0 );
+ // update the status:
+ this.controlBuilder.setStatus( this.getTimeRange() );
+ // reset buffer indicator:
+ this.bufferedPercent = 0;
+ this.updateBufferStatus();
+ },
+
+ /**
+ * Base Embed mute
+ *
+ * Handles interface updates for toggling mute. Plug-in / player interface
+ * must handle the actual media player action
+ */
+ toggleMute: function( userAction ) {
+ mw.log( 'EmbedPlayer::toggleMute> (old state:) ' + this.muted );
+ if ( this.muted ) {
+ this.muted = false;
+ var percent = this.preMuteVolume;
+ } else {
+ this.muted = true;
+ this.preMuteVolume = this.volume;
+ var percent = 0;
+ }
+ // Change the volume and trigger the volume change so that other plugins can listen.
+ this.setVolume( percent, true );
+ // Update the interface
+ this.setInterfaceVolume( percent );
+ // trigger the onToggleMute event
+ $( this ).trigger('onToggleMute');
+ },
+
+ /**
+ * Update volume function ( called from interface updates )
+ *
+ * @param {float}
+ * percent Percent of full volume
+ * @param {triggerChange}
+ * boolean change if the event should be triggered
+ */
+ setVolume: function( percent, triggerChange ) {
+ var _this = this;
+ // ignore NaN percent:
+ if( isNaN( percent ) ){
+ return ;
+ }
+ // Set the local volume attribute
+ this.previousVolume = this.volume;
+
+ this.volume = percent;
+
+ // Un-mute if setting positive volume
+ if( percent != 0 ){
+ this.muted = false;
+ }
+
+ // Update the playerElement volume
+ this.setPlayerElementVolume( percent );
+ //mw.log("EmbedPlayer:: setVolume:: " + percent + ' trigger volumeChanged: ' + triggerChange );
+ if( triggerChange ){
+ $( _this ).trigger('volumeChanged', percent );
+ }
+ },
+
+ /**
+ * Updates the interface volume
+ *
+ * TODO should move to controlBuilder
+ *
+ * @param {float}
+ * percent Percentage volume to update interface
+ */
+ setInterfaceVolume: function( percent ) {
+ if( this.supports[ 'volumeControl' ] &&
+ this.getInterface().find( '.volume-slider' ).length
+ ) {
+ this.getInterface().find( '.volume-slider' ).slider( 'value', percent * 100 );
+ }
+ },
+
+ /**
+ * Abstract method Update volume Method must be override by plug-in / player interface
+ *
+ * @param {float}
+ * percent Percentage volume to update
+ */
+ setPlayerElementVolume: function( percent ) {
+ mw.log('Error player does not support volume adjustment' );
+ },
+
+ /**
+ * Abstract method get volume Method must be override by plug-in / player interface
+ * (if player does not override we return the abstract player value )
+ */
+ getPlayerElementVolume: function(){
+ // mw.log(' error player does not support getting volume property' );
+ return this.volume;
+ },
+
+ /**
+ * Abstract method get volume muted property must be overwritten by plug-in /
+ * player interface (if player does not override we return the abstract
+ * player value )
+ */
+ getPlayerElementMuted: function(){
+ // mw.log(' error player does not support getting mute property' );
+ return this.muted;
+ },
+
+ /**
+ * Passes a fullscreen request to the controlBuilder interface
+ */
+ fullscreen: function() {
+ this.controlBuilder.toggleFullscreen();
+ },
+
+ /**
+ * Abstract method to be run post embedding the player Generally should be
+ * overwritten by the plug-in / player
+ */
+ postEmbedActions:function() {
+ return ;
+ },
+
+ /**
+ * Checks the player state based on thumbnail display & paused state
+ *
+ * @return {Boolean} true if playing false if not playing
+ */
+ isPlaying : function() {
+ if ( this.stopped ) {
+ // in stopped state
+ return false;
+ } else if ( this.paused ) {
+ // paused state
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Get Stopped state
+ *
+ * @return {Boolean} true if stopped false if playing
+ */
+ isStopped: function() {
+ return this.stopped;
+ },
+ /**
+ * Stop the play state monitor
+ */
+ stopMonitor: function(){
+ clearInterval( this.monitorInterval );
+ this.monitorInterval = 0;
+ },
+ /**
+ * Start the play state monitor
+ */
+ startMonitor: function(){
+ this.monitor();
+ },
+
+ /**
+ * Monitor playback and update interface components. underling player classes
+ * are responsible for updating currentTime
+ */
+ monitor: function() {
+ var _this = this;
+
+ // Check for current time update outside of embed player
+ _this.syncCurrentTime();
+
+ // mw.log( "monitor:: " + this.currentTime + ' propagateEvents: ' + _this._propagateEvents );
+
+ // update player status
+ _this.updatePlayheadStatus();
+
+ // Keep volume proprties set outside of the embed player in sync
+ _this.syncVolume();
+
+ // Make sure the monitor continues to run as long as the video is not stoped
+ _this.syncMonitor()
+
+ if( _this._propagateEvents ){
+
+ // mw.log('trigger:monitor:: ' + this.currentTime );
+ $( _this ).trigger( 'monitorEvent', [ _this.id ] );
+
+ // Trigger the "progress" event per HTML5 api support
+ if( _this.progressEventData ) {
+ $( _this ).trigger( 'progress', _this.progressEventData );
+ }
+ }
+ },
+ /**
+ * Sync the monitor function
+ */
+ syncMonitor: function(){
+ var _this = this;
+ // Call monitor at this.monitorRate interval.
+ // ( use setInterval to avoid stacking monitor requests )
+ if( ! this.isStopped() ) {
+ if( !this.monitorInterval ){
+ this.monitorInterval = setInterval( function(){
+ if( _this.monitor )
+ _this.monitor();
+ }, this.monitorRate );
+ }
+ } else {
+ // If stopped "stop" monitor:
+ this.stopMonitor();
+ }
+ },
+
+ /**
+ * Sync the video volume
+ */
+ syncVolume: function(){
+ var _this = this;
+ // Check if volume was set outside of embed player function
+ // mw.log( ' this.volume: ' + _this.volume + ' prev Volume:: ' + _this.previousVolume );
+ if( Math.round( _this.volume * 100 ) != Math.round( _this.previousVolume * 100 ) ) {
+ _this.setInterfaceVolume( _this.volume );
+ }
+ // Update the previous volume
+ _this.previousVolume = _this.volume;
+
+ // Update the volume from the player element
+ _this.volume = this.getPlayerElementVolume();
+
+ // update the mute state from the player element
+ if( _this.muted != _this.getPlayerElementMuted() && ! _this.isStopped() ){
+ mw.log( "EmbedPlayer::syncVolume: muted does not mach embed player" );
+ _this.toggleMute();
+ // Make sure they match:
+ _this.muted = _this.getPlayerElementMuted();
+ }
+ },
+
+ /**
+ * Checks if the currentTime was updated outside of the getPlayerElementTime function
+ */
+ syncCurrentTime: function(){
+ var _this = this;
+
+ // Hide the spinner once we have time update:
+ if( _this._checkHideSpinner && _this.currentTime != _this.getPlayerElementTime() ){
+ _this._checkHideSpinner = false;
+ _this.hideSpinnerAndPlayBtn();
+
+ if( _this.isPersistantPlayBtn() ){
+ // add the play button likely iphone or native player that needs the play button on
+ // non-event "exit native html5 player"
+ _this.addLargePlayBtn();
+ } else{
+ // also hide the play button ( in case it was there somehow )
+ _this.hideLargePlayBtn();
+ }
+ }
+
+ // Check if a javascript currentTime change based seek has occurred
+ if( parseInt( _this.previousTime ) != parseInt( _this.currentTime ) &&
+ !this.userSlide &&
+ !this.seeking &&
+ !this.isStopped()
+ ){
+ // If the time has been updated and is in range issue a seek
+ if( _this.getDuration() && _this.currentTime <= _this.getDuration() ){
+ var seekPercent = _this.currentTime / _this.getDuration();
+ mw.log("EmbedPlayer::syncCurrentTime::" + _this.previousTime + ' != ' +
+ _this.currentTime + " javascript based currentTime update to " +
+ seekPercent + ' == ' + _this.currentTime );
+ _this.previousTime = _this.currentTime;
+ this.seek( seekPercent );
+ }
+ }
+
+ // Update currentTime via embedPlayer
+ _this.currentTime = _this.getPlayerElementTime();
+
+ // Update any offsets from server seek
+ if( _this.serverSeekTime && _this.supportsURLTimeEncoding() ){
+ _this.currentTime = parseInt( _this.serverSeekTime ) + parseInt( _this.getPlayerElementTime() );
+ }
+
+ // Update the previousTime ( so we can know if the user-javascript changed currentTime )
+ _this.previousTime = _this.currentTime;
+
+ // Check for a pauseTime to stop playback in temporal media fragments
+ if( _this.pauseTime && _this.currentTime > _this.pauseTime ){
+ _this.pause();
+ _this.pauseTime = null;
+ }
+ },
+ /**
+ * Updates the player time and playhead position based on currentTime
+ */
+ updatePlayheadStatus: function(){
+ var _this = this;
+ if ( this.currentTime >= 0 && this.duration ) {
+ if ( !this.userSlide && !this.seeking ) {
+ if ( parseInt( this.startOffset ) != 0 ) {
+ this.updatePlayHead( ( this.currentTime - this.startOffset ) / this.duration );
+ var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( parseFloat( this.startOffset ) + parseFloat( this.duration ) ) : '';
+ this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et );
+ } else {
+ // use raw currentTIme for playhead updates
+ var ct = ( this.getPlayerElement() ) ? this.getPlayerElement().currentTime || this.currentTime: this.currentTime;
+ this.updatePlayHead( ct / this.duration );
+ // Only include the end time if longTimeDisp is enabled:
+ var et = ( this.controlBuilder.longTimeDisp ) ? '/' + mw.seconds2npt( this.duration ) : '';
+ this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + et );
+ }
+ }
+ // Check if we are "done"
+ var endPresentationTime = ( this.startOffset ) ? ( this.startOffset + this.duration ) : this.duration;
+ if ( this.currentTime >= endPresentationTime && !this.isStopped() ) {
+ mw.log( "EmbedPlayer::updatePlayheadStatus > should run clip done :: " + this.currentTime + ' > ' + endPresentationTime );
+ this.onClipDone();
+ }
+ } else {
+ // Media lacks duration just show end time
+ if ( this.isStopped() ) {
+ this.controlBuilder.setStatus( this.getTimeRange() );
+ } else if ( this.paused ) {
+ this.controlBuilder.setStatus( mw.msg( 'mwe-embedplayer-paused' ) );
+ } else if ( this.isPlaying() ) {
+ if ( this.currentTime && ! this.duration )
+ this.controlBuilder.setStatus( mw.seconds2npt( this.currentTime ) + ' /' );
+ else
+ this.controlBuilder.setStatus( " - - - " );
+ } else {
+ this.controlBuilder.setStatus( this.getTimeRange() );
+ }
+ }
+ },
+
+ /**
+ * Abstract getPlayerElementTime function
+ */
+ getPlayerElementTime: function(){
+ mw.log("Error: getPlayerElementTime should be implemented by embed library");
+ },
+
+ /**
+ * Abstract getPlayerElementTime function
+ */
+ getPlayerElement: function(){
+ mw.log("Error: getPlayerElement should be implemented by embed library, or you may be calling this event too soon");
+ },
+
+ /**
+ * Update the Buffer status based on the local bufferedPercent var
+ */
+ updateBufferStatus: function() {
+ // Get the buffer target based for playlist vs clip
+ var $buffer = this.getInterface().find( '.mw_buffer' );
+ // Update the buffer progress bar (if available )
+ if ( this.bufferedPercent != 0 ) {
+ // mw.log('Update buffer css: ' + ( this.bufferedPercent * 100 ) +
+ // '% ' + $buffer.length );
+ if ( this.bufferedPercent > 1 ){
+ this.bufferedPercent = 1;
+ }
+ $buffer.css({
+ "width" : ( this.bufferedPercent * 100 ) + '%'
+ });
+ $( this ).trigger( 'updateBufferPercent', this.bufferedPercent );
+ } else {
+ $buffer.css( "width", '0px' );
+ }
+
+ // if we have not already run the buffer start hook
+ if( this.bufferedPercent > 0 && !this.bufferStartFlag ) {
+ this.bufferStartFlag = true;
+ mw.log("EmbedPlayer::bufferStart");
+ $( this ).trigger( 'bufferStartEvent' );
+ }
+
+ // if we have not already run the buffer end hook
+ if( this.bufferedPercent == 1 && !this.bufferEndFlag){
+ this.bufferEndFlag = true;
+ $( this ).trigger( 'bufferEndEvent' );
+ }
+ },
+
+ /**
+ * Update the player playhead
+ *
+ * @param {Float}
+ * perc Value between 0 and 1 for position of playhead
+ */
+ updatePlayHead: function( perc ) {
+ //mw.log( 'EmbedPlayer: updatePlayHead: '+ perc);
+ if( this.getInterface() ){
+ var $playHead = this.getInterface().find( '.play_head' );
+ if ( !this.useNativePlayerControls() && $playHead.length != 0 ) {
+ var val = parseInt( perc * 1000 );
+ $playHead.slider( 'value', val );
+ }
+ }
+ $( this ).trigger('updatePlayHeadPercent', perc);
+ },
+
+
+ /**
+ * Helper Functions for selected source
+ */
+
+ /**
+ * Get the current selected media source or first source
+ *
+ * @param {Number}
+ * Requested time in seconds to be passed to the server if the
+ * server supports supportsURLTimeEncoding
+ * @return src url
+ */
+ getSrc: function( serverSeekTime ) {
+ if( serverSeekTime ){
+ this.serverSeekTime = serverSeekTime;
+ }
+ if( this.currentTime && !this.serverSeekTime){
+ this.serverSeekTime = this.currentTime;
+ }
+
+ // No media element we can't return src
+ if( !this.mediaElement ){
+ return false;
+ }
+
+ // If no source selected auto select the source:
+ if( !this.mediaElement.selectedSource ){
+ this.mediaElement.autoSelectSource();
+ };
+
+ // Return selected source:
+ if( this.mediaElement.selectedSource ){
+ // See if we should pass the requested time to the source generator:
+ if( this.supportsURLTimeEncoding() ){
+ // get the first source:
+ return this.mediaElement.selectedSource.getSrc( this.serverSeekTime );
+ } else {
+ return this.mediaElement.selectedSource.getSrc();
+ }
+ }
+ // No selected source return false:
+ return false;
+ },
+ /**
+ * Return the currently selected source
+ */
+ getSource: function(){
+ // update the current selected source:
+ this.mediaElement.autoSelectSource();
+ return this.mediaElement.selectedSource;
+ },
+ /**
+ * Static helper to get media sources from a set of videoFiles
+ *
+ * Uses mediaElement select logic to chose a
+ * video file among a set of sources
+ *
+ * @param videoFiles
+ * @return
+ */
+ getCompatibleSource: function( videoFiles ){
+ // Convert videoFiles json into HTML element:
+ // TODO mediaElement should probably accept JSON
+ var $media = $('<video />');
+ $.each(videoFiles, function( inx, source){
+ $media.append( $('<source />').attr({
+ 'src' : source.src,
+ 'type' : source.type
+ }));
+ mw.log("EmbedPlayer::getCompatibleSource: add " + source.src + ' of type:' + source.type );
+ });
+ var myMediaElement = new mw.MediaElement( $media[0] );
+ var source = myMediaElement.autoSelectSource();
+ if( source ){
+ mw.log("EmbedPlayer::getCompatibleSource: " + source.getSrc());
+ return source;
+ }
+ mw.log("Error:: could not find compatible source");
+ return false;
+ },
+ /**
+ * If the selected src supports URL time encoding
+ *
+ * @return {Boolean} true if the src supports url time requests false if the
+ * src does not support url time requests
+ */
+ supportsURLTimeEncoding: function() {
+ var timeUrls = mw.config.get('EmbedPlayer.EnableURLTimeEncoding') ;
+ if( timeUrls == 'none' ){
+ return false;
+ } else if( timeUrls == 'always' ){
+ return this.mediaElement.selectedSource.URLTimeEncoding;
+ } else if( timeUrls == 'flash' ){
+ if( this.mediaElement.selectedSource && this.mediaElement.selectedSource.URLTimeEncoding){
+ // see if the current selected player is flash:
+ return ( this.instanceOf == 'Kplayer' );
+ }
+ } else {
+ mw.log("Error:: invalid config value for EmbedPlayer.EnableURLTimeEncoding:: " + mw.config.get('EmbedPlayer.EnableURLTimeEncoding') );
+ }
+ return false;
+ }
+ };
+
+})( mw, jQuery );