summaryrefslogtreecommitdiff
path: root/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js')
-rw-r--r--extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js2721
1 files changed, 2721 insertions, 0 deletions
diff --git a/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js
new file mode 100644
index 00000000..5ef54d4f
--- /dev/null
+++ b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js
@@ -0,0 +1,2721 @@
+/**
+* Msg text is inherited from embedPlayer
+*/
+
+( function( mw, $ ) { "use strict";
+/**
+* mw.PlayerControlBuilder object
+* @param the embedPlayer element we are targeting
+*/
+mw.PlayerControlBuilder = function( embedPlayer, options ) {
+ return this.init( embedPlayer, options );
+};
+
+/**
+ * ControlsBuilder prototype:
+ */
+mw.PlayerControlBuilder.prototype = {
+ //Default Local values:
+
+ // Parent css Class name
+ playerClass : 'mv-player',
+
+ // Long string display of time value
+ longTimeDisp: true,
+
+ // Default volume layout is "vertical"
+ volumeLayout : 'vertical',
+
+ // Default control bar height
+ height: mw.config.get( 'EmbedPlayer.ControlsHeight' ),
+
+ // Default supported components is merged with embedPlayer set of supported types
+ supportedComponents: {
+ // All playback types support options
+ 'options': true
+ },
+
+ // Default supported menu items is merged with skin menu items
+ supportedMenuItems: {
+ // Player Select
+ 'playerSelect' : true,
+
+ // Download the file menu
+ 'download' : true,
+
+ // Share the video menu
+ 'share' : true,
+
+ // Player library link
+ 'aboutPlayerLibrary': true
+ },
+
+ // Flag to store the current fullscreen mode
+ inFullScreen: false,
+
+ // Flag to store if a warning binding has been added
+ addWarningFlag: false,
+
+ // Flag to store state of overlay on player
+ displayOptionsMenuFlag: false,
+
+ // Local storage of ControlBar Callback
+ hideControlBarCallback: false,
+
+ // Flag to store controls status (disabled/enabled)
+ controlsDisabled: false,
+
+ // binding postfix
+ bindPostfix: '.controlBuilder',
+
+ /**
+ * Initialization Object for the control builder
+ *
+ * @param {Object} embedPlayer EmbedPlayer interface
+ */
+ init: function( embedPlayer ) {
+ var _this = this;
+ this.embedPlayer = embedPlayer;
+ // Check for skin overrides for controlBuilder
+ var skinClass = embedPlayer.skinName.substr(0,1).toUpperCase() + embedPlayer.skinName.substr( 1 );
+ if ( mw['PlayerSkin' + skinClass ] ) {
+ // Clone as to not override prototype with the skin config
+ _this = $.extend( true, { }, this, mw['PlayerSkin' + skinClass ] );
+ }
+ if ( _this.embedPlayer.mediaElement.getPlayableSources().length <= 1
+ && _this.supportedMenuItems.playerSelect ) {
+ delete _this.supportedMenuItems.playerSelect;
+ }
+ // Return the controlBuilder Object:
+ return _this;
+ },
+
+ /**
+ * Get the control bar height
+ * @return {Number} control bar height
+ */
+ getHeight: function(){
+ return this.height;
+ },
+
+
+ /**
+ * Add the controls html to player interface
+ */
+ addControls: function() {
+ // Set up local pointer to the embedPlayer
+ var embedPlayer = this.embedPlayer,
+ profile = $.client.profile();
+
+ // Set up local controlBuilder
+ var _this = this;
+
+ // Remove any old controls & old overlays:
+ embedPlayer.getInterface().find( '.control-bar,.overlay-win' ).remove();
+
+ // Reset flags:
+ _this.displayOptionsMenuFlag = false;
+
+ // Setup the controlBar container ( starts hidden )
+ var $controlBar = $('<div />')
+ .addClass( 'ui-state-default ui-widget-header ui-helper-clearfix control-bar' )
+ .css( 'height', this.height );
+
+ // Controls are hidden by default if overlaying controls:
+ if( _this.isOverlayControls() ){
+ $controlBar.hide();
+ } else {
+ // Include the control bar height when calculating the layout
+ $controlBar.addClass('block');
+ }
+
+ // Make room for audio controls in the interface:
+ if( embedPlayer.isAudio() && embedPlayer.getInterface().height() == 0 ){
+ embedPlayer.getInterface().css( {
+ 'height' : this.height
+ } );
+ }
+
+ // Add the controls to the interface
+ embedPlayer.getInterface().append( $controlBar );
+
+ if ( profile.name === 'firefox' && profile.versionNumber < 2 ) {
+ embedPlayer.triggerHelper( 'resizeIframeContainer', [ {'height' : embedPlayer.height + $controlBar.height() - 1} ] );
+ }
+
+ // Add the Controls Component
+ this.addControlComponents();
+
+ // Add top level Controls bindings
+ this.addControlBindings();
+ },
+
+ /**
+ * Add control components as defined per this.components
+ */
+ addControlComponents: function( ) {
+ var _this = this;
+
+ // Set up local pointer to the embedPlayer
+ var embedPlayer = this.embedPlayer;
+
+ //Set up local var to control container:
+ var $controlBar = embedPlayer.getInterface().find( '.control-bar' );
+
+ this.availableWidth = embedPlayer.getPlayerWidth();
+
+ mw.log( 'PlayerControlsBuilder:: addControlComponents into:' + this.availableWidth );
+ // Build the supportedComponents list
+ this.supportedComponents = $.extend( this.supportedComponents, embedPlayer.supports );
+
+ // Check for Attribution button
+ if( mw.config.get( 'EmbedPlayer.AttributionButton' ) && embedPlayer.attributionbutton ){
+ this.supportedComponents[ 'attributionButton' ] = true;
+ }
+ // Check global fullscreen enabled flag
+ if( mw.config.get( 'EmbedPlayer.EnableFullscreen' ) === false ){
+ this.supportedComponents[ 'fullscreen'] = false;
+ }
+ // Check if the options item is available
+ if( mw.config.get( 'EmbedPlayer.EnableOptionsMenu' ) === false ){
+ this.supportedComponents[ 'options'] = false;
+ }
+ // Check for volume control
+ if( mw.config.get( 'EmbedPlayer.EnableVolumeControl') === false ){
+ this.supportedComponents[ 'volumeControl'] = false;
+ }
+
+ // Check if we have multiple playable sources ( if only one source don't display source switch )
+
+ if( embedPlayer.mediaElement.getPlayableSources().length == 1 ){
+ this.supportedComponents[ 'sourceSwitch' ] = false;
+
+ }
+
+ // Give embeds option to explicitly disable components via flag
+ var source = embedPlayer.mediaElement.getPlayableSources()[0];
+ if ( !embedPlayer.disablecontrols && source ) {
+ embedPlayer.disablecontrols = source.disablecontrols;
+ }
+ if ( embedPlayer.disablecontrols ) {
+ embedPlayer.disablecontrols.split(',').forEach(function( key ) {
+ mw.log( 'PlayerControlBuilder:: disabled component via flag:' + key );
+ _this.supportedComponents[ key ] = false;
+ });
+ }
+
+ $( embedPlayer ).trigger( 'addControlBarComponent', this );
+
+ var components = [];
+ var largestPos = 0;
+ var addComponent = function( componentId ){
+ if ( _this.supportedComponents[ componentId ] ) {
+ if ( _this.availableWidth >= _this.components[ componentId ].w ) {
+ _this.availableWidth -= _this.components[ componentId ].w;
+ // Check if position is defined, if not, place at end of known positions
+ var position = _this.components[ componentId ].position ?
+ _this.components[ componentId ].position:
+ largestPos+1
+ if( position > largestPos ){
+ largestPos = position;
+ }
+ components.push({
+ 'id': componentId,
+ 'position': position
+ });
+ //mw.log(" availableWidth:" + _this.availableWidth + ' ' + componentId + ' took: ' + _this.components[ componentId ].w )
+ } else {
+ mw.log( 'PlayerControlBuilder:: Not enough space for control component:' + componentId );
+ }
+ }
+ };
+
+ var addComponents = function() {
+ components.sort(function(a, b) {
+ return b.position - a.position;
+ });
+ for(var i=0;i<components.length;i++) {
+ $controlBar.append(
+ _this.getComponent( components[ i ]['id'] )
+ );
+ }
+ }
+
+ // Output components
+ for ( var componentId in this.components ) {
+ // Check for (component === false ) and skip
+ if( this.components[ componentId ] === false ){
+ continue;
+ }
+
+ // Special case with playhead and time ( to make sure they are to the left of everything else )
+ if ( componentId == 'playHead' ){
+ continue;
+ }
+ if( componentId == 'timeDisplay' && !mw.config.get( 'EmbedPlayer.EnableTimeDisplay' ) ){
+ continue;
+ }
+
+ // Skip "fullscreen" button for assets or where height is 0px ( audio )
+ if( componentId == 'fullscreen' && this.embedPlayer.isAudio() ){
+ continue;
+ }
+ // Skip sourceSwitch if width < smalles derivative
+ if ( componentId == 'sourceSwitch' && this.availableWidth < 320) {
+ continue;
+ }
+ addComponent( componentId );
+ }
+ if( this.availableWidth > 30 ){
+ addComponent( 'playHead' );
+ }
+ addComponents();
+ $(embedPlayer).trigger( 'controlBarBuildDone' );
+ },
+
+ /**
+ * Get a window size for the player while preserving aspect ratio:
+ *
+ * @@TODO This has similar logic to mw.embedPlayerNative applyIntrinsicAspect we should look
+ * at merging their functionality.
+ *
+ * @param {object} windowSize
+ * object that set { 'width': {width}, 'height':{height} } of target window
+ * @return {object}
+ * css settings for fullscreen player
+ */
+ getAspectPlayerWindowCss: function( windowSize ) {
+ var embedPlayer = this.embedPlayer;
+ var _this = this;
+ // Setup target height width based on max window size
+ if( !windowSize ){
+ var windowSize = {
+ 'width' : $( window ).width(),
+ 'height' : $( window ).height()
+ };
+ }
+ windowSize.width = parseInt( windowSize.width );
+ windowSize.height = parseInt( windowSize.height );
+ // See if we need to leave space for control bar
+ if( !_this.isOverlayControls() ){
+ //targetHeight = targetHeight - this.height;
+ windowSize.height = windowSize.height - this.height;
+ }
+
+ // Set target width
+ var targetWidth = windowSize.width;
+ var targetHeight = targetWidth * ( 1 / _this.getIntrinsicAspect() );
+ // Check if it exceeds the height constraint:
+ if( targetHeight > windowSize.height ){
+ targetHeight = windowSize.height;
+ targetWidth = parseInt( targetHeight * _this.getIntrinsicAspect() );
+ }
+ var offsetTop = 0;
+ // Move the video down 1/2 of the difference of window height
+ offsetTop+= ( targetHeight < windowSize.height )? ( windowSize.height- targetHeight ) / 2 : 0;
+ // if the video is very tall in a short window adjust the size:
+ var offsetLeft = ( targetWidth < windowSize.width )? parseInt( windowSize.width- targetWidth ) / 2 : 0;
+
+ var position = (mw.isIOS4() && mw.isIphone()) ? 'static' : 'absolute';
+ mw.log( 'PlayerControlBuilder::getAspectPlayerWindowCss: ' + ' h:' + targetHeight + ' w:' + targetWidth + ' t:' + offsetTop + ' l:' + offsetLeft );
+ return {
+ 'position' : position,
+ 'height': parseInt( targetHeight ),
+ 'width' : parseInt( targetWidth ),
+ 'top' : parseInt( offsetTop ),
+ 'left': parseInt( offsetLeft)
+ };
+ },
+
+ /**
+ * Get the intrinsic aspect ratio of media ( width / height )
+ * @return {float}
+ * size object with width and height
+ */
+ getIntrinsicAspect: function(){
+ var vid = this.embedPlayer.getPlayerElement();
+ // Check for raw intrinsic media size:
+ if( vid && vid.videoWidth && vid.videoHeight ){
+ return vid.videoWidth / vid.videoHeight;
+ }
+
+ // See if we have source data attributes available:
+ if( this.embedPlayer.mediaElement &&
+ this.embedPlayer.mediaElement.selectedSource )
+ {
+ var ss = this.embedPlayer.mediaElement.selectedSource;
+ // See if we have a hardcoded aspect to the source ( Adaptive streams don't have width / height )
+ if( ss.aspect ){
+ return ss.aspect;
+ }
+
+ if( ss.width && ss.height ){
+ return ss.width / ss.height
+ }
+ }
+
+ // check for posterImage size: ( should have Intrinsic aspect size as well )
+ var img = this.embedPlayer.getInterface().find('.playerPoster')[0];
+ if( img && img.naturalWidth && img.naturalHeight){
+ return img.naturalWidth / img.naturalHeight
+ }
+
+ // if all else fails use embedPlayer.getWidth()
+ return this.embedPlayer.getWidth() / this.embedPlayer.getHeight()
+ },
+
+ /**
+ * Get the play button css
+ */
+ getPlayButtonPosition: function() {
+ var _this = this;
+ return {
+ 'position' : 'absolute',
+ 'left' : '50%',
+ 'top' : '50%',
+ 'margin-left' : - .5 * this.getComponentWidth( 'playButtonLarge' ),
+ 'margin-top' : - .5 * this.getComponentHeight( 'playButtonLarge' )
+ };
+ },
+
+ /**
+ * Check if we're in Fullscreen
+ * @return {boolean)
+ */
+ isInFullScreen: function() {
+ return this.inFullScreen;
+ },
+
+ /**
+ * Toggles full screen by calling
+ * doFullScreenPlayer to enable fullscreen mode
+ * restoreWindowPlayer to restore window mode
+ */
+ toggleFullscreen: function( forceClose ) {
+ var _this = this;
+ // Do normal in-page fullscreen handling:
+ if( this.isInFullScreen() ){
+ this.restoreWindowPlayer();
+ }else {
+ this.doFullScreenPlayer();
+ }
+ // Don't follow the # link:
+ return false;
+ },
+
+ /**
+ * Do full-screen mode
+ */
+ doFullScreenPlayer: function( callback ) {
+ mw.log("PlayerControlBuilder:: doFullScreenPlayer" );
+ // Setup pointer to control builder :
+ var _this = this,
+ profile = $.client.profile();
+
+ // Store the page vertical scroll
+ var doc = window.document;
+ var context = window;
+ this.verticalScrollPosition = doc.all ? doc.scrollTop : context.pageYOffset;
+
+ // Setup local reference to embed player:
+ var embedPlayer = this.embedPlayer;
+
+ // Setup a local reference to the player interface:
+ var $interface = embedPlayer.getInterface();
+ // Check fullscreen state ( if already true do nothing )
+ if( this.isInFullScreen() == true ){
+ return ;
+ }
+ this.inFullScreen = true;
+
+ // Add fullscreen class to interface:
+ $interface.addClass( 'fullscreen' );
+
+ // if overlaying controls add hide show player binding.
+ if( _this.isOverlayControls() && !embedPlayer.isTouchDevice() ){
+ _this.addFullscreenMouseMoveHideShowControls();
+ }
+
+ // Store the current scroll location on the iframe:
+ $( embedPlayer ).trigger( 'fullScreenStoreVerticalScroll' );
+
+ if( window.fullScreenApi.supportsFullScreen ) {
+ _this.preFullscreenPlayerSize = this.getPlayerSize();
+ var fullscreenHeight = null;
+ var fsTarget = this.getFsTarget();
+
+ var escapeFullscreen = function( event ) {
+ // grab the correct document target to check for fullscreen
+ if ( ! window.fullScreenApi.isFullScreen( window.document ) ) {
+ _this.restoreWindowPlayer();
+ }
+ }
+ // remove any old binding:
+ fsTarget.removeEventListener( fullScreenApi.fullScreenEventName, escapeFullscreen );
+ // Add a binding to catch "escape" fullscreen
+ fsTarget.addEventListener( fullScreenApi.fullScreenEventName, escapeFullscreen );
+ // Make the iframe fullscreen:
+ window.fullScreenApi.requestFullScreen( fsTarget );
+
+ // There is a bug with mozfullscreenchange event in all versions of firefox with supportsFullScreen
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=724816
+ // so we have to have an extra binding to check for size change and then restore.
+ if( profile.name === 'firefox' ){
+ _this.fullscreenRestoreCheck = setInterval( function(){
+ if( fullscreenHeight && $(window).height() < fullscreenHeight ){
+ // Mozilla triggered size change:
+ clearInterval ( _this.fullscreenRestoreCheck );
+ _this.restoreWindowPlayer();
+ }
+ // set fullscreen height:
+ if( ! fullscreenHeight && _this.preFullscreenPlayerSize.height != $(window).height() ){
+ fullscreenHeight = $(window).height();
+ }
+ }, 250 );
+ }
+ } else {
+ // Check for hybrid html controls / native fullscreen support:
+ var vid = this.embedPlayer.getPlayerElement();
+ if( mw.config.get('EmbedPlayer.EnableIpadNativeFullscreen')
+ &&
+ vid && vid.webkitSupportsFullscreen
+ ){
+ this.doHybridNativeFullscreen();
+ return ;
+ } else {
+ // make the player traget or iframe fullscreen
+ this.doContextTargetFullscreen();
+ }
+ }
+
+ // Bind escape to restore in page clip ( IE9 needs a secondary escape binding )
+ $( window ).keyup( function( event ) {
+ // Escape check
+ if( event.keyCode == 27 ){
+ _this.restoreWindowPlayer();
+ }
+ } );
+
+ // trigger the open fullscreen event:
+ $( embedPlayer ).trigger( 'onOpenFullScreen' );
+
+ // re draw the controls after a timeout ( to allow the screen dom to update )
+ setTimeout( function(){
+ _this.addControls();
+ },100)
+ },
+
+ /**
+ * Make the target player interface or iframe fullscreen
+ */
+ doContextTargetFullscreen: function() {
+ var
+ _this = this,
+ doc = window.document,
+ $doc = $( doc ),
+ $target = $( this.getFsTarget() ),
+ context = window;
+
+ // update / reset local restore properties
+ this.parentsAbsoluteList = [];
+ this.parentsRelativeList = [];
+
+ // Set the original parent page scale if possible:
+ this.orginalParnetViewPortContent = $doc.find( 'meta[name="viewport"]' ).attr( 'content' );
+ this.orginalTargetElementLayout = {
+ 'style' : $target[0].style.cssText,
+ 'width' : $target.width(),
+ 'height' : $target.height()
+ };
+
+ mw.log("PlayerControls:: doParentIframeFullscreen> verticalScrollPosition:" + this.verticalScrollPosition);
+ context.scroll(0, 0);
+
+ // Make sure the parent page page has a zoom of 1:
+ if( ! $doc.find('meta[name="viewport"]').length ){
+ $doc.find('head').append( $( '<meta />' ).attr('name', 'viewport') );
+ }
+ $doc.find('meta[name="viewport"]').attr('content', 'initial-scale=1; maximum-scale=1; minimum-scale=1;' );
+
+ // iPad 5 supports fixed position in a bad way, use absolute pos for iOS
+ var playerCssPosition = ( mw.isIOS() ) ? 'absolute': 'fixed';
+
+ // Remove absolute css of the $target's parents
+ $target.parents().each( function() {
+ var $parent = $( this );
+ if( $parent.css( 'position' ) == 'absolute' ) {
+ _this.parentsAbsoluteList.push( $parent );
+ $parent.css( 'position', 'static' );
+ }
+ if( $parent.css( 'position' ) == 'relative' ) {
+ _this.parentsRelativeList.push( $parent );
+ $parent.css( 'position', 'static' );
+ }
+ });
+
+ // Make the $target fullscreen
+ $target
+ .css({
+ 'z-index': mw.config.get( 'EmbedPlayer.FullScreenZIndex' ),
+ 'position': playerCssPosition,
+ 'top' : '0px',
+ 'left' : '0px',
+ 'margin': 0
+ })
+ .data(
+ 'isFullscreen', true
+ );
+
+ var updateTargetSize = function() {
+ context.scroll(0, 0);
+ $target.css({
+ 'width' : context.innerWidth,
+ 'height' : context.innerHeight
+ });
+ // update player size if needed:
+ _this.embedPlayer.applyIntrinsicAspect();
+ };
+
+ updateTargetSize();
+
+ // Bind orientation change to resize player ( if fullscreen )
+ $( context ).bind( 'orientationchange', function(e){
+ if( _this.isInFullScreen() ){
+ updateTargetSize();
+ }
+ });
+
+ // prevent scrolling when in fullscreen: ( both iframe and dom target use document )
+ document.ontouchmove = function( e ){
+ if( _this.isInFullScreen() ){
+ e.preventDefault();
+ }
+ };
+ },
+ /**
+ * Restore the player interface or iframe to a window player
+ */
+ restoreContextPlayer: function(){
+ var
+ _this = this,
+ doc = window.document,
+ $doc = $( doc ),
+ $target = $( this.getFsTarget() ),
+ context = window;
+
+ mw.log("PlayerControlsBuilder:: restoreContextPlayer> verticalScrollPosition:" + this.verticalScrollPosition );
+
+ // Restore document zoom:
+ if( this.orginalParnetViewPortContent ){
+ $doc.find('meta[name="viewport"]').attr('content', this.orginalParnetViewPortContent );
+ } else {
+ // Restore user zoom: ( NOTE, there does not appear to be a way to know the
+ // initial scale, so we just restore to 1 in the absence of explicit viewport tag )
+ // In order to restore zoom, we must set maximum-scale to a valid value
+ $doc.find('meta[name="viewport"]').attr('content', 'initial-scale=1; maximum-scale=8; minimum-scale=1;' );
+ }
+ if( this.orginalTargetElementLayout ) {
+ $target[0].style.cssText = this.orginalTargetElementLayout.style;
+ $target.attr({
+ 'width': this.orginalTargetElementLayout.width,
+ 'height': this.orginalTargetElementLayout.height
+ });
+ // update player size if needed:
+ _this.embedPlayer.applyIntrinsicAspect();
+ }
+ // Restore any parent absolute pos:
+ $doc.find( _this.parentsAbsoluteList ).each( function() {
+ $( this ).css( 'position', 'absolute' );
+ } );
+ $doc.find( _this.parentsRelativeList ).each( function() {
+ $( this ).css( 'position', 'relative' );
+ } );
+ },
+
+ /**
+ * Supports hybrid native fullscreen, player html controls, and fullscreen is native
+ */
+ doHybridNativeFullscreen: function(){
+ var vid = this.embedPlayer.getPlayerElement();
+ var _this = this;
+ vid.webkitEnterFullscreen();
+ // start to pull for exit fullscreen:
+ this.fsIntervalID = setInterval( function(){
+ var currentFS = vid.webkitDisplayingFullscreen;
+ // Check if we have entered fullscreen but the player
+ // has exited fullscreen with native controls click
+ if( _this.isInFullScreen() && !currentFS ){
+ // restore non-fullscreen player state
+ _this.inFullScreen = false;
+ // Trigger the onCloseFullscreen event:
+ $( _this.embedPlayer ).trigger( 'onCloseFullScreen' );
+ // stop polling for state change.
+ clearInterval( _this.fsIntervalID );
+ }
+ }, 250 );
+ },
+ getWindowSize: function(){
+ return {
+ 'width' : $(window).width(),
+ 'height' : $(window).height()
+ };
+ },
+ doDomFullscreen: function(){
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+ var $interface = embedPlayer.getInterface();
+ // Remove any old mw-fullscreen-overlay
+ $( '.mw-fullscreen-overlay' ).remove();
+
+ _this.preFullscreenPlayerSize = this.getPlayerSize();
+
+ // Add the css fixed fullscreen black overlay as a sibling to the video element
+ // iOS4 does not respect z-index
+ $interface.after(
+ $( '<div />' )
+ .addClass( 'mw-fullscreen-overlay' )
+ // Set some arbitrary high z-index
+ .css('z-index', mw.config.get( 'EmbedPlayer.FullScreenZIndex' ) )
+ .hide()
+ .fadeIn("slow")
+ );
+
+ // get the original interface to absolute positioned:
+ if( ! this.windowPositionStyle ){
+ this.windowPositionStyle = $interface.css( 'position' );
+ }
+ if( !this.windowZindex ){
+ this.windowZindex = $interface.css( 'z-index' );
+ }
+ // Get the base offset:
+ this.windowOffset = this.getWindowOffset();
+
+ // Change the z-index of the interface
+ $interface.css( {
+ 'position' : 'fixed',
+ 'z-index' : mw.config.get( 'EmbedPlayer.FullScreenZIndex' ) + 1,
+ 'top' : this.windowOffset.top,
+ 'left' : this.windowOffset.left
+ } );
+
+ // If native persistent native player update z-index:
+ if( embedPlayer.isPersistentNativePlayer() ){
+ $( embedPlayer.getPlayerElement() ).css( {
+ 'z-index': mw.config.get( 'EmbedPlayer.FullScreenZIndex' ) + 1,
+ 'position': 'absolute'
+ });
+ }
+
+ // Empty out the parent absolute index
+ _this.parentsAbsolute = [];
+
+ // Hide the body scroll bar
+ $('body').css( 'overflow', 'hidden' );
+
+ var topOffset = '0px';
+ var leftOffset = '0px';
+
+ // Check if we have an offsetParent
+ if( $interface.offsetParent()[0].tagName
+ &&
+ $interface.offsetParent()[0].tagName.toLowerCase() != 'body' )
+ {
+ topOffset = -this.windowOffset.top + 'px';
+ leftOffset = -this.windowOffset.left + 'px';
+ }
+
+ // Overflow hidden in fullscreen:
+ $interface.css( 'overlow', 'hidden' );
+
+ // Remove absolute css of the interface parents
+ $interface.parents().each( function() {
+ //mw.log(' parent : ' + $( this ).attr('id' ) + ' class: ' + $( this ).attr('class') + ' pos: ' + $( this ).css( 'position' ) );
+ if( $( this ).css( 'position' ) == 'absolute' ) {
+ _this.parentsAbsolute.push( $( this ) );
+ $( this ).css( 'position', null );
+ mw.log( 'PlayerControlBuilder:: should update position: ' + $( this ).css( 'position' ) );
+ }
+ });
+
+ // Bind escape to restore in page clip
+ $( window ).keyup( function( event ) {
+ // Escape check
+ if( event.keyCode == 27 ){
+ _this.restoreWindowPlayer();
+ }
+ } );
+ },
+ addFullscreenMouseMoveHideShowControls:function(){
+ var _this = this;
+ // Bind mouse move in interface to hide control bar
+ _this.mouseMovedFlag = false;
+ _this.embedPlayer.getInterface().mousemove( function(e){
+ _this.mouseMovedFlag = true;
+ });
+
+ // Check every 2 seconds reset flag status if controls are overlay
+ var checkMovedMouse = function(){
+ if( _this.isInFullScreen() ){
+ if( _this.mouseMovedFlag ){
+ _this.mouseMovedFlag = false;
+ _this.showControlBar();
+ // Once we move the mouse keep displayed for 3 seconds
+ setTimeout(checkMovedMouse, 3000);
+ } else {
+ // Check for mouse movement every 250ms
+ _this.hideControlBar();
+ setTimeout(checkMovedMouse, 250 );
+ }
+ return;
+ }
+ };
+ // always initially show the control bar:
+ _this.showControlBar();
+ // start monitoring for moving mouse
+ checkMovedMouse();
+ },
+ getWindowOffset: function(){
+ var windowOffset = this.embedPlayer.getInterface().offset();
+ windowOffset.top = windowOffset.top - $(document).scrollTop();
+ windowOffset.left = windowOffset.left - $(document).scrollLeft();
+ this.windowOffset = windowOffset;
+ return this.windowOffset;
+ },
+ // Display a fullscreen tip if configured to do and the browser supports it.
+ displayFullscreenTip: function(){
+ var _this = this;
+ // Mobile devices don't have f11 key
+ if( mw.isMobileDevice() ){
+ return ;
+ }
+ // Safari does not have a DOM fullscreen ( no subtitles, no controls )
+ if ( $.client.profile().name === 'safari' ) {
+ return;
+ }
+
+ // OSX has a different short cut than windows and liux
+ var toolTipMsg = ( navigator.userAgent.indexOf('Mac OS X') != -1 )?
+ mw.msg( 'mwe-embedplayer-fullscreen-tip-osx') :
+ mw.msg( 'mwe-embedplayer-fullscreen-tip');
+
+ var $targetTip = this.addWarningBinding( 'EmbedPlayer.FullscreenTip',
+ $('<h3/>').html(
+ toolTipMsg
+ )
+ );
+
+ // Display the target warning:
+ $targetTip.show();
+
+ var hideTip = function(){
+ mw.setConfig('EmbedPlayer.FullscreenTip', false );
+ $targetTip.fadeOut('fast');
+ };
+
+ // Hide fullscreen tip if:
+ // We leave fullscreen,
+ $( this.embedPlayer ).bind( 'onCloseFullScreen', hideTip );
+ // After 5 seconds,
+ setTimeout( hideTip, 5000 );
+ // Or if we catch an f11 button press
+ $( document ).keyup( function( event ){
+ if( event.keyCode == 122 ){
+ hideTip();
+ }
+ return true;
+ });
+ },
+ // TOOD fullscreen iframe vs inpage object abstraction
+ //( avoid repatiave conditionals in getters )
+ getPlayerSize: function(){
+ var height = $(window).height() - this.getHeight();
+ if( mw.config.get('EmbedPlayer.IsIframeServer' ) ){
+ return {
+ 'height' : height,
+ 'width' : $(window).width()
+ }
+ } else {
+ return {
+ 'height' : this.embedPlayer.getInterface().height(),
+ 'width' : this.embedPlayer.getInterface().width()
+ }
+ }
+ },
+ getFsTarget: function(){
+ var $interface = this.embedPlayer.getInterface();
+ return $interface[0];
+ },
+ /**
+ * Restore the window player
+ */
+ restoreWindowPlayer: function() {
+ var _this = this;
+ mw.log("PlayerControlBuilder :: restoreWindowPlayer" );
+ var embedPlayer = this.embedPlayer;
+
+ // Check if fullscreen mode is already restored:
+ if( this.isInFullScreen() === false ){
+ return ;
+ }
+ // Set fullscreen mode to false
+ this.inFullScreen = false;
+
+ // remove the fullscreen interface
+ embedPlayer.getInterface().removeClass( 'fullscreen' );
+
+ // Check for native support for fullscreen and support native fullscreen restore
+ if ( window.fullScreenApi.supportsFullScreen ) {
+ var fsTarget = this.getFsTarget();
+ window.fullScreenApi.cancelFullScreen( fsTarget );
+ }
+
+ // Restore the iFrame context player
+ this.restoreContextPlayer();
+
+ // Restore scrolling on iPad
+ $( document ).unbind( 'touchend.fullscreen' );
+
+ // Trigger the onCloseFullscreen event:
+ $( embedPlayer ).trigger( 'onCloseFullScreen' );
+
+ // Scroll back to the previews position ( do in async call to allow dom fullscreen restore )
+ setTimeout( function(){
+ window.scroll( 0, _this.verticalScrollPosition );
+ }, 100 );
+
+ // re draw the controls after a timeout ( to allow the screen dom to update )
+ setTimeout( function(){
+ _this.addControls();
+ },100)
+ },
+ restoreDomPlayer: function(){
+ var _this = this;
+ // local ref to embedPlayer:
+ var embedPlayer = this.embedPlayer;
+
+ var $interface = embedPlayer.$interface;
+ var interfaceHeight = ( _this.isOverlayControls() )
+ ? embedPlayer.getHeight()
+ : embedPlayer.getHeight() + _this.getHeight();
+
+ mw.log( 'restoreWindowPlayer:: h:' + interfaceHeight + ' w:' + embedPlayer.getWidth());
+ $('.mw-fullscreen-overlay').remove( 'slow' );
+
+ mw.log( 'restore embedPlayer:: ' + embedPlayer.getWidth() + ' h: ' + embedPlayer.getHeight() );
+
+ // Restore the player:
+ embedPlayer.getInterface().css( {
+ 'width' : _this.preFullscreenPlayerSize.width,
+ 'height' : _this.preFullscreenPlayerSize.height
+ });
+ var topPos = {
+ 'position' : _this.windowPositionStyle,
+ 'z-index' : _this.windowZindex,
+ 'overlow' : 'visible',
+ 'top' : '0px',
+ 'left' : '0px'
+ };
+ // Restore non-absolute layout:
+ $( [ $interface, $interface.find('.playerPoster'), embedPlayer ] ).css( topPos );
+ if( embedPlayer.getPlayerElement() ){
+ $( embedPlayer.getPlayerElement() )
+ .css( topPos )
+ }
+ // Restore the body scroll bar
+ $('body').css( 'overflow', 'auto' );
+
+ // If native player restore z-index:
+ if( embedPlayer.isPersistentNativePlayer() ){
+ $( embedPlayer.getPlayerElement() ).css( {
+ 'z-index': 'auto'
+ });
+ }
+ },
+ /**
+ * Get minimal width for interface overlay
+ */
+ getOverlayWidth: function( ) {
+ return ( this.embedPlayer.getPlayerWidth() < 300 )? 300 : this.embedPlayer.getPlayerWidth();
+ },
+
+ /**
+ * Get minimal height for interface overlay
+ */
+ getOverlayHeight: function( ) {
+ return ( this.embedPlayer.getPlayerHeight() < 200 )? 200 : this.embedPlayer.getPlayerHeight();
+ },
+
+ /**
+ * addControlBindings
+ * Adds control hooks once controls are in the DOM
+ */
+ addControlBindings: function( ) {
+ // Set up local pointer to the embedPlayer
+ var embedPlayer = this.embedPlayer,
+ _this = this,
+ $interface = embedPlayer.getInterface(),
+ profile = $.client.profile();
+
+ _this.onControlBar = false;
+
+ // Remove any old interface bindings
+ $( embedPlayer ).unbind( this.bindPostfix );
+
+ var bindFirstPlay = false;
+ _this.addRightClickBinding();
+
+ // add the player click bindings
+ _this.addPlayerClickBindings();
+
+ // Bind into play.ctrl namespace ( so we can unbind without affecting other play bindings )
+ $( embedPlayer ).bind( 'onplay' + this.bindPostfix, function() { //Only bind once played
+ // add right click binding again ( in case the player got swaped )
+ embedPlayer.controlBuilder.addRightClickBinding();
+ });
+
+ $( embedPlayer ).bind( 'timeupdate' + this.bindPostfix, function(){
+ embedPlayer.updatePlayheadStatus()
+ });
+
+ // Update buffer information
+ $( embedPlayer ).bind( 'progress' + this.bindPostfix, function( event, jEvent, id){
+ // regain scope
+ var embedPlayer = $( '#' + id )[0];
+ embedPlayer.updateBufferStatus();
+ });
+
+ // Bind to EnableInterfaceComponents
+ $( embedPlayer ).bind( 'onEnableInterfaceComponents' + this.bindPostfix, function() {
+ embedPlayer.controlBuilder.controlsDisabled = false;
+ embedPlayer.controlBuilder.addPlayerClickBindings();
+ });
+
+ // Bind to DisableInterfaceComponents
+ $( embedPlayer ).bind( 'onDisableInterfaceComponents' + this.bindPostfix, function() {
+ embedPlayer.controlBuilder.controlsDisabled = true;
+ embedPlayer.controlBuilder.removePlayerClickBindings();
+ });
+
+
+ // TODO select a player on the page
+ var bindSpaceUp = function(){
+ $(window).bind('keyup' + _this.bindPostfix, function(e) {
+ if( e.keyCode == 32 ) {
+ if(embedPlayer.paused) {
+ embedPlayer.play();
+ } else {
+ embedPlayer.pause();
+ }
+ return false;
+ }
+ });
+ };
+
+ var bindSpaceDown = function() {
+ $(window).unbind( 'keyup' + _this.bindPostfix );
+ };
+
+ // Bind to resize event
+ /*
+ var triggerUpdate;
+ $( window ).resize(function() {
+ // We use setTimeout because of iOS 4.2 issues
+ clearTimeout(triggerUpdate);
+ triggerUpdate = setTimeout(function() {
+ //embedPlayer.triggerHelper('updateLayout');
+ }, 100);
+ });
+ */
+
+ $(window).on("debouncedresize", function() {
+ embedPlayer.triggerHelper('updateLayout');
+ });
+
+ // Add hide show bindings for control overlay (if overlay is enabled )
+ if( ! _this.isOverlayControls() ) {
+ $interface
+ .show()
+ .hover( bindSpaceUp, bindSpaceDown );
+
+ // include touch start pause binding
+ $( embedPlayer).bind( 'touchstart' + this.bindPostfix, function() {
+ embedPlayer._playContorls = true;
+ mw.log( "PlayerControlBuilder:: touchstart:" + ' isPause:' + embedPlayer.paused);
+ if( embedPlayer.paused ) {
+ embedPlayer.play();
+ } else {
+ embedPlayer.pause();
+ }
+ });
+ } else { // hide show controls:
+ // Bind a startTouch to show controls
+ $( embedPlayer).bind( 'touchstart' + this.bindPostfix, function() {
+ if ( embedPlayer.getInterface().find( '.control-bar' ).is( ':visible' ) ) {
+ if( embedPlayer.paused ) {
+ embedPlayer.play();
+ } else {
+ embedPlayer.pause();
+ }
+ } else {
+ _this.showControlBar();
+ }
+ clearTimeout( _this.hideControlBarCallback );
+ _this.hideControlBarCallback = setTimeout( function() {
+ _this.hideControlBar();
+ }, 60000 );
+ // ( Once the user touched the video "don't hide" )
+ return true;
+ } );
+
+ var hoverIntentConfig = {
+ 'sensitivity': 100,
+ 'timeout' : 1000,
+ 'over' : function(e){
+ // Clear timeout on IE9
+ if( mw.isIE9() ) {
+ clearTimeout(_this.hideControlBarCallback);
+ _this.hideControlBarCallback = false;
+ }
+ // Show controls with a set timeout ( avoid fade in fade out on short mouse over )
+ _this.showControlBar();
+ bindSpaceUp();
+ },
+ 'out' : function(e){
+ _this.hideControlBar();
+ bindSpaceDown();
+ }
+ };
+
+ // Check if we should display the interface:
+ // special check for IE9 ( does not count hover on non-visiable inerface div
+ if( mw.isIE9() ){
+ $( embedPlayer.getPlayerElement() ).hoverIntent( hoverIntentConfig );
+
+ // Add hover binding to control bar
+ embedPlayer.getInterface().find( '.control-bar' ).hover( function(e) {
+ _this.onControlBar = true;
+ embedPlayer.getInterface().find( '.control-bar' ).show();
+ }, function( e ) {
+ if (!_this.hideControlBarCallback) {
+ _this.hideControlBarCallback = setTimeout(function(){
+ _this.hideControlBar();
+ },1000);
+ }
+ _this.onControlBar = false;
+ });
+
+ } else {
+ if ( !mw.isIpad() ) {
+ $interface.hoverIntent( hoverIntentConfig );
+ }
+ }
+
+ }
+
+ // Add recommend firefox if we have non-native playback:
+ if ( _this.checkNativeWarning( ) ) {
+ _this.addWarningBinding(
+ 'EmbedPlayer.ShowNativeWarning',
+ mw.msg( 'mwe-embedplayer-for_best_experience',
+ $('<div>').append(
+ $('<a />')
+ .attr({
+ 'href': 'http://www.mediawiki.org/wiki/Extension:TimedMediaHandler/Client_download',
+ 'target' : '_new'
+ })
+ )[0].innerHTML
+ )
+ );
+ }
+
+ // Do png fix for ie6
+ if ( profile.name === 'msie' && profile.versionNumber <= 6 ) {
+ $( '#' + embedPlayer.id + ' .play-btn-large' ).pngFix();
+ }
+
+ this.doVolumeBinding();
+
+ // Check if we have any custom skin Bindings to run
+ if ( this.addSkinControlBindings && typeof( this.addSkinControlBindings ) == 'function' ){
+ this.addSkinControlBindings();
+ }
+
+ mw.log( 'trigger::addControlBindingsEvent' );
+ $( embedPlayer ).trigger( 'addControlBindingsEvent' );
+ },
+ removePlayerClickBindings: function(){
+ $( this.embedPlayer )
+ .unbind( "click" + this.bindPostfix )
+ .unbind( "dblclick" + this.bindPostfix );
+ },
+ addPlayerClickBindings: function(){
+
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+
+ // prevent scrolling when in fullscreen:
+ document.ontouchmove = function( e ){
+ if( _this.isInFullScreen() ){
+ e.preventDefault();
+ }
+ };
+ // Remove old click bindings before adding:
+ this.removePlayerClickBindings();
+
+ // Setup "dobuleclick" fullscreen binding to embedPlayer ( if enabled )
+ if ( this.supportedComponents['fullscreen'] ){
+ $( embedPlayer ).bind( "dblclick" + _this.bindPostfix, function(){
+ embedPlayer.fullscreen();
+ });
+ }
+
+ var dblClickTime = 300;
+ var lastClickTime = 0;
+ var didDblClick = false;
+
+ var playerClickCb = function( event ) {
+ // make sure the event matches:
+ if( event.currentTarget.id != embedPlayer.id ){
+ embedPlayer = $( '#' + event.currentTarget.id )[0];
+ }
+ mw.log( "PlayerControlBuilder:: click:" + embedPlayer.id + ' isPause:' + embedPlayer.paused);
+ // Don't do anything if touch interface or native controls are shown
+ if( embedPlayer.useNativePlayerControls()
+ ||
+ _this.isControlsDisabled()
+ ||
+ embedPlayer.isTouchDevice()
+ ) {
+ return true;
+ }
+ var clickTime = new Date().getTime();
+ if( clickTime -lastClickTime < dblClickTime ) {
+ didDblClick = true;
+ setTimeout( function(){
+ didDblClick = false;
+ }, dblClickTime + 10 );
+ }
+ lastClickTime = clickTime;
+ setTimeout( function(){
+ // check if no click has since the time we called the setTimeout
+ if( !didDblClick ){
+ if( embedPlayer.paused ) {
+ embedPlayer.play();
+ } else {
+ embedPlayer.pause();
+ }
+ }
+ }, dblClickTime );
+ return true;
+ };
+ // Add click binding: ( $(embedPlayer).click ) has scope issues )
+ if ( embedPlayer.attachEvent ) {
+ embedPlayer.attachEvent("onclick", playerClickCb);
+ } else{
+ // Firefox 3.5 requires third argument to addEventListener
+ embedPlayer.addEventListener('click', playerClickCb, false );
+ }
+
+ },
+ addRightClickBinding: function(){
+ var embedPlayer = this.embedPlayer;
+ // check config:
+ if( mw.config.get( 'EmbedPlayer.EnableRightClick') === false ){
+ document.oncontextmenu= function(e){return false;};
+ $(embedPlayer).mousedown(function(e){
+ if( e.button == 2 ) {
+ return false;
+ }
+ });
+ }
+ },
+ /**
+ * Hide the control bar.
+ */
+ hideControlBar : function(){
+ var animateDuration = 'fast';
+ var _this = this;
+
+ // Do not hide control bar if overlay menu item is being displayed:
+ if( _this.displayOptionsMenuFlag || _this.keepControlBarOnScreen ) {
+ setTimeout( function(){
+ _this.hideControlBar();
+ }, 200 );
+ return ;
+ }
+
+ // IE9: If the user mouse is on the control bar, don't hide it
+ if( this.onControlBar === true ) {
+ return ;
+ }
+
+ // Hide the control bar
+ this.embedPlayer.getInterface().find( '.control-bar')
+ .fadeOut( animateDuration );
+ //mw.log('about to trigger hide control bar')
+ // Allow interface items to update:
+ $( this.embedPlayer ).trigger('onHideControlBar', [ {'bottom' : 15}, this.embedPlayer.id ] );
+
+ },
+ restoreControlsHover:function(){
+ if( this.isOverlayControls() ){
+ this.keepControlBarOnScreen = false;
+ }
+ },
+ /**
+ * Show the control bar
+ */
+ showControlBar: function( keepOnScreen ){
+ var animateDuration = 'fast';
+ if(! this.embedPlayer )
+ return ;
+
+ if( this.embedPlayer.getPlayerElement && ! this.embedPlayer.isPersistentNativePlayer() ){
+ $( this.embedPlayer.getPlayerElement() ).css( 'z-index', '1' );
+ }
+ mw.log( 'PlayerControlBuilder:: ShowControlBar, keep on screen: ' + keepOnScreen );
+
+ // Show interface controls
+ this.embedPlayer.getInterface().find( '.control-bar' )
+ .fadeIn( animateDuration );
+
+ if( keepOnScreen ){
+ this.keepControlBarOnScreen = true;
+ }
+
+ // Trigger the screen overlay with layout info:
+ $( this.embedPlayer ).trigger( 'onShowControlBar', [{
+ 'bottom' : this.getHeight() + 15
+ }, this.embedPlayer.id ] );
+ },
+
+ /**
+ * Checks if the browser supports overlays and the controlsOverlay is
+ * set to true for the player or via config
+ */
+ isOverlayControls: function(){
+ //if the player "supports" overlays:
+ if( ! this.embedPlayer.supports['overlays'] ){
+ return false;
+ }
+
+ // If disabled via the player
+ if( this.embedPlayer.overlaycontrols === false ){
+ return false;
+ }
+
+ // Don't overlay controls if in audio mode:
+ if( this.embedPlayer.isAudio() ){
+ return false;
+ }
+
+
+ // If the config is false
+ if( mw.config.get( 'EmbedPlayer.OverlayControls' ) === false){
+ return false;
+ }
+
+ if( this.embedPlayer.controls === false ){
+ return false;
+ }
+
+ // Past all tests OverlayControls is true:
+ return true;
+ },
+
+ /* Check if the controls are disabled */
+
+ isControlsDisabled: function() {
+ return this.controlsDisabled;
+ },
+
+ /**
+ * Check if a warning should be issued to non-native playback systems
+ *
+ * dependent on mediaElement being setup
+ */
+ checkNativeWarning: function( ) {
+ if( mw.config.get( 'EmbedPlayer.ShowNativeWarning' ) === false ){
+ return false;
+ }
+
+ // Don't show for imageOverlay player:
+ if( this.embedPlayer.instanceOf == 'ImageOverlay' ){
+ return false;
+ }
+
+ // If the resolution is too small don't display the warning
+ if( parseInt( this.embedPlayer.getPlayerHeight() ) < 199 ){
+ return false;
+ }
+
+ // See if we have we have native support
+ if( this.embedPlayer.instanceOf == 'Native' ){
+ return false;
+ }
+
+ // Not a lot of good options for an iPhone
+ if( this.embedPlayer.instanceOf == 'VLCApp' ){
+ return false;
+ }
+ if( this.embedPlayer.instanceOf == 'OgvJs' ){
+ return false;
+ }
+
+ // Chrome's webM support is oky though:
+ if( /chrome/.test(navigator.userAgent.toLowerCase() ) &&
+ mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/webm' ).length ){
+ return false;
+ }
+
+
+ // Check for h264 and or flash/flv source and playback support and don't show warning
+ if(
+ ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/h264' ).length
+ && this.embedPlayer.mediaElement.getSources( 'video/h264' ).length )
+ ||
+ ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'video/x-flv' ).length
+ && this.embedPlayer.mediaElement.getSources( 'video/x-flv' ).length )
+ ||
+ ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'application/vnd.apple.mpegurl' ).length
+ && this.embedPlayer.mediaElement.getSources( 'application/vnd.apple.mpegurl' ).length )
+ ||
+ ( mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'audio/mpeg' ).length
+ && this.embedPlayer.mediaElement.getSources( 'audio/mpeg' ).length )
+ ){
+ // No firefox link if a h.264 or flash/flv stream is present
+ return false;
+ }
+
+ // Should issue the native warning
+ return true;
+ },
+
+ /**
+ * Does a native warning check binding to the player on mouse over.
+ * @param {string} preferenceId The preference Id
+ * @param {object} warningMsg The jQuery object warning message to be displayed.
+ *
+ */
+ /**
+ * Display a warning message on the player
+ * checks a preference Id to enable or disable it.
+ * @param {string} preferenceId The preference Id
+ * @param {object} warningMsg The jQuery object warning message to be displayed.
+ * @param {boolean} if the hide ui should be exposed
+ *
+ */
+ addWarningBinding: function( preferenceId, warningMsg, hideDisableUi ) {
+ mw.log( 'mw.PlayerControlBuilder: addWarningBinding: ' + preferenceId + ' wm: ' + warningMsg);
+ // Set up local pointer to the embedPlayer
+ var embedPlayer = this.embedPlayer;
+ var _this = this;
+ // make sure the player is large enough
+ if( embedPlayer.getWidth() < 200 ){
+ return false;
+ }
+
+ // Can be uncommented to reset hide prefrence
+ //$.cookie( preferenceId, '' );
+
+ // Check if a cookie has been set to hide the warning:
+ if ( mw.config.get( preferenceId ) === true && $.cookie( preferenceId ) == 'hidewarning' ){
+ return ;
+ }
+
+ var warnId = "warningOverlay_" + embedPlayer.id;
+ $( '#' + warnId ).remove();
+
+ // Add the targetWarning:
+ var $targetWarning = $('<div />')
+ .attr( {
+ 'id': warnId
+ } )
+ .addClass( 'ui-corner-all' )
+ .css({
+ 'position' : 'absolute',
+ 'background' : '#FFF',
+ 'color' : '#111',
+ 'top' : '10px',
+ 'left' : '10px',
+ 'right' : '10px',
+ 'padding' : '4px',
+ // z-index should be > than play button, as well as greater
+ // than the dialog box (in pop up video), or link won't work.
+ 'z-index' : '1502',
+ })
+ .html( warningMsg );
+
+ embedPlayer.getInterface().append(
+ $targetWarning
+ );
+
+ $targetWarning.append(
+ $('<br />')
+ );
+ // check if we should show the checkbox
+ if( !hideDisableUi ){
+
+ $targetWarning.append(
+ $( '<input type="checkbox" />' )
+ .attr({
+ 'id' : 'ffwarn_' + embedPlayer.id,
+ 'name' : 'ffwarn_' + embedPlayer.id
+ })
+ .click( function() {
+ mw.log("WarningBindinng:: set " + preferenceId + ' to hidewarning ' );
+ // Set up a cookie for 30 days:
+ $.cookie( preferenceId, 'hidewarning', {expires: 30} );
+ // Set the current instance
+ mw.setConfig( preferenceId, false );
+ $( '#warningOverlay_' + embedPlayer.id ).fadeOut( 'slow' );
+ // set the local preference to false
+ _this.addWarningFlag = false;
+ } )
+ );
+ $targetWarning.append(
+ $('<label />')
+ .text( mw.msg( 'mwe-embedplayer-do_not_warn_again' ) )
+ .attr( 'for', 'ffwarn_' + embedPlayer.id )
+ );
+ }
+
+ return $targetWarning;
+ },
+
+ /**
+ * Binds the volume controls
+ */
+ doVolumeBinding: function( ) {
+ var embedPlayer = this.embedPlayer;
+ var _this = this;
+ embedPlayer.getInterface().find( '.volume_control' ).unbind().buttonHover().click( function() {
+ mw.log( 'Volume control toggle' );
+ embedPlayer.toggleMute();
+ } );
+
+ // Add vertical volume display hover
+ if ( this.volumeLayout == 'vertical' ) {
+ // Default volume binding:
+ var hoverOverDelay = false;
+ var $targetvol = embedPlayer.getInterface().find( '.vol_container' ).hide();
+ embedPlayer.getInterface().find( '.volume_control' ).hover(
+ function() {
+ $targetvol.addClass( 'vol_container_top' );
+ // Set to "below" if playing and embedType != native
+ if ( embedPlayer && embedPlayer.isPlaying && embedPlayer.isPlaying() && !embedPlayer.supports['overlays'] ) {
+ $targetvol.removeClass( 'vol_container_top' ).addClass( 'vol_container_below' );
+ }
+ $targetvol.fadeIn( 'fast' );
+ hoverOverDelay = true;
+ },
+ function() {
+ hoverOverDelay = false;
+ setTimeout( function() {
+ if ( !hoverOverDelay ) {
+ $targetvol.fadeOut( 'fast' );
+ }
+ }, 500 );
+ }
+ );
+ }
+ var userSlide=false;
+ // Setup volume slider:
+ var sliderConf = {
+ range: "min",
+ value: 80,
+ min: 0,
+ max: 100,
+ slide: function( event, ui ) {
+ var percent = ui.value / 100;
+ mw.log('PlayerControlBuilder::slide:update volume:' + percent);
+ embedPlayer.setVolume( percent );
+ userSlide = true;
+ },
+ change: function( event, ui ) {
+ var percent = ui.value / 100;
+ if ( percent == 0 ) {
+ embedPlayer.getInterface().find( '.volume_control span' ).removeClass( 'ui-icon-volume-on' ).addClass( 'ui-icon-volume-off' );
+ } else {
+ embedPlayer.getInterface().find( '.volume_control span' ).removeClass( 'ui-icon-volume-off' ).addClass( 'ui-icon-volume-on' );
+ }
+ mw.log('PlayerControlBuilder::change:update volume:' + percent);
+ embedPlayer.setVolume( percent, userSlide );
+ userSlide = false;
+ }
+ };
+
+ if ( this.volumeLayout == 'vertical' ) {
+ sliderConf[ 'orientation' ] = "vertical";
+ }
+
+ embedPlayer.getInterface().find( '.volume-slider' ).slider( sliderConf );
+ },
+
+ /**
+ * Get the options menu ul with li menu items
+ */
+ getOptionsMenu: function( ) {
+ var $optionsMenu = $( '<ul />' );
+ for( var menuItemKey in this.optionMenuItems ){
+
+ // Make sure its supported in the current controlBuilder config:
+ if( $.inArray( menuItemKey, mw.config.get( 'EmbedPlayer.EnabledOptionsMenuItems' ) ) === -1 ) {
+ continue;
+ }
+
+ $optionsMenu.append(
+ this.optionMenuItems[ menuItemKey ]( this )
+ );
+ }
+ return $optionsMenu;
+ },
+
+ /**
+ * Allow the controlBuilder to do interface actions onDone
+ */
+ onClipDone: function(){
+ // Related videos could be shown here
+ },
+
+ /**
+ * The ctrl builder updates the interface on seeking
+ */
+ onSeek: function(){
+ //mw.log( "controlBuilder:: onSeek" );
+ // Update the interface:
+ this.setStatus( mw.msg( 'mwe-embedplayer-seeking' ) );
+ // add a loading spinner:
+ this.embedPlayer.addPlayerSpinner();
+ // hide once playing again:
+ this.embedPlayer.hideSpinnerOncePlaying();
+ },
+
+ /**
+ * Updates the player status that displays short text msgs and the play clock
+ * @param {String} value Status string value to update
+ */
+ setStatus: function( value ) {
+ // update status:
+ if( this.embedPlayer.getInterface() ){
+ this.embedPlayer.getInterface().find( '.time-disp' ).text( value );
+ }
+ },
+
+ /**
+ * Option menu items
+ *
+ * @return
+ * 'li' a li line item with click action for that menu item
+ */
+ optionMenuItems: {
+ // Share the video menu
+ 'share': function( ctrlObj ) {
+ return $.getLineItem(
+ mw.msg( 'mwe-embedplayer-share' ),
+ 'mail-closed',
+ function( ) {
+ ctrlObj.displayMenuOverlay(
+ ctrlObj.getShare()
+ );
+ $( ctrlObj.embedPlayer ).trigger( 'showShareEvent' );
+ }
+ );
+ },
+
+ 'aboutPlayerLibrary' : function( ctrlObj ){
+ return $.getLineItem(
+ mw.msg( 'mwe-embedplayer-about-library' ),
+ 'info',
+ function( ) {
+ ctrlObj.displayMenuOverlay(
+ ctrlObj.aboutPlayerLibrary()
+ );
+ $( ctrlObj.embedPlayer ).trigger( 'aboutPlayerLibrary' );
+ }
+ );
+ }
+ },
+
+ /**
+ * Close a menu overlay
+ */
+ closeMenuOverlay: function(){
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+ var $overlay = embedPlayer.getInterface().find( '.overlay-win,.ui-widget-overlay,.ui-widget-shadow' );
+
+ this.displayOptionsMenuFlag = false;
+ //mw.log(' closeMenuOverlay: ' + this.displayOptionsMenuFlag);
+
+ $overlay.fadeOut( "slow", function() {
+ $overlay.remove();
+ } );
+
+ // Show the big play button: ( if not in an ad .. TODO clean up )
+ if( embedPlayer.isStopped() &&
+ (
+ embedPlayer.sequenceProxy &&
+ embedPlayer.sequenceProxy.isInSequence == false
+ )
+ ){
+ embedPlayer.getInterface().find( '.play-btn-large' ).fadeIn( 'slow' );
+ }
+
+ $(embedPlayer).trigger( 'closeMenuOverlay' );
+
+ return false; // onclick action return false
+ },
+
+ /**
+ * Generic function to display custom HTML overlay on video.
+ *
+ * @param {String} overlayContent content to be displayed
+ */
+ displayMenuOverlay: function( overlayContent, closeCallback, hideCloseButton ) {
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+ mw.log( 'PlayerControlBuilder:: displayMenuOverlay' );
+ // set the overlay display flag to true:
+ this.displayOptionsMenuFlag = true;
+
+ if ( !this.supportedComponents[ 'overlays' ] ) {
+ embedPlayer.stop();
+ }
+
+
+ // Hide the big play button:
+ embedPlayer.hideLargePlayBtn();
+
+ // Check if overlay window is already present:
+ if ( embedPlayer.getInterface().find( '.overlay-win' ).length != 0 ) {
+ //Update the content
+ embedPlayer.getInterface().find( '.overlay-content' ).html(
+ overlayContent
+ );
+ return ;
+ }
+
+ // Add an overlay
+ embedPlayer.getInterface().append(
+ $('<div />')
+ .addClass( 'ui-widget-overlay' )
+ .css( {
+ 'height' : '100%',
+ 'width' : '100%',
+ 'z-index' : 2
+ } )
+ );
+
+ var $closeButton = [];
+
+ if ( !hideCloseButton ) {
+ // Setup the close button
+ $closeButton = $('<div />')
+ .addClass( 'ui-state-default ui-corner-all ui-icon_link rButton')
+ .css({
+ 'position': 'absolute',
+ 'cursor' : 'pointer',
+ 'top' : '2px',
+ 'right' : '2px'
+ })
+ .click( function() {
+ _this.closeMenuOverlay();
+ if( closeCallback ){
+ closeCallback();
+ }
+ } )
+ .append(
+ $('<span />')
+ .addClass( 'ui-icon ui-icon-closethick' )
+ );
+ }
+
+ var controlBarHeight = embedPlayer.getInterface().find( '.control-bar' ).height();
+ var overlayWidth = (embedPlayer.getWidth() - 30);
+ var overlayHeight = (embedPlayer.getHeight() - (controlBarHeight + 30));
+ var overlayTop = (( (embedPlayer.getInterface().height() - controlBarHeight) - overlayHeight) / 2);
+ var overlayLeft = ((embedPlayer.getInterface().width() - overlayWidth) / 2);
+
+ var overlayMenuCss = {
+ 'height' : overlayHeight + 'px',
+ 'width' : overlayWidth + 'px',
+ 'position' : 'absolute',
+ 'top' : overlayTop + 'px',
+ 'left': overlayLeft + 'px',
+ 'margin': '0 10px 10px 0',
+ 'overflow' : 'auto',
+ 'padding' : '4px',
+ 'z-index' : 3
+ };
+ var $overlayMenu = $('<div />')
+ .addClass( 'overlay-win ui-state-default ui-widget-header ui-corner-all' )
+ .css( overlayMenuCss )
+ .append(
+ $closeButton,
+ $('<div />')
+ .addClass( 'overlay-content' )
+ .append( overlayContent )
+ );
+
+
+ // Append the overlay menu to the player interface
+ embedPlayer.getInterface().prepend(
+ $overlayMenu
+ )
+ .find( '.overlay-win' )
+ .fadeIn( "slow" );
+
+ // Trigger menu overlay display
+ $( embedPlayer ).trigger( 'displayMenuOverlay' );
+
+ return false; // onclick action return false
+ },
+
+ /**
+ * Close an alert
+ */
+ closeAlert: function( keepOverlay ) {
+ var embedPlayer = this.embedPlayer;
+ var $alert = $( '#alertContainer' );
+
+ mw.log( 'mw.PlayerControlBuilder::closeAlert' );
+ if ( !keepOverlay || ( mw.isIpad() && this.inFullScreen ) ) {
+ embedPlayer.controlBuilder.closeMenuOverlay();
+ if ( mw.isIpad() ) {
+ embedPlayer.disablePlayControls();
+ }
+ }
+
+ $alert.remove();
+
+ return false; // onclick action return false;
+ },
+
+ /**
+ * Generic function to display custom alert overlay on video.
+ *
+ * @param (Object) Object which includes:
+ * title Alert Title
+ * body Alert body
+ * buttonSet[label,callback] Array of buttons
+ * style CSS object
+ */
+ displayAlert: function( alertObj ) {
+ var embedPlayer = this.embedPlayer;
+ var callback;
+ mw.log( 'PlayerControlBuilder::displayAlert:: ' + alertObj.title );
+ // Check if callback is external or internal (Internal by default)
+
+ // Check if overlay window is already present:
+ if ( embedPlayer.getInterface().find( '.overlay-win' ).length != 0 ) {
+ return;
+ }
+ if( typeof alertObj.callbackFunction == 'string' ) {
+ if ( alertObj.isExternal ) {
+ // TODO better support of running external JS functions, instead of window.parent
+ try{
+ callback = window.parent[ alertObj.callbackFunction ];
+ } catch ( e ){
+ // could not call parent method
+ }
+ } else {
+ callback = window[ alertObj.callbackFunction ];
+ }
+ } else if( typeof alertObj.callbackFunction == 'function' ) {
+ // Make life easier for internal usage of the listener mapping by supporting
+ // passing a callback by function ref
+ callback = alertObj.callbackFunction;
+ } else {
+ mw.log( "PlayerControlBuilder :: displayAlert :: Error: bad callback type" );
+ callback = function() {};
+ }
+
+ var $container = $( '<div />' ).attr( 'id', 'alertContainer' ).addClass( 'alert-container' );
+ var $title = $( '<div />' ).text( alertObj.title ).addClass( 'alert-title alert-text' );
+ if ( alertObj.props && alertObj.props.titleTextColor ) {
+ $title.removeClass( 'alert-text' );
+ $title.css( 'color', mw.getHexColor( alertObj.props.titleTextColor ) );
+ }
+ var $message = $( '<div />' ).text( alertObj.message ).addClass( 'alert-message alert-text' );
+ if ( alertObj.isError ) {
+ $message.addClass( 'error' );
+ }
+ if ( alertObj.props && alertObj.props.textColor ) {
+ $message.removeClass( 'alert-text' );
+ $message.css( 'color', mw.getHexColor( alertObj.props.textColor ) );
+ }
+ var $buttonsContainer = $( '<div />' ).addClass( 'alert-buttons-container' );
+ if ( alertObj.props && alertObj.props.buttonRowSpacing ) {
+ $buttonsContainer.css( 'margin-top', alertObj.props.buttonRowSpacing );
+ }
+ var $buttonSet = alertObj.buttons || [];
+
+ // If no button was passed display just OK button
+ var buttonsNum = $buttonSet.length;
+ if ( buttonsNum == 0 && !alertObj.noButtons ) {
+ $buttonSet = ["OK"];
+ buttonsNum++;
+ }
+
+ $.each( $buttonSet, function(i) {
+ var label = this.toString();
+ var $currentButton = $( '<button />' )
+ .addClass( 'alert-button' )
+ .text( label )
+ .click( function( eventObject ) {
+ callback( eventObject );
+ embedPlayer.controlBuilder.closeAlert( alertObj.keepOverlay );
+ } );
+ if ( alertObj.props && alertObj.props.buttonHeight ) {
+ $currentButton.css( 'height', alertObj.props.buttonHeight );
+ }
+ // Apply buttons spacing only when more than one is present
+ if (buttonsNum > 1) {
+ if (i < buttonsNum-1) {
+ if ( alertObj.props && alertObj.props.buttonSpacing ) {
+ $currentButton.css( 'margin-right', alertObj.props.buttonSpacing );
+ }
+ }
+ }
+ $buttonsContainer.append( $currentButton );
+ } )
+ $container.append( $title, $message, $buttonsContainer );
+ return embedPlayer.controlBuilder.displayMenuOverlay( $container, false, true );
+ },
+
+ aboutPlayerLibrary: function(){
+ return $( '<div />' )
+ .append(
+ $( '<h2 />' )
+ .text(
+ mw.msg('mwe-embedplayer-about-library')
+ )
+ ,
+ $( '<span />')
+ .append(
+ mw.msg('mwe-embedplayer-about-library-desc',
+ $('<div>').append(
+ $('<a />').attr({
+ 'href' : mw.config.get( 'EmbedPlayer.LibraryPage' ),
+ 'target' : '_new'
+ })
+ )[0].innerHTML
+ )
+ )
+ );
+ },
+ /**
+ * Get the "share" interface
+ *
+ * TODO share should be enabled via <embed> tag usage to be compatible
+ * with sites social networking sites that allow <embed> tags but not js
+ *
+ * @param {Object} $target Target jQuery object to set share html
+ */
+ getShare: function( ) {
+ var embedPlayer = this.embedPlayer;
+ var embed_code = embedPlayer.getSharingEmbedCode();
+ var embed_wiki_code = embedPlayer.getWikiEmbedCode();
+ var _this = this;
+
+ var $shareInterface = $('<div />');
+
+ var $shareList = $( '<ul />' );
+
+ $shareList
+ .append(
+ $('<li />').text(
+ mw.msg( 'mwe-embedplayer-embed_site_or_blog' )
+ )
+ /*
+ .append(
+ $('<a />')
+ .attr('href', '#')
+ .addClass( 'active' )
+ .text(
+ mw.msg( 'mwe-embedplayer-embed_site_or_blog' )
+ )
+ )
+ */
+ );
+
+ $shareInterface.append(
+ $( '<h2 />' )
+ .text( embedPlayer.isAudio() ?
+ mw.msg( 'mwe-embedplayer-share_this_audio' ) :
+ mw.msg( 'mwe-embedplayer-share_this_video' ) )
+ );
+
+ if ( embed_wiki_code ) {
+ $shareInterface.append(
+ $('<ul />').append(
+ $('<li />').text(
+ mw.msg( 'mwe-embedplayer-embed_wiki' )
+ )
+ ),
+ $( '<textarea />' )
+ .attr( 'rows', 1 )
+ .html( embed_wiki_code )
+ .click( function() {
+ $( this ).select();
+ }),
+ $('<br />')
+ );
+ }
+
+ $shareInterface.append(
+ $shareList
+ );
+
+ $shareInterface.append(
+
+ $( '<textarea />' )
+ .attr( 'rows', 4 )
+ .html( embed_code )
+ .click( function() {
+ $( this ).select();
+ }),
+
+ $('<br />'),
+ $('<br />')
+ );
+ return $shareInterface;
+ },
+
+ /**
+ * Shows the Player Select interface
+ *
+ * @param {Object} $target jQuery target for output
+ */
+ getPlayerSelect: function( ) {
+ mw.log('PlayerControlBuilder::getPlayerSelect: source:' +
+ this.embedPlayer.mediaElement.selectedSource.getSrc() +
+ ' player: ' + this.embedPlayer.selectedPlayer.id );
+
+ var embedPlayer = this.embedPlayer;
+
+ var _this = this;
+
+ var $playerSelect = $('<div />')
+ .append(
+ $( '<h2 />' )
+ .text( mw.msg( 'mwe-embedplayer-choose_player' ) )
+ );
+
+ $.each( embedPlayer.mediaElement.getPlayableSources(), function( sourceId, source ) {
+
+ var isPlayable = (typeof mw.EmbedTypes.getMediaPlayers().defaultPlayer( source.getMIMEType() ) == 'object' );
+ var isSelected = ( source.getSrc() == embedPlayer.mediaElement.selectedSource.getSrc() );
+
+ $playerSelect.append(
+ $( '<h3 />' )
+ .text( source.getTitle() )
+ );
+
+ if ( isPlayable ) {
+ var $playerList = $('<ul />');
+ // output the player select code:
+
+ var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
+
+ for ( var i = 0; i < supportingPlayers.length ; i++ ) {
+ // Add link to select the player if not already selected )
+ if( embedPlayer.selectedPlayer.id == supportingPlayers[i].id && isSelected ) {
+ // Active player ( no link )
+ var $playerLine = $( '<span />' )
+ .append(
+ $('<a />')
+ .attr({
+ 'href' : '#'
+ })
+ .addClass( 'active')
+ .text(
+ supportingPlayers[i].getName()
+ ).click( function(){
+ embedPlayer.controlBuilder.closeMenuOverlay();
+ // Don't follow the # link:
+ return false;
+ })
+ );
+ //.addClass( 'ui-state-highlight ui-corner-all' ); removed by ran
+ } else {
+ // Non active player add link to select:
+ $playerLine = $( '<a />')
+ .attr({
+ 'href' : '#',
+ 'id' : 'sc_' + sourceId + '_' + supportingPlayers[i].id
+ })
+ .addClass( 'ui-corner-all')
+ .text( supportingPlayers[i].getName() )
+ .click( function() {
+ var iparts = $( this ).attr( 'id' ).replace(/sc_/ , '' ).split( '_' );
+ var sourceId = iparts[0];
+ var player_id = iparts[1];
+ mw.log( 'PlayerControlBuilder:: source id: ' + sourceId + ' player id: ' + player_id );
+
+ embedPlayer.controlBuilder.closeMenuOverlay();
+
+ // Close fullscreen if we are in fullscreen mode
+ if( _this.isInFullScreen() ){
+ _this.restoreWindowPlayer();
+ }
+
+ embedPlayer.mediaElement.setSourceByIndex( sourceId );
+ var playableSources = embedPlayer.mediaElement.getPlayableSources();
+
+ mw.EmbedTypes.getMediaPlayers().setPlayerPreference(
+ player_id,
+ playableSources[ sourceId ].getMIMEType()
+ );
+
+ // Issue a stop
+ embedPlayer.stop();
+
+ // Don't follow the # link:
+ return false;
+ } )
+ .hover(
+ function(){
+ $( this ).addClass('active');
+ },
+ function(){
+ $( this ).removeClass('active');
+ }
+ );
+ }
+
+ // Add the player line to the player list:
+ $playerList.append(
+ $( '<li />' ).append(
+ $playerLine
+ )
+ );
+ }
+
+ // Append the player list:
+ $playerSelect.append( $playerList );
+
+ } else {
+ // No player available:
+ $playerSelect.append( mw.msg( 'mwe-embedplayer-no-player', source.getTitle() ) );
+ }
+ } );
+
+ // Return the player select elements
+ return $playerSelect;
+ },
+
+ /**
+ * Loads sources and calls showDownloadWithSources
+ * @param {Object} $target jQuery target to output to
+ */
+ showDownload: function( $target ) {
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+ _this.showDownloadWithSources( $target );
+ },
+
+ /**
+ * Shows the download interface with sources loaded
+ * @param {Object} $target jQuery target to output to
+ */
+ showDownloadWithSources : function( $target ) {
+ var _this = this;
+ mw.log( 'PlayerControlBuilder:: showDownloadWithSources::' + $target.length );
+ var embedPlayer = this.embedPlayer;
+ // Empty the target:
+ $target.empty();
+ $target.append( $('<div />') );
+ $target = $target.find('div');
+
+ var $mediaList = $( '<ul />' );
+ var $textList = $( '<ul />' );
+ $.each( embedPlayer.mediaElement.getSources(), function( index, source ) {
+ if( source.getSrc() ) {
+ mw.log("showDownloadWithSources:: Add src: " + source.getTitle() );
+ var fileName = source.mwtitle;
+ if ( !fileName ) {
+ var path = new mw.Uri( source.getSrc() ).path;
+ var pathParts = path.split( '/' );
+ fileName = pathParts[ pathParts.length -1 ];
+ }
+ var $dlLine = $( '<li />').append(
+ $('<a />')
+ .attr( {
+ 'href': source.getSrc(),
+ 'download': fileName
+ })
+ .text( source.getTitle() )
+ );
+ // Add link to correct "bucket"
+
+ //Add link to time segment:
+ if ( source.getSrc().indexOf( '?t=' ) !== -1 ) {
+ $target.append( $dlLine );
+ } else if ( this.getMIMEType().indexOf('text') === 0 ) {
+ // Add link to text list
+ $textList.append( $dlLine );
+ } else {
+ // Add link to media list
+ $mediaList.append( $dlLine );
+ }
+
+ }
+ } );
+ if( $mediaList.find('li').length != 0 ) {
+ $target.append(
+ $('<h2 />')
+ .text( embedPlayer.isAudio() ?
+ mw.msg( 'mwe-embedplayer-download_full_audio' ) :
+ mw.msg( 'mwe-embedplayer-download_full_video' ) ),
+ $mediaList
+ );
+ }
+
+ if( $textList.find('li').length != 0 ) {
+ $target.append(
+ $('<h2 />')
+ .html( mw.msg( 'mwe-embedplayer-download_text' ) ),
+ $textList
+ );
+ }
+ },
+ getSwitchSourceMenu: function(){
+ var _this = this;
+ var embedPlayer = this.embedPlayer;
+ // for each source with "native playback"
+ var $sourceMenu = $('<ul />');
+
+ // Local function to closure the "source" variable scope:
+ function addToSourceMenu( source ){
+ // Check if source is selected:
+ var icon = ( source.getSrc() == embedPlayer.mediaElement.selectedSource.getSrc() ) ? 'bullet' : 'radio-on';
+ $sourceMenu.append(
+ $.getLineItem( source.getShortTitle() , icon, function(){
+ mw.log( 'PlayerControlBuilder::SwitchSourceMenu: ' + source.getSrc() );
+ // update menu selecting parent li siblings
+ $( this ).parent().siblings().find('span.ui-icon').removeClass( 'ui-icon-bullet').addClass( 'ui-icon-radio-on' );
+ $( this ).find('span.ui-icon').removeClass( 'ui-icon-radio-on').addClass( 'ui-icon-bullet' );
+ // update control bar text
+ embedPlayer.getInterface().find( '.source-switch' ).text( source.getShortTitle() );
+
+
+ // TODO this logic should be in mw.EmbedPlayer
+ embedPlayer.mediaElement.setSource( source );
+ if( ! _this.embedPlayer.isStopped() ){
+ // Get the exact play time from the video element ( instead of parent embed Player )
+ var oldMediaTime = _this.embedPlayer.getPlayerElement().currentTime;
+ var oldPaused = _this.embedPlayer.paused;
+ // Do a live switch
+ embedPlayer.playerSwitchSource( source, function( vid ){
+ // issue a seek
+ embedPlayer.setCurrentTime( oldMediaTime, function(){
+ // reflect pause state
+ if( oldPaused ){
+ embedPlayer.pause();
+ }
+ } );
+ });
+ }
+ })
+ );
+ }
+ var addedSources = {};
+ $.each( this.embedPlayer.mediaElement.getPlayableSources(), function( sourceIndex, source ) {
+ // Output the player select code:
+ var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
+ for ( var i = 0; i < supportingPlayers.length ; i++ ) {
+ var lib = supportingPlayers[i].library;
+ if( lib === 'Native' || lib === 'OgvJs' ){ // @fixme use supports.sourceSwitch ... if preloaded?
+ if ( !( source.getSrc() in addedSources ) ) {
+ addedSources[source.getSrc()] = true;
+ addToSourceMenu( source );
+ }
+ }
+ }
+ });
+ return $sourceMenu;
+ },
+
+ /**
+ * Get component
+ *
+ * @param {String} componentId Component key to grab html output
+ */
+ getComponent: function( componentId ) {
+ if ( this.components[ componentId ] ) {
+ return this.components[ componentId ].o( this );
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Get a component height
+ *
+ * @param {String} componentId Component key to grab height
+ * @return height or false if not set
+ */
+ getComponentHeight: function( componentId ) {
+ if ( this.components[ componentId ]
+ && this.components[ componentId ].h )
+ {
+ return this.components[ componentId ].h;
+ }
+ return 0;
+ },
+
+ /**
+ * Get a component width
+ * @param {String} componentId Component key to grab width
+ * @return width or false if not set
+ */
+ getComponentWidth: function( componentId ){
+ if ( this.components[ componentId ]
+ && this.components[ componentId ].w )
+ {
+ return this.components[ componentId ].w;
+ }
+ return 0;
+ },
+
+ // Set up the disable playhead function:
+ // TODO this will move into the disableSeekBar binding in the new theme framework
+ disableSeekBar : function(){
+ var $playHead = this.embedPlayer.getInterface().find( ".play_head" );
+ if( $playHead.length ){
+ $playHead.slider( "option", "disabled", true );
+ }
+ },
+ enableSeekBar : function(){
+ var $playHead = this.embedPlayer.getInterface().find( ".play_head" );
+ if( $playHead.length ){
+ $playHead.slider( "option", "disabled", false);
+ }
+ },
+
+ /**
+ * Components Object
+ * Take in the embedPlayer and return some html for the given component.
+ *
+ * components can be overwritten by skin javascript
+ *
+ * Component JSON structure is as follows:
+ * 'o' Function to return a binded jQuery object ( accepts the ctrlObject as a parameter )
+ * 'w' The width of the component
+ * 'h' The height of the component ( if height is undefined the height of the control bar is used )
+ * 'position' elements are inserted into the dom based on component order and available space.
+ * if the element is inserted, position is then used to set relative dom insert order.
+ */
+ components: {
+ /**
+ * The pause / play button
+ */
+ 'pause': {
+ 'w': 28,
+ 'position': 1,
+ 'o': function( ctrlObj ) {
+ return $( '<div />' )
+ .attr( 'title', mw.msg( 'mwe-embedplayer-play_clip' ) )
+ .addClass ( "ui-state-default ui-corner-all ui-icon_link lButton play-btn" )
+ .append(
+ $( '<span />' )
+ .addClass( "ui-icon ui-icon-play" )
+ )
+ // Play / pause binding
+ .buttonHover()
+ .click( function() {
+ ctrlObj.embedPlayer.play();
+ // Don't follow the # link:
+ return false;
+ });
+ }
+ },
+
+ /**
+ * The volume control interface html
+ */
+ 'volumeControl': {
+ 'w' : 28,
+ 'position': 7,
+ 'o' : function( ctrlObj ) {
+ mw.log( 'PlayerControlBuilder::Set up volume control for: ' + ctrlObj.embedPlayer.id );
+ var $volumeOut = $( '<span />' );
+ if ( ctrlObj.volumeLayout == 'horizontal' ) {
+ $volumeOut.append(
+ $( '<div />' )
+ .addClass( "ui-slider ui-slider-horizontal rButton volume-slider" )
+ );
+ }
+
+ // Add the volume control icon
+ $volumeOut.append(
+ $('<div />')
+ .attr( 'title', mw.msg( 'mwe-embedplayer-volume_control' ) )
+ .addClass( "ui-state-default ui-corner-all ui-icon_link rButton volume_control" )
+ .append(
+ $( '<span />' )
+ .addClass( "ui-icon ui-icon-volume-on" )
+ )
+ );
+ if ( ctrlObj.volumeLayout == 'vertical' ) {
+ $volumeOut.find('.volume_control').append(
+ $( '<div />' )
+ .hide()
+ .addClass( "vol_container ui-corner-all" )
+ .append(
+ $( '<div />' )
+ .addClass ( "volume-slider" )
+ )
+ );
+ }
+ //Return the inner html
+ return $volumeOut.html();
+ }
+ },
+
+ /**
+ * The large play button in center of the player
+ */
+ 'playButtonLarge': {
+ 'w' : 70,
+ 'h' : 53,
+ 'position': 2,
+ 'o' : function( ctrlObj ) {
+ return $( '<div />' )
+ .attr( {
+ 'title' : mw.msg( 'mwe-embedplayer-play_clip' ),
+ 'class' : "play-btn-large"
+ } )
+ // Get dynamic position for big play button
+ .css( ctrlObj.getPlayButtonPosition() )
+ // Add play hook:
+ .click( function() {
+ ctrlObj.embedPlayer.play();
+ return false; // Event Stop Propagation
+ } );
+ }
+ },
+
+ /**
+ * The Attribution button ( by default this is kaltura-icon
+ */
+ 'attributionButton' : {
+ 'w' : 28,
+ 'position': 3,
+ 'o' : function( ctrlObj ){
+ var buttonConfig = mw.config.get( 'EmbedPlayer.AttributionButton');
+ // Check for source ( by configuration convention this is a 16x16 image
+ if( buttonConfig.iconurl ){
+ var $icon = $('<img />')
+ .attr('src', buttonConfig.iconurl );
+ } else {
+ var $icon = $('<span />')
+ .addClass( 'ui-icon' );
+ if( buttonConfig['class'] ){
+ $icon.addClass( buttonConfig['class'] );
+ }
+ }
+ if( typeof buttonConfig.style != 'object'){
+ buttonConfig.style = {};
+ }
+ // update the configured size of the attribution button if we have a specific width configured
+ if( buttonConfig.style.width ){
+ this.w = parseInt( buttonConfig.style.width );
+ } else {
+ buttonConfig.style.width = parseInt( this.w ) + 'px';
+ }
+
+ return $( '<div />' )
+ .addClass( 'rButton' )
+ .css({
+ 'top' : '1px',
+ 'left' : '2px'
+ })
+ // Allow button config style to override
+ .css( buttonConfig.style )
+ .append(
+ $('<a />')
+ .attr({
+ 'href': buttonConfig.href,
+ 'title' : buttonConfig.title,
+ 'target' : '_new'
+ })
+ .append( $icon )
+ );
+ }
+ },
+
+ /*
+ * The time display area
+ */
+ 'timeDisplay': {
+ 'w' : mw.config.get( 'EmbedPlayer.TimeDisplayWidth' ),
+ 'position': 6,
+ 'o' : function( ctrlObj ) {
+ return $( '<div />' )
+ .addClass( "ui-widget time-disp" )
+ .append(
+ ctrlObj.embedPlayer.getTimeRange()
+ );
+ }
+ },
+
+ /**
+ * The options button, invokes display of the options menu
+ */
+ 'options': {
+ 'w': 28,
+ 'position': 10,
+ 'o': function( ctrlObj ) {
+ return $( '<div />' )
+ .attr( 'title', mw.msg( 'mwe-embedplayer-player_options' ) )
+ .addClass( 'ui-state-default ui-corner-all ui-icon_link rButton options-btn' )
+ .append(
+ $('<span />')
+ .addClass( 'ui-icon ui-icon-wrench' )
+ )
+ .buttonHover()
+ // Options binding:
+ .embedMenu( {
+ 'content' : ctrlObj.getOptionsMenu(),
+ 'zindex' : mw.config.get( 'EmbedPlayer.FullScreenZIndex' ) + 2,
+ 'positionOpts': {
+ 'directionV' : 'up',
+ 'offsetY' : 30,
+ 'directionH' : 'left',
+ 'offsetX' : -28
+ }
+ } );
+ }
+ },
+
+ /**
+ * The fullscreen button for displaying the video fullscreen
+ */
+ 'fullscreen': {
+ 'w': 24,
+ 'position': 8,
+ 'o': function( ctrlObj ) {
+ var $btn = $( '<div />' )
+ .attr( 'title', mw.msg( 'mwe-embedplayer-player_fullscreen' ) )
+ .addClass( "ui-state-default ui-corner-all ui-icon_link rButton fullscreen-btn" )
+ .append(
+ $( '<span />' )
+ .addClass( "ui-icon ui-icon-arrow-4-diag" )
+ )
+ // Fullscreen binding:
+ .buttonHover();
+ // Link out to another window if iPad 3x ( broken iframe resize )
+ if( (
+ mw.config.get('EmbedPlayer.IsIframeServer')
+ &&
+ mw.isIpad3()
+ )
+ ||
+ mw.config.get( "EmbedPlayer.NewWindowFullscreen" )
+ ||
+ ( mw.config.get('EmbedPlayer.IsIframeServer') && mw.config.get('EmbedPlayer.EnableIframeApi') === false )
+ ){
+ // Get the iframe url:
+ var url = ctrlObj.embedPlayer.getIframeSourceUrl();
+ // Change button into new window ( of the same url as the iframe ) :
+ return $('<a />').attr({
+ 'href': url,
+ 'target' : '_new'
+ })
+ .click(function(){
+ // Update the url:
+ var url = $(this).attr('href');
+ var iframeMwConfig = {};
+
+ iframeMwConfig['EmbedPlayer.IsFullscreenIframe'] = true;
+ // add a seek offset:
+ iframeMwConfig['EmbedPlayer.IframeCurrentTime'] = ctrlObj.embedPlayer.currentTime;
+ // add play state:
+ iframeMwConfig['EmbedPlayer.IframeIsPlaying'] = ctrlObj.embedPlayer.isPlaying();
+
+ // Append the configuration and request domain to the iframe hash:
+
+ // Add the parentUrl to the iframe config:
+ iframeMwConfig['EmbedPlayer.IframeParentUrl'] = document.URL;
+
+ url += '#' + encodeURIComponent(
+ JSON.stringify({
+ 'mwConfig' :iframeMwConfig,
+ 'playerId' : playerId
+ })
+ );
+ ctrlObj.embedPlayer.pause();
+ // try and do a browser popup:
+ var newwin = window.open(
+ url,
+ ctrlObj.embedPlayer.id,
+ // Fullscreen window params:
+ 'width=' + screen.width +
+ ', height=' + ( screen.height - 90 ) +
+ ', top=0, left=0' +
+ ', fullscreen=yes'
+ );
+ // if for some reason we could not open the window run the href link:
+ if( newwin === null){
+ return true;
+ }
+ if ( window.focus ) {
+ newwin.focus();
+ }
+ // Else do not follow the href link
+ return false;
+ })
+ .append($btn);
+ } else {
+ return $btn.click( function() {
+ ctrlObj.embedPlayer.fullscreen();
+ } );
+ }
+ }
+ },
+
+ 'sourceSwitch' : {
+ 'w' : 70,
+ 'position': 9,
+ 'o' : function( ctrlObj ){
+ var $menuContainer = $('<div />').addClass( 'swMenuContainer' ).hide();
+ ctrlObj.embedPlayer.getInterface().append(
+ $menuContainer
+ )
+ // Stream switching widget ( display the current selected stream text )
+ return $( '<div />' )
+ .addClass('ui-widget source-switch')
+ .append(
+ ctrlObj.embedPlayer.mediaElement.selectedSource.getShortTitle()
+ ).embedMenu( {
+ 'content' : ctrlObj.getSwitchSourceMenu(),
+ 'zindex' : mw.config.get( 'EmbedPlayer.FullScreenZIndex' ) + 2,
+ 'keepPosition' : true,
+ 'targetMenuContainer' : $menuContainer,
+ 'width' : 130,
+ 'showSpeed': 0,
+ 'createMenuCallback' : function(){
+ var $interface = ctrlObj.embedPlayer.getInterface();
+ var $sw = $interface.find( '.source-switch' );
+ var $swMenuContainer = $interface.find('.swMenuContainer');
+ var height = $swMenuContainer.find( 'li' ).length * 30;
+ // position from top ( unkown why we can't use bottom here )
+ var top = $interface.height() - height - ctrlObj.getHeight() - 6;
+ $menuContainer.css({
+ 'position' : 'absolute',
+ 'left': $sw[0].offsetLeft,
+ 'top' : top,
+ 'bottom': ctrlObj.getHeight(),
+ 'height' : height
+ })
+ ctrlObj.showControlBar( true );
+ },
+ 'closeMenuCallback' : function(){
+ ctrlObj.restoreControlsHover()
+ }
+ } );
+ }
+ },
+
+ /**
+ * The playhead component
+ */
+ 'playHead': {
+ 'w':0, // special case (takes up remaining space)
+ 'position': 5,
+ 'o':function( ctrlObj ) {
+
+ var sliderConfig = {
+ range: "min",
+ value: 0,
+ min: 0,
+ max: 1000,
+ start: function( event, ui ) {
+ var id = ( embedPlayer.pc != null ) ? embedPlayer.pc.pp.id:embedPlayer.id;
+ embedPlayer.userSlide = true;
+ $( id + ' .play-btn-large' ).fadeOut( 'fast' );
+ // If playlist always start at 0
+ embedPlayer.startTimeSec = ( embedPlayer.instanceOf == 'mvPlayList' ) ? 0:
+ mw.npt2seconds( embedPlayer.getTimeRange().split( '/' )[0] );
+ },
+ slide: function( event, ui ) {
+ var perc = ui.value / 1000;
+ embedPlayer.jumpTime = mw.seconds2npt( parseFloat( parseFloat( embedPlayer.getDuration() ) * perc ) + embedPlayer.startTimeSec );
+ // mw.log('perc:' + perc + ' * ' + embedPlayer.getDuration() + ' jt:'+ this.jumpTime);
+ if ( _this.longTimeDisp ) {
+ ctrlObj.setStatus( mw.msg( 'mwe-embedplayer-seek_to', embedPlayer.jumpTime ) );
+ } else {
+ ctrlObj.setStatus( embedPlayer.jumpTime );
+ }
+ // Update the thumbnail / frame
+ if ( embedPlayer.isPlaying == false ) {
+ embedPlayer.updateThumbPerc( perc );
+ }
+ },
+ change: function( event, ui ) {
+ // Only run the onChange event if done by a user slide
+ // (otherwise it runs times it should not)
+ if ( embedPlayer.userSlide ) {
+ embedPlayer.userSlide = false;
+ embedPlayer.seeking = true;
+
+ var perc = ui.value / 1000;
+ // set seek time (in case we have to do a url seek)
+ embedPlayer.seekTimeSec = mw.npt2seconds( embedPlayer.jumpTime, true );
+ mw.log( 'PlayerControlBuilder:: seek to: ' + embedPlayer.jumpTime + ' perc:' + perc + ' sts:' + embedPlayer.seekTimeSec );
+ ctrlObj.setStatus( mw.msg( 'mwe-embedplayer-seeking' ) );
+ if( embedPlayer.isStopped() ){
+ embedPlayer.play();
+ }
+ embedPlayer.seek( perc );
+ }
+ }
+ };
+
+ var embedPlayer = ctrlObj.embedPlayer;
+ var _this = this;
+ var $playHead = $( '<div />' )
+ .addClass ( "play_head" )
+ .css({
+ "position" : 'absolute',
+ "left" : '33px',
+ "right" : ( ( embedPlayer.getPlayerWidth() - ctrlObj.availableWidth - 33 ) ) + 'px'
+ })
+ // Playhead binding
+ .slider( sliderConfig );
+
+ // Up the z-index of the default status indicator:
+ $playHead.find( '.ui-slider-handle' ).css( 'z-index', 4 );
+ $playHead.find( '.ui-slider-range' ).addClass( 'ui-corner-all' ).css( 'z-index', 2 );
+
+ // Add buffer html:
+ $playHead.append(
+ $('<div />')
+ .addClass( "ui-slider-range ui-slider-range-min ui-widget-header")
+ .addClass( "ui-state-highlight ui-corner-all mw_buffer")
+ );
+
+ // Show video timeline position on hover and when dragging playhead
+ function showPosition(event) {
+ var pos = ( event.clientX - $playHead.offset().left ) / $playHead.width();
+ var time = mw.seconds2npt( parseFloat( embedPlayer.getDuration() ) * pos + (embedPlayer.startTimeSec || 0) );
+ $playHead.attr('title', time);
+ }
+ $playHead.on({
+ mouseenter: showPosition,
+ mouseleave: function(event) {
+ $playHead.attr({title: ''});
+ },
+ mousemove: showPosition
+ });
+
+ return $playHead;
+ }
+ }
+ }
+};
+
+} )( mediaWiki, jQuery );