app/assets/javascripts/mediaelement_rails/mediaelementplayer.js in mediaelement_rails-0.4.0 vs app/assets/javascripts/mediaelement_rails/mediaelementplayer.js in mediaelement_rails-0.5.0

- old
+ new

@@ -4,1095 +4,1111 @@ * * Creates a controller bar for HTML5 <video> add <audio> tags * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) * * Copyright 2010-2012, John Dyer (http://j.hn/) - * Dual licensed under the MIT or GPL Version 2 licenses. + * License: MIT * */ if (typeof jQuery != 'undefined') { mejs.$ = jQuery; } else if (typeof ender != 'undefined') { mejs.$ = ender; } -(function ($) { - - // default player values - mejs.MepDefaults = { - // url to poster (to fix iOS 3.x) - poster: '', - // default if the <video width> is not specified - defaultVideoWidth: 480, - // default if the <video height> is not specified - defaultVideoHeight: 270, - // if set, overrides <video width> - videoWidth: -1, - // if set, overrides <video height> - videoHeight: -1, - // default if the user doesn't specify - defaultAudioWidth: 400, - // default if the user doesn't specify - defaultAudioHeight: 30, - - // default amount to move back when back key is pressed - defaultSeekBackwardInterval: function(media) { - return (media.duration * 0.05); - }, - // default amount to move forward when forward key is pressed - defaultSeekForwardInterval: function(media) { - return (media.duration * 0.05); - }, - - // width of audio player - audioWidth: -1, - // height of audio player - audioHeight: -1, - // initial volume when the player starts (overrided by user cookie) - startVolume: 0.8, - // useful for <audio> player loops - loop: false, - // resize to media dimensions - enableAutosize: true, - // forces the hour marker (##:00:00) - alwaysShowHours: false, - - // show framecount in timecode (##:00:00:00) - showTimecodeFrameCount: false, - // used when showTimecodeFrameCount is set to true - framesPerSecond: 25, - - // automatically calculate the width of the progress bar based on the sizes of other elements - autosizeProgress : true, - // Hide controls when playing and mouse is not over the video - alwaysShowControls: false, - // force iPad's native controls - iPadUseNativeControls: false, - // force iPhone's native controls - iPhoneUseNativeControls: false, - // force Android's native controls - AndroidUseNativeControls: false, - // features to show - features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], - // only for dynamic - isVideo: true, - - // turns keyboard support on and off for this instance - enableKeyboard: true, - - // whenthis player starts, it will pause other players - pauseOtherPlayers: true, - - // array of keyboard actions such as play pause - keyActions: [ - { - keys: [ - 32, // SPACE - 179 // GOOGLE play/pause button - ], - action: function(player, media) { - if (media.paused || media.ended) { - media.play(); - } else { - media.pause(); - } - } - }, - { - keys: [38], // UP - action: function(player, media) { - var newVolume = Math.min(media.volume + 0.1, 1); - media.setVolume(newVolume); - } - }, - { - keys: [40], // DOWN - action: function(player, media) { - var newVolume = Math.max(media.volume - 0.1, 0); - media.setVolume(newVolume); - } - }, - { - keys: [ - 37, // LEFT - 227 // Google TV rewind - ], - action: function(player, media) { - if (!isNaN(media.duration) && media.duration > 0) { - if (player.isVideo) { - player.showControls(); - player.startControlsTimer(); - } - - // 5% - var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); - media.setCurrentTime(newTime); - } - } - }, - { - keys: [ - 39, // RIGHT - 228 // Google TV forward - ], - action: function(player, media) { - if (!isNaN(media.duration) && media.duration > 0) { - if (player.isVideo) { - player.showControls(); - player.startControlsTimer(); - } - - // 5% - var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); - media.setCurrentTime(newTime); - } - } - }, - { - keys: [70], // f - action: function(player, media) { - if (typeof player.enterFullScreen != 'undefined') { - if (player.isFullScreen) { - player.exitFullScreen(); - } else { - player.enterFullScreen(); - } - } - } - } - ] - }; - - mejs.mepIndex = 0; - - mejs.players = []; - - // wraps a MediaElement object in player controls - mejs.MediaElementPlayer = function(node, o) { - // enforce object, even without "new" (via John Resig) - if ( !(this instanceof mejs.MediaElementPlayer) ) { - return new mejs.MediaElementPlayer(node, o); - } - - var t = this; - - // these will be reset after the MediaElement.success fires - t.$media = t.$node = $(node); - t.node = t.media = t.$media[0]; - - // check for existing player - if (typeof t.node.player != 'undefined') { - return t.node.player; - } else { - // attach player to DOM node for reference - t.node.player = t; - } - - - // try to get options from data-mejsoptions - if (typeof o == 'undefined') { - o = t.$node.data('mejsoptions'); - } - - // extend default options - t.options = $.extend({},mejs.MepDefaults,o); - - // add to player array (for focus events) - mejs.players.push(t); - - // start up - t.init(); - - return t; - }; - - // actual player - mejs.MediaElementPlayer.prototype = { - - hasFocus: false, - - controlsAreVisible: true, - - init: function() { - - var - t = this, - mf = mejs.MediaFeatures, - // options for MediaElement (shim) - meOptions = $.extend(true, {}, t.options, { - success: function(media, domNode) { t.meReady(media, domNode); }, - error: function(e) { t.handleError(e);} - }), - tagName = t.media.tagName.toLowerCase(); - - t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); - - if (t.isDynamic) { - // get video from src or href? - t.isVideo = t.options.isVideo; - } else { - t.isVideo = (tagName !== 'audio' && t.options.isVideo); - } - - // use native controls in iPad, iPhone, and Android - if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { - - // add controls and stop - t.$media.attr('controls', 'controls'); - - // attempt to fix iOS 3 bug - //t.$media.removeAttr('poster'); - // no Issue found on iOS3 -ttroxell - - // override Apple's autoplay override for iPads - if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { - t.media.load(); - t.media.play(); - } - - } else if (mf.isAndroid && t.AndroidUseNativeControls) { - - // leave default player - - } else { - - // DESKTOP: use MediaElementPlayer controls - - // remove native controls - t.$media.removeAttr('controls'); - - // unique ID - t.id = 'mep_' + mejs.mepIndex++; - - // build container - t.container = - $('<div id="' + t.id + '" class="mejs-container">'+ - '<div class="mejs-inner">'+ - '<div class="mejs-mediaelement"></div>'+ - '<div class="mejs-layers"></div>'+ - '<div class="mejs-controls"></div>'+ - '<div class="mejs-clear"></div>'+ - '</div>' + - '</div>') - .addClass(t.$media[0].className) - .insertBefore(t.$media); - - // add classes for user and content - t.container.addClass( - (mf.isAndroid ? 'mejs-android ' : '') + - (mf.isiOS ? 'mejs-ios ' : '') + - (mf.isiPad ? 'mejs-ipad ' : '') + - (mf.isiPhone ? 'mejs-iphone ' : '') + - (t.isVideo ? 'mejs-video ' : 'mejs-audio ') - ); - - - // move the <video/video> tag into the right spot - if (mf.isiOS) { - - // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! - var $newMedia = t.$media.clone(); - - t.container.find('.mejs-mediaelement').append($newMedia); - - t.$media.remove(); - t.$node = t.$media = $newMedia; - t.node = t.media = $newMedia[0] - - } else { - - // normal way of moving it into place (doesn't work on iOS) - t.container.find('.mejs-mediaelement').append(t.$media); - } - - // find parts - t.controls = t.container.find('.mejs-controls'); - t.layers = t.container.find('.mejs-layers'); - - // determine the size - - /* size priority: - (1) videoWidth (forced), - (2) style="width;height;" - (3) width attribute, - (4) defaultVideoWidth (for unspecified cases) - */ - - var tagType = (t.isVideo ? 'video' : 'audio'), - capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); - - - if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { - t.width = t.options[tagType + 'Width']; - } else if (t.media.style.width !== '' && t.media.style.width !== null) { - t.width = t.media.style.width; - } else if (t.media.getAttribute('width') !== null) { - t.width = t.$media.attr('width'); - } else { - t.width = t.options['default' + capsTagName + 'Width']; - } - - if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { - t.height = t.options[tagType + 'Height']; - } else if (t.media.style.height !== '' && t.media.style.height !== null) { - t.height = t.media.style.height; - } else if (t.$media[0].getAttribute('height') !== null) { - t.height = t.$media.attr('height'); - } else { - t.height = t.options['default' + capsTagName + 'Height']; - } - - // set the size, while we wait for the plugins to load below - t.setPlayerSize(t.width, t.height); - - // create MediaElementShim - meOptions.pluginWidth = t.height; - meOptions.pluginHeight = t.width; - } - - - - // create MediaElement shim - mejs.MediaElement(t.$media[0], meOptions); - }, - - showControls: function(doAnimation) { - var t = this; - - doAnimation = typeof doAnimation == 'undefined' || doAnimation; - - if (t.controlsAreVisible) - return; - - if (doAnimation) { - t.controls - .css('visibility','visible') - .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); - - // any additional controls people might add and want to hide - t.container.find('.mejs-control') - .css('visibility','visible') - .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); - - } else { - t.controls - .css('visibility','visible') - .css('display','block'); - - // any additional controls people might add and want to hide - t.container.find('.mejs-control') - .css('visibility','visible') - .css('display','block'); - - t.controlsAreVisible = true; - } - - t.setControlsSize(); - - }, - - hideControls: function(doAnimation) { - var t = this; - - doAnimation = typeof doAnimation == 'undefined' || doAnimation; - - if (!t.controlsAreVisible) - return; - - if (doAnimation) { - // fade out main controls - t.controls.stop(true, true).fadeOut(200, function() { - $(this) - .css('visibility','hidden') - .css('display','block'); - - t.controlsAreVisible = false; - }); - - // any additional controls people might add and want to hide - t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { - $(this) - .css('visibility','hidden') - .css('display','block'); - }); - } else { - - // hide main controls - t.controls - .css('visibility','hidden') - .css('display','block'); - - // hide others - t.container.find('.mejs-control') - .css('visibility','hidden') - .css('display','block'); - - t.controlsAreVisible = false; - } - }, - - controlsTimer: null, - - startControlsTimer: function(timeout) { - - var t = this; - - timeout = typeof timeout != 'undefined' ? timeout : 1500; - - t.killControlsTimer('start'); - - t.controlsTimer = setTimeout(function() { - //console.log('timer fired'); - t.hideControls(); - t.killControlsTimer('hide'); - }, timeout); - }, - - killControlsTimer: function(src) { - - var t = this; - - if (t.controlsTimer !== null) { - clearTimeout(t.controlsTimer); - delete t.controlsTimer; - t.controlsTimer = null; - } - }, - - controlsEnabled: true, - - disableControls: function() { - var t= this; - - t.killControlsTimer(); - t.hideControls(false); - this.controlsEnabled = false; - }, - - enableControls: function() { - var t= this; - - t.showControls(false); - - t.controlsEnabled = true; - }, - - - // Sets up all controls and events - meReady: function(media, domNode) { - - - var t = this, - mf = mejs.MediaFeatures, - autoplayAttr = domNode.getAttribute('autoplay'), - autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), - featureIndex, - feature; - - // make sure it can't create itself again if a plugin reloads - if (t.created) - return; - else - t.created = true; - - t.media = media; - t.domNode = domNode; - - if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { - - // two built in features - t.buildposter(t, t.controls, t.layers, t.media); - t.buildkeyboard(t, t.controls, t.layers, t.media); - t.buildoverlays(t, t.controls, t.layers, t.media); - - // grab for use by features - t.findTracks(); - - // add user-defined features/controls - for (featureIndex in t.options.features) { - feature = t.options.features[featureIndex]; - if (t['build' + feature]) { - try { - t['build' + feature](t, t.controls, t.layers, t.media); - } catch (e) { - // TODO: report control error - //throw e; - //console.log('error building ' + feature); - //console.log(e); - } - } - } - - t.container.trigger('controlsready'); - - // reset all layers and controls - t.setPlayerSize(t.width, t.height); - t.setControlsSize(); - - - // controls fade - if (t.isVideo) { - - if (mejs.MediaFeatures.hasTouch) { - - // for touch devices (iOS, Android) - // show/hide without animation on touch - - t.$media.bind('touchstart', function() { - - - // toggle controls - if (t.controlsAreVisible) { - t.hideControls(false); - } else { - if (t.controlsEnabled) { - t.showControls(false); - } - } - }); - - } else { - // click controls - var clickElement = (t.media.pluginType == 'native') ? t.$media : $(t.media.pluginElement); - - // click to play/pause - clickElement.click(function() { - if (media.paused) { - media.play(); - } else { - media.pause(); - } - }); - - - // show/hide controls - t.container - .bind('mouseenter mouseover', function () { - if (t.controlsEnabled) { - if (!t.options.alwaysShowControls) { - t.killControlsTimer('enter'); - t.showControls(); - t.startControlsTimer(2500); - } - } - }) - .bind('mousemove', function() { - if (t.controlsEnabled) { - if (!t.controlsAreVisible) { - t.showControls(); - } - //t.killControlsTimer('move'); - if (!t.options.alwaysShowControls) { - t.startControlsTimer(2500); - } - } - }) - .bind('mouseleave', function () { - if (t.controlsEnabled) { - if (!t.media.paused && !t.options.alwaysShowControls) { - t.startControlsTimer(1000); - } - } - }); - } - - // check for autoplay - if (autoplay && !t.options.alwaysShowControls) { - t.hideControls(); - } - - // resizer - if (t.options.enableAutosize) { - t.media.addEventListener('loadedmetadata', function(e) { - // if the <video height> was not set and the options.videoHeight was not set - // then resize to the real dimensions - if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { - t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); - t.setControlsSize(); - t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); - } - }, false); - } - } - - // EVENTS - - // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) - media.addEventListener('play', function() { - - // go through all other players - for (var i=0, il=mejs.players.length; i<il; i++) { - var p = mejs.players[i]; - if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { - p.pause(); - } - p.hasFocus = false; - } - - t.hasFocus = true; - },false); - - - // ended for all - t.media.addEventListener('ended', function (e) { - try{ - t.media.setCurrentTime(0); - } catch (exp) { - - } - t.media.pause(); - - if (t.setProgressRail) - t.setProgressRail(); - if (t.setCurrentRail) - t.setCurrentRail(); - - if (t.options.loop) { - t.media.play(); - } else if (!t.options.alwaysShowControls && t.controlsEnabled) { - t.showControls(); - } - }, false); - - // resize on the first play - t.media.addEventListener('loadedmetadata', function(e) { - if (t.updateDuration) { - t.updateDuration(); - } - if (t.updateCurrent) { - t.updateCurrent(); - } - - if (!t.isFullScreen) { - t.setPlayerSize(t.width, t.height); - t.setControlsSize(); - } - }, false); - - - // webkit has trouble doing this without a delay - setTimeout(function () { - t.setPlayerSize(t.width, t.height); - t.setControlsSize(); - }, 50); - - // adjust controls whenever window sizes (used to be in fullscreen only) - $(window).resize(function() { - - // don't resize for fullscreen mode - if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { - t.setPlayerSize(t.width, t.height); - } - - // always adjust controls - t.setControlsSize(); - }); - - // TEMP: needs to be moved somewhere else - if (t.media.pluginType == 'youtube') { - t.container.find('.mejs-overlay-play').hide(); - } - } - - // force autoplay for HTML5 - if (autoplay && media.pluginType == 'native') { - media.load(); - media.play(); - } - - - if (t.options.success) { - - if (typeof t.options.success == 'string') { - window[t.options.success](t.media, t.domNode, t); - } else { - t.options.success(t.media, t.domNode, t); - } - } - }, - - handleError: function(e) { - var t = this; - - t.controls.hide(); - - // Tell user that the file cannot be played - if (t.options.error) { - t.options.error(e); - } - }, - - setPlayerSize: function(width,height) { - var t = this; - - if (typeof width != 'undefined') - t.width = width; - - if (typeof height != 'undefined') - t.height = height; - - // detect 100% mode - if (t.height.toString().indexOf('%') > 0) { - - // do we have the native dimensions yet? - var - nativeWidth = (t.media.videoWidth && t.media.videoWidth > 0) ? t.media.videoWidth : t.options.defaultVideoWidth, - nativeHeight = (t.media.videoHeight && t.media.videoHeight > 0) ? t.media.videoHeight : t.options.defaultVideoHeight, - parentWidth = t.container.parent().width(), - newHeight = parseInt(parentWidth * nativeHeight/nativeWidth, 10); - - if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { - parentWidth = $(window).width(); - newHeight = $(window).height(); - } - - if ( newHeight != 0 ) { - // set outer container size - t.container - .width(parentWidth) - .height(newHeight); - - // set native <video> - t.$media - .width('100%') - .height('100%'); - - // set shims - t.container.find('object, embed, iframe') - .width('100%') - .height('100%'); - - // if shim is ready, send the size to the embeded plugin - if (t.isVideo) { - if (t.media.setVideoSize) { - t.media.setVideoSize(parentWidth, newHeight); - } - } - - // set the layers - t.layers.children('.mejs-layer') - .width('100%') - .height('100%'); - } - - - } else { - - t.container - .width(t.width) - .height(t.height); - - t.layers.children('.mejs-layer') - .width(t.width) - .height(t.height); - - } - }, - - setControlsSize: function() { - var t = this, - usedWidth = 0, - railWidth = 0, - rail = t.controls.find('.mejs-time-rail'), - total = t.controls.find('.mejs-time-total'), - current = t.controls.find('.mejs-time-current'), - loaded = t.controls.find('.mejs-time-loaded'), - others = rail.siblings(); - - - // allow the size to come from custom CSS - if (t.options && !t.options.autosizeProgress) { - // Also, frontends devs can be more flexible - // due the opportunity of absolute positioning. - railWidth = parseInt(rail.css('width')); - } - - // attempt to autosize - if (railWidth === 0 || !railWidth) { - - // find the size of all the other controls besides the rail - others.each(function() { - if ($(this).css('position') != 'absolute') { - usedWidth += $(this).outerWidth(true); - } - }); - - // fit the rail into the remaining space - railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); - } - - // outer area - rail.width(railWidth); - // dark space - total.width(railWidth - (total.outerWidth(true) - total.width())); - - if (t.setProgressRail) - t.setProgressRail(); - if (t.setCurrentRail) - t.setCurrentRail(); - }, - - - buildposter: function(player, controls, layers, media) { - var t = this, - poster = - $('<div class="mejs-poster mejs-layer">' + - '</div>') - .appendTo(layers), - posterUrl = player.$media.attr('poster'); - - // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) - if (player.options.poster !== '') { - posterUrl = player.options.poster; - } - - // second, try the real poster - if (posterUrl !== '' && posterUrl != null) { - t.setPoster(posterUrl); - } else { - poster.hide(); - } - - media.addEventListener('play',function() { - poster.hide(); - }, false); - }, - - setPoster: function(url) { - var t = this, - posterDiv = t.container.find('.mejs-poster'), - posterImg = posterDiv.find('img'); - - if (posterImg.length == 0) { - posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); - } - - posterImg.attr('src', url); - }, - - buildoverlays: function(player, controls, layers, media) { - if (!player.isVideo) - return; - - var - loading = - $('<div class="mejs-overlay mejs-layer">'+ - '<div class="mejs-overlay-loading"><span></span></div>'+ - '</div>') - .hide() // start out hidden - .appendTo(layers), - error = - $('<div class="mejs-overlay mejs-layer">'+ - '<div class="mejs-overlay-error"></div>'+ - '</div>') - .hide() // start out hidden - .appendTo(layers), - // this needs to come last so it's on top - bigPlay = - $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ - '<div class="mejs-overlay-button"></div>'+ - '</div>') - .appendTo(layers) - .click(function() { - if (media.paused) { - media.play(); - } else { - media.pause(); - } - }); - - /* - if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { - bigPlay.remove(); - loading.remove(); - } - */ - - - // show/hide big play button - media.addEventListener('play',function() { - bigPlay.hide(); - loading.hide(); - controls.find('.mejs-time-buffering').hide(); - error.hide(); - }, false); - - media.addEventListener('playing', function() { - bigPlay.hide(); - loading.hide(); - controls.find('.mejs-time-buffering').hide(); - error.hide(); - }, false); - - media.addEventListener('seeking', function() { - loading.show(); - controls.find('.mejs-time-buffering').show(); - }, false); - - media.addEventListener('seeked', function() { - loading.hide(); - controls.find('.mejs-time-buffering').hide(); - }, false); - - media.addEventListener('pause',function() { - if (!mejs.MediaFeatures.isiPhone) { - bigPlay.show(); - } - }, false); - - media.addEventListener('waiting', function() { - loading.show(); - controls.find('.mejs-time-buffering').show(); - }, false); - - - // show/hide loading - media.addEventListener('loadeddata',function() { - // for some reason Chrome is firing this event - //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') - // return; - - loading.show(); - controls.find('.mejs-time-buffering').show(); - }, false); - media.addEventListener('canplay',function() { - loading.hide(); - controls.find('.mejs-time-buffering').hide(); - }, false); - - // error handling - media.addEventListener('error',function() { - loading.hide(); - controls.find('.mejs-time-buffering').hide(); - error.show(); - error.find('mejs-overlay-error').html("Error loading this resource"); - }, false); - }, - - buildkeyboard: function(player, controls, layers, media) { - - var t = this; - - // listen for key presses - $(document).keydown(function(e) { - - if (player.hasFocus && player.options.enableKeyboard) { - - // find a matching key - for (var i=0, il=player.options.keyActions.length; i<il; i++) { - var keyAction = player.options.keyActions[i]; - - for (var j=0, jl=keyAction.keys.length; j<jl; j++) { - if (e.keyCode == keyAction.keys[j]) { - e.preventDefault(); - keyAction.action(player, media, e.keyCode); - return false; - } - } - } - } - - return true; - }); - - // check if someone clicked outside a player region, then kill its focus - $(document).click(function(event) { - if ($(event.target).closest('.mejs-container').length == 0) { - player.hasFocus = false; - } - }); - - }, - - findTracks: function() { - var t = this, - tracktags = t.$media.find('track'); - - // store for use by plugins - t.tracks = []; - tracktags.each(function(index, track) { - - track = $(track); - - t.tracks.push({ - srclang: track.attr('srclang').toLowerCase(), - src: track.attr('src'), - kind: track.attr('kind'), - label: track.attr('label') || '', - entries: [], - isLoaded: false - }); - }); - }, - changeSkin: function(className) { - this.container[0].className = 'mejs-container ' + className; - this.setPlayerSize(this.width, this.height); - this.setControlsSize(); - }, - play: function() { - this.media.play(); - }, - pause: function() { - this.media.pause(); - }, - load: function() { - this.media.load(); - }, - setMuted: function(muted) { - this.media.setMuted(muted); - }, - setCurrentTime: function(time) { - this.media.setCurrentTime(time); - }, - getCurrentTime: function() { - return this.media.currentTime; - }, - setVolume: function(volume) { - this.media.setVolume(volume); - }, - getVolume: function() { - return this.media.volume; - }, - setSrc: function(src) { - this.media.setSrc(src); - }, - remove: function() { - var t = this; - - if (t.media.pluginType === 'flash') { - t.media.remove(); - } else if (t.media.pluginType === 'native') { - t.$media.prop('controls', true); - } - - // grab video and put it back in place - if (!t.isDynamic) { - t.$node.insertBefore(t.container) - } - - t.container.remove(); - } - }; - - // turn into jQuery plugin - if (typeof jQuery != 'undefined') { - jQuery.fn.mediaelementplayer = function (options) { - return this.each(function () { - new mejs.MediaElementPlayer(this, options); - }); - }; - } - - $(document).ready(function() { - // auto enable using JSON attribute - $('.mejs-player').mediaelementplayer(); - }); - - // push out to window - window.MediaElementPlayer = mejs.MediaElementPlayer; - -})(mejs.$); +(function ($) { + // default player values + mejs.MepDefaults = { + // url to poster (to fix iOS 3.x) + poster: '', + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // if set, overrides <video width> + videoWidth: -1, + // if set, overrides <video height> + videoHeight: -1, + // default if the user doesn't specify + defaultAudioWidth: 400, + // default if the user doesn't specify + defaultAudioHeight: 30, + + // default amount to move back when back key is pressed + defaultSeekBackwardInterval: function(media) { + return (media.duration * 0.05); + }, + // default amount to move forward when forward key is pressed + defaultSeekForwardInterval: function(media) { + return (media.duration * 0.05); + }, + + // width of audio player + audioWidth: -1, + // height of audio player + audioHeight: -1, + // initial volume when the player starts (overrided by user cookie) + startVolume: 0.8, + // useful for <audio> player loops + loop: false, + // rewind to beginning when media ends + autoRewind: true, + // resize to media dimensions + enableAutosize: true, + // forces the hour marker (##:00:00) + alwaysShowHours: false, + + // show framecount in timecode (##:00:00:00) + showTimecodeFrameCount: false, + // used when showTimecodeFrameCount is set to true + framesPerSecond: 25, + + // automatically calculate the width of the progress bar based on the sizes of other elements + autosizeProgress : true, + // Hide controls when playing and mouse is not over the video + alwaysShowControls: false, + // Enable click video element to toggle play/pause + clickToPlayPause: true, + // force iPad's native controls + iPadUseNativeControls: false, + // force iPhone's native controls + iPhoneUseNativeControls: false, + // force Android's native controls + AndroidUseNativeControls: false, + // features to show + features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], + // only for dynamic + isVideo: true, + + // turns keyboard support on and off for this instance + enableKeyboard: true, + + // whenthis player starts, it will pause other players + pauseOtherPlayers: true, + + // array of keyboard actions such as play pause + keyActions: [ + { + keys: [ + 32, // SPACE + 179 // GOOGLE play/pause button + ], + action: function(player, media) { + if (media.paused || media.ended) { + media.play(); + } else { + media.pause(); + } + } + }, + { + keys: [38], // UP + action: function(player, media) { + var newVolume = Math.min(media.volume + 0.1, 1); + media.setVolume(newVolume); + } + }, + { + keys: [40], // DOWN + action: function(player, media) { + var newVolume = Math.max(media.volume - 0.1, 0); + media.setVolume(newVolume); + } + }, + { + keys: [ + 37, // LEFT + 227 // Google TV rewind + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [ + 39, // RIGHT + 228 // Google TV forward + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [70], // f + action: function(player, media) { + if (typeof player.enterFullScreen != 'undefined') { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + } + ] + }; + + mejs.mepIndex = 0; + + mejs.players = []; + + // wraps a MediaElement object in player controls + mejs.MediaElementPlayer = function(node, o) { + // enforce object, even without "new" (via John Resig) + if ( !(this instanceof mejs.MediaElementPlayer) ) { + return new mejs.MediaElementPlayer(node, o); + } + + var t = this; + + // these will be reset after the MediaElement.success fires + t.$media = t.$node = $(node); + t.node = t.media = t.$media[0]; + + // check for existing player + if (typeof t.node.player != 'undefined') { + return t.node.player; + } else { + // attach player to DOM node for reference + t.node.player = t; + } + + + // try to get options from data-mejsoptions + if (typeof o == 'undefined') { + o = t.$node.data('mejsoptions'); + } + + // extend default options + t.options = $.extend({},mejs.MepDefaults,o); + + // add to player array (for focus events) + mejs.players.push(t); + + // start up + t.init(); + + return t; + }; + + // actual player + mejs.MediaElementPlayer.prototype = { + + hasFocus: false, + + controlsAreVisible: true, + + init: function() { + + var + t = this, + mf = mejs.MediaFeatures, + // options for MediaElement (shim) + meOptions = $.extend(true, {}, t.options, { + success: function(media, domNode) { t.meReady(media, domNode); }, + error: function(e) { t.handleError(e);} + }), + tagName = t.media.tagName.toLowerCase(); + + t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); + + if (t.isDynamic) { + // get video from src or href? + t.isVideo = t.options.isVideo; + } else { + t.isVideo = (tagName !== 'audio' && t.options.isVideo); + } + + // use native controls in iPad, iPhone, and Android + if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // add controls and stop + t.$media.attr('controls', 'controls'); + + // attempt to fix iOS 3 bug + //t.$media.removeAttr('poster'); + // no Issue found on iOS3 -ttroxell + + // override Apple's autoplay override for iPads + if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { + t.media.load(); + t.media.play(); + } + + } else if (mf.isAndroid && t.AndroidUseNativeControls) { + + // leave default player + + } else { + + // DESKTOP: use MediaElementPlayer controls + + // remove native controls + t.$media.removeAttr('controls'); + + // unique ID + t.id = 'mep_' + mejs.mepIndex++; + + // build container + t.container = + $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+ + '<div class="mejs-inner">'+ + '<div class="mejs-mediaelement"></div>'+ + '<div class="mejs-layers"></div>'+ + '<div class="mejs-controls"></div>'+ + '<div class="mejs-clear"></div>'+ + '</div>' + + '</div>') + .addClass(t.$media[0].className) + .insertBefore(t.$media); + + // add classes for user and content + t.container.addClass( + (mf.isAndroid ? 'mejs-android ' : '') + + (mf.isiOS ? 'mejs-ios ' : '') + + (mf.isiPad ? 'mejs-ipad ' : '') + + (mf.isiPhone ? 'mejs-iphone ' : '') + + (t.isVideo ? 'mejs-video ' : 'mejs-audio ') + ); + + + // move the <video/video> tag into the right spot + if (mf.isiOS) { + + // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! + var $newMedia = t.$media.clone(); + + t.container.find('.mejs-mediaelement').append($newMedia); + + t.$media.remove(); + t.$node = t.$media = $newMedia; + t.node = t.media = $newMedia[0] + + } else { + + // normal way of moving it into place (doesn't work on iOS) + t.container.find('.mejs-mediaelement').append(t.$media); + } + + // find parts + t.controls = t.container.find('.mejs-controls'); + t.layers = t.container.find('.mejs-layers'); + + // determine the size + + /* size priority: + (1) videoWidth (forced), + (2) style="width;height;" + (3) width attribute, + (4) defaultVideoWidth (for unspecified cases) + */ + + var tagType = (t.isVideo ? 'video' : 'audio'), + capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); + + + if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { + t.width = t.options[tagType + 'Width']; + } else if (t.media.style.width !== '' && t.media.style.width !== null) { + t.width = t.media.style.width; + } else if (t.media.getAttribute('width') !== null) { + t.width = t.$media.attr('width'); + } else { + t.width = t.options['default' + capsTagName + 'Width']; + } + + if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { + t.height = t.options[tagType + 'Height']; + } else if (t.media.style.height !== '' && t.media.style.height !== null) { + t.height = t.media.style.height; + } else if (t.$media[0].getAttribute('height') !== null) { + t.height = t.$media.attr('height'); + } else { + t.height = t.options['default' + capsTagName + 'Height']; + } + + // set the size, while we wait for the plugins to load below + t.setPlayerSize(t.width, t.height); + + // create MediaElementShim + meOptions.pluginWidth = t.height; + meOptions.pluginHeight = t.width; + } + + + + // create MediaElement shim + mejs.MediaElement(t.$media[0], meOptions); + + // controls are shown when loaded + t.container.trigger('controlsshown'); + }, + + showControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (t.controlsAreVisible) + return; + + if (doAnimation) { + t.controls + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() { + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); + + } else { + t.controls + .css('visibility','visible') + .css('display','block'); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .css('display','block'); + + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + } + + t.setControlsSize(); + + }, + + hideControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (!t.controlsAreVisible) + return; + + if (doAnimation) { + // fade out main controls + t.controls.stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + }); + } else { + + // hide main controls + t.controls + .css('visibility','hidden') + .css('display','block'); + + // hide others + t.container.find('.mejs-control') + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + } + }, + + controlsTimer: null, + + startControlsTimer: function(timeout) { + + var t = this; + + timeout = typeof timeout != 'undefined' ? timeout : 1500; + + t.killControlsTimer('start'); + + t.controlsTimer = setTimeout(function() { + //console.log('timer fired'); + t.hideControls(); + t.killControlsTimer('hide'); + }, timeout); + }, + + killControlsTimer: function(src) { + + var t = this; + + if (t.controlsTimer !== null) { + clearTimeout(t.controlsTimer); + delete t.controlsTimer; + t.controlsTimer = null; + } + }, + + controlsEnabled: true, + + disableControls: function() { + var t= this; + + t.killControlsTimer(); + t.hideControls(false); + this.controlsEnabled = false; + }, + + enableControls: function() { + var t= this; + + t.showControls(false); + + t.controlsEnabled = true; + }, + + + // Sets up all controls and events + meReady: function(media, domNode) { + + + var t = this, + mf = mejs.MediaFeatures, + autoplayAttr = domNode.getAttribute('autoplay'), + autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), + featureIndex, + feature; + + // make sure it can't create itself again if a plugin reloads + if (t.created) + return; + else + t.created = true; + + t.media = media; + t.domNode = domNode; + + if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // two built in features + t.buildposter(t, t.controls, t.layers, t.media); + t.buildkeyboard(t, t.controls, t.layers, t.media); + t.buildoverlays(t, t.controls, t.layers, t.media); + + // grab for use by features + t.findTracks(); + + // add user-defined features/controls + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['build' + feature]) { + try { + t['build' + feature](t, t.controls, t.layers, t.media); + } catch (e) { + // TODO: report control error + //throw e; + //console.log('error building ' + feature); + //console.log(e); + } + } + } + + t.container.trigger('controlsready'); + + // reset all layers and controls + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + + + // controls fade + if (t.isVideo) { + + if (mejs.MediaFeatures.hasTouch) { + + // for touch devices (iOS, Android) + // show/hide without animation on touch + + t.$media.bind('touchstart', function() { + + + // toggle controls + if (t.controlsAreVisible) { + t.hideControls(false); + } else { + if (t.controlsEnabled) { + t.showControls(false); + } + } + }); + + } else { + // click to play/pause + t.media.addEventListener('click', function() { + if (t.options.clickToPlayPause) { + if (t.media.paused) { + t.media.play(); + } else { + t.media.pause(); + } + } + }); + + // show/hide controls + t.container + .bind('mouseenter mouseover', function () { + if (t.controlsEnabled) { + if (!t.options.alwaysShowControls) { + t.killControlsTimer('enter'); + t.showControls(); + t.startControlsTimer(2500); + } + } + }) + .bind('mousemove', function() { + if (t.controlsEnabled) { + if (!t.controlsAreVisible) { + t.showControls(); + } + //t.killControlsTimer('move'); + if (!t.options.alwaysShowControls) { + t.startControlsTimer(2500); + } + } + }) + .bind('mouseleave', function () { + if (t.controlsEnabled) { + if (!t.media.paused && !t.options.alwaysShowControls) { + t.startControlsTimer(1000); + } + } + }); + } + + // check for autoplay + if (autoplay && !t.options.alwaysShowControls) { + t.hideControls(); + } + + // resizer + if (t.options.enableAutosize) { + t.media.addEventListener('loadedmetadata', function(e) { + // if the <video height> was not set and the options.videoHeight was not set + // then resize to the real dimensions + if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { + t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); + t.setControlsSize(); + t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); + } + }, false); + } + } + + // EVENTS + + // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) + media.addEventListener('play', function() { + + // go through all other players + for (var i=0, il=mejs.players.length; i<il; i++) { + var p = mejs.players[i]; + if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { + p.pause(); + } + p.hasFocus = false; + } + + t.hasFocus = true; + },false); + + + // ended for all + t.media.addEventListener('ended', function (e) { + if(t.options.autoRewind) { + try{ + t.media.setCurrentTime(0); + } catch (exp) { + + } + } + t.media.pause(); + + if (t.setProgressRail) + t.setProgressRail(); + if (t.setCurrentRail) + t.setCurrentRail(); + + if (t.options.loop) { + t.media.play(); + } else if (!t.options.alwaysShowControls && t.controlsEnabled) { + t.showControls(); + } + }, false); + + // resize on the first play + t.media.addEventListener('loadedmetadata', function(e) { + if (t.updateDuration) { + t.updateDuration(); + } + if (t.updateCurrent) { + t.updateCurrent(); + } + + if (!t.isFullScreen) { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + } + }, false); + + + // webkit has trouble doing this without a delay + setTimeout(function () { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + }, 50); + + // adjust controls whenever window sizes (used to be in fullscreen only) + $(window).resize(function() { + + // don't resize for fullscreen mode + if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { + t.setPlayerSize(t.width, t.height); + } + + // always adjust controls + t.setControlsSize(); + }); + + // TEMP: needs to be moved somewhere else + if (t.media.pluginType == 'youtube') { + t.container.find('.mejs-overlay-play').hide(); + } + } + + // force autoplay for HTML5 + if (autoplay && media.pluginType == 'native') { + media.load(); + media.play(); + } + + + if (t.options.success) { + + if (typeof t.options.success == 'string') { + window[t.options.success](t.media, t.domNode, t); + } else { + t.options.success(t.media, t.domNode, t); + } + } + }, + + handleError: function(e) { + var t = this; + + t.controls.hide(); + + // Tell user that the file cannot be played + if (t.options.error) { + t.options.error(e); + } + }, + + setPlayerSize: function(width,height) { + var t = this; + + if (typeof width != 'undefined') + t.width = width; + + if (typeof height != 'undefined') + t.height = height; + + // detect 100% mode - use currentStyle for IE since css() doesn't return percentages + if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) { + + // do we have the native dimensions yet? + var + nativeWidth = t.isVideo ? ((t.media.videoWidth && t.media.videoWidth > 0) ? t.media.videoWidth : t.options.defaultVideoWidth) : t.options.defaultAudioWidth, + nativeHeight = t.isVideo ? ((t.media.videoHeight && t.media.videoHeight > 0) ? t.media.videoHeight : t.options.defaultVideoHeight) : t.options.defaultAudioHeight, + parentWidth = t.container.parent().closest(':visible').width(), + newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight; + + if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { + parentWidth = $(window).width(); + newHeight = $(window).height(); + } + + if ( newHeight != 0 && parentWidth != 0 ) { + // set outer container size + t.container + .width(parentWidth) + .height(newHeight); + + // set native <video> or <audio> + t.$media + .width('100%') + .height('100%'); + + // set shims + t.container.find('object, embed, iframe') + .width('100%') + .height('100%'); + + // if shim is ready, send the size to the embeded plugin + if (t.isVideo) { + if (t.media.setVideoSize) { + t.media.setVideoSize(parentWidth, newHeight); + } + } + + // set the layers + t.layers.children('.mejs-layer') + .width('100%') + .height('100%'); + } + + + } else { + + t.container + .width(t.width) + .height(t.height); + + t.layers.children('.mejs-layer') + .width(t.width) + .height(t.height); + + } + }, + + setControlsSize: function() { + var t = this, + usedWidth = 0, + railWidth = 0, + rail = t.controls.find('.mejs-time-rail'), + total = t.controls.find('.mejs-time-total'), + current = t.controls.find('.mejs-time-current'), + loaded = t.controls.find('.mejs-time-loaded'), + others = rail.siblings(); + + + // allow the size to come from custom CSS + if (t.options && !t.options.autosizeProgress) { + // Also, frontends devs can be more flexible + // due the opportunity of absolute positioning. + railWidth = parseInt(rail.css('width')); + } + + // attempt to autosize + if (railWidth === 0 || !railWidth) { + + // find the size of all the other controls besides the rail + others.each(function() { + if ($(this).css('position') != 'absolute') { + usedWidth += $(this).outerWidth(true); + } + }); + + // fit the rail into the remaining space + railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); + } + + // outer area + rail.width(railWidth); + // dark space + total.width(railWidth - (total.outerWidth(true) - total.width())); + + if (t.setProgressRail) + t.setProgressRail(); + if (t.setCurrentRail) + t.setCurrentRail(); + }, + + + buildposter: function(player, controls, layers, media) { + var t = this, + poster = + $('<div class="mejs-poster mejs-layer">' + + '</div>') + .appendTo(layers), + posterUrl = player.$media.attr('poster'); + + // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) + if (player.options.poster !== '') { + posterUrl = player.options.poster; + } + + // second, try the real poster + if (posterUrl !== '' && posterUrl != null) { + t.setPoster(posterUrl); + } else { + poster.hide(); + } + + media.addEventListener('play',function() { + poster.hide(); + }, false); + }, + + setPoster: function(url) { + var t = this, + posterDiv = t.container.find('.mejs-poster'), + posterImg = posterDiv.find('img'); + + if (posterImg.length == 0) { + posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); + } + + posterImg.attr('src', url); + }, + + buildoverlays: function(player, controls, layers, media) { + var t = this; + if (!player.isVideo) + return; + + var + loading = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-loading"><span></span></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + error = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-error"></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + // this needs to come last so it's on top + bigPlay = + $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ + '<div class="mejs-overlay-button"></div>'+ + '</div>') + .appendTo(layers) + .click(function() { + if (t.options.clickToPlayPause) { + if (media.paused) { + media.play(); + } else { + media.pause(); + } + } + }); + + /* + if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { + bigPlay.remove(); + loading.remove(); + } + */ + + + // show/hide big play button + media.addEventListener('play',function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('playing', function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('seeking', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + media.addEventListener('seeked', function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + media.addEventListener('pause',function() { + if (!mejs.MediaFeatures.isiPhone) { + bigPlay.show(); + } + }, false); + + media.addEventListener('waiting', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + + // show/hide loading + media.addEventListener('loadeddata',function() { + // for some reason Chrome is firing this event + //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') + // return; + + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + media.addEventListener('canplay',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + // error handling + media.addEventListener('error',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.show(); + error.find('mejs-overlay-error').html("Error loading this resource"); + }, false); + }, + + buildkeyboard: function(player, controls, layers, media) { + + var t = this; + + // listen for key presses + $(document).keydown(function(e) { + + if (player.hasFocus && player.options.enableKeyboard) { + + // find a matching key + for (var i=0, il=player.options.keyActions.length; i<il; i++) { + var keyAction = player.options.keyActions[i]; + + for (var j=0, jl=keyAction.keys.length; j<jl; j++) { + if (e.keyCode == keyAction.keys[j]) { + e.preventDefault(); + keyAction.action(player, media, e.keyCode); + return false; + } + } + } + } + + return true; + }); + + // check if someone clicked outside a player region, then kill its focus + $(document).click(function(event) { + if ($(event.target).closest('.mejs-container').length == 0) { + player.hasFocus = false; + } + }); + + }, + + findTracks: function() { + var t = this, + tracktags = t.$media.find('track'); + + // store for use by plugins + t.tracks = []; + tracktags.each(function(index, track) { + + track = $(track); + + t.tracks.push({ + srclang: track.attr('srclang').toLowerCase(), + src: track.attr('src'), + kind: track.attr('kind'), + label: track.attr('label') || '', + entries: [], + isLoaded: false + }); + }); + }, + changeSkin: function(className) { + this.container[0].className = 'mejs-container ' + className; + this.setPlayerSize(this.width, this.height); + this.setControlsSize(); + }, + play: function() { + this.media.play(); + }, + pause: function() { + this.media.pause(); + }, + load: function() { + this.media.load(); + }, + setMuted: function(muted) { + this.media.setMuted(muted); + }, + setCurrentTime: function(time) { + this.media.setCurrentTime(time); + }, + getCurrentTime: function() { + return this.media.currentTime; + }, + setVolume: function(volume) { + this.media.setVolume(volume); + }, + getVolume: function() { + return this.media.volume; + }, + setSrc: function(src) { + this.media.setSrc(src); + }, + remove: function() { + var t = this; + + if (t.media.pluginType === 'flash') { + t.media.remove(); + } else if (t.media.pluginType === 'native') { + t.$media.prop('controls', true); + } + + // grab video and put it back in place + if (!t.isDynamic) { + t.$node.insertBefore(t.container) + } + + t.container.remove(); + } + }; + + // turn into jQuery plugin + if (typeof jQuery != 'undefined') { + jQuery.fn.mediaelementplayer = function (options) { + return this.each(function () { + new mejs.MediaElementPlayer(this, options); + }); + }; + } + + $(document).ready(function() { + // auto enable using JSON attribute + $('.mejs-player').mediaelementplayer(); + }); + + // push out to window + window.MediaElementPlayer = mejs.MediaElementPlayer; + +})(mejs.$); + (function($) { $.extend(mejs.MepDefaults, { playpauseText: 'Play/Pause' }); @@ -1155,11 +1171,12 @@ .click(function() { if (!media.paused) { media.pause(); } if (media.currentTime > 0) { - media.setCurrentTime(0); + media.setCurrentTime(0); + media.pause(); controls.find('.mejs-time-current').width('0px'); controls.find('.mejs-time-handle').css('left', '0px'); controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); layers.find('.mejs-poster').show(); @@ -1199,22 +1216,29 @@ timefloatcurrent = controls.find('.mejs-time-float-current'), handleMouseMove = function (e) { // mouse position relative to the object var x = e.pageX, offset = total.offset(), - width = total.outerWidth(), + width = total.outerWidth(true), percentage = 0, newTime = 0, - pos = x - offset.left; + pos = 0; - if (x > offset.left && x <= width + offset.left && media.duration) { - percentage = ((x - offset.left) / width); + if (media.duration) { + if (x < offset.left) { + x = offset.left; + } else if (x > width + offset.left) { + x = width + offset.left; + } + + pos = x - offset.left; + percentage = (pos / width); newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; // seek to where the mouse is - if (mouseIsDown) { + if (mouseIsDown && newTime !== media.currentTime) { media.setCurrentTime(newTime); } // position floating time box if (!mejs.MediaFeatures.hasTouch) { @@ -1334,90 +1358,94 @@ } } }); })(mejs.$); -(function($) { - - // options - $.extend(mejs.MepDefaults, { - duration: -1, - timeAndDurationSeparator: ' <span> | </span> ' - }); - - - // current and duration 00:00 / 00:00 - $.extend(MediaElementPlayer.prototype, { - buildcurrent: function(player, controls, layers, media) { - var t = this; - - $('<div class="mejs-time">'+ - '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') - + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ - '</div>') - .appendTo(controls); - - t.currenttime = t.controls.find('.mejs-currenttime'); - - media.addEventListener('timeupdate',function() { - player.updateCurrent(); - }, false); - }, - - - buildduration: function(player, controls, layers, media) { - var t = this; - - if (controls.children().last().find('.mejs-currenttime').length > 0) { - $(t.options.timeAndDurationSeparator + - '<span class="mejs-duration">' + - (t.options.duration > 0 ? - mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : - ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) - ) + - '</span>') - .appendTo(controls.find('.mejs-time')); - } else { - - // add class to current time - controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); - - $('<div class="mejs-time mejs-duration-container">'+ - '<span class="mejs-duration">' + - (t.options.duration > 0 ? - mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : - ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) - ) + - '</span>' + - '</div>') - .appendTo(controls); - } - - t.durationD = t.controls.find('.mejs-duration'); - - media.addEventListener('timeupdate',function() { - player.updateDuration(); - }, false); - }, - - updateCurrent: function() { - var t = this; - - if (t.currenttime) { - t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); - } - }, - - updateDuration: function() { - var t = this; - - if (t.media.duration && t.durationD) { - t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); - } - } - }); - + +(function($) { + + // options + $.extend(mejs.MepDefaults, { + duration: -1, + timeAndDurationSeparator: ' <span> | </span> ' + }); + + + // current and duration 00:00 / 00:00 + $.extend(MediaElementPlayer.prototype, { + buildcurrent: function(player, controls, layers, media) { + var t = this; + + $('<div class="mejs-time">'+ + '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ + '</div>') + .appendTo(controls); + + t.currenttime = t.controls.find('.mejs-currenttime'); + + media.addEventListener('timeupdate',function() { + player.updateCurrent(); + }, false); + }, + + + buildduration: function(player, controls, layers, media) { + var t = this; + + if (controls.children().last().find('.mejs-currenttime').length > 0) { + $(t.options.timeAndDurationSeparator + + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>') + .appendTo(controls.find('.mejs-time')); + } else { + + // add class to current time + controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); + + $('<div class="mejs-time mejs-duration-container">'+ + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>' + + '</div>') + .appendTo(controls); + } + + t.durationD = t.controls.find('.mejs-duration'); + + media.addEventListener('timeupdate',function() { + player.updateDuration(); + }, false); + }, + + updateCurrent: function() { + var t = this; + + if (t.currenttime) { + t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + }, + + updateDuration: function() { + var t = this; + + //Toggle the long video class if the video is longer than an hour. + t.container.toggleClass("mejs-long-video", t.media.duration > 3600); + + if (t.media.duration && t.durationD) { + t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + } + }); + })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { muteText: 'Mute Toggle', @@ -1465,11 +1493,11 @@ volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), positionVolumeHandle = function(volume, secondTry) { - if (!volumeSlider.is(':visible') && typeof secondTry != 'undefined') { + if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') { volumeSlider.show(); positionVolumeHandle(volume, true); volumeSlider.hide() return; } @@ -1498,11 +1526,11 @@ // the new top position based on the current volume // 70% volume on 100px height == top:30px newTop = totalHeight - (totalHeight * volume); // handle - volumeHandle.css('top', totalPosition.top + newTop - (volumeHandle.height() / 2)); + volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2))); // show the current visibility volumeCurrent.height(totalHeight - newTop ); volumeCurrent.css('top', totalPosition.top + newTop); } else { @@ -1516,14 +1544,14 @@ // the new left position based on the current volume newLeft = totalWidth * volume; // handle - volumeHandle.css('left', totalPosition.left + newLeft - (volumeHandle.width() / 2)); + volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); // rezize the current part of the volume bar - volumeCurrent.width( newLeft ); + volumeCurrent.width( Math.round(newLeft) ); } }, handleVolumeMove = function(e) { var volume = null, @@ -1637,448 +1665,448 @@ } }); })(mejs.$); -(function($) { - - $.extend(mejs.MepDefaults, { - usePluginFullScreen: true, - newWindowCallback: function() { return '';}, - fullscreenText: 'Fullscreen' - }); - - $.extend(MediaElementPlayer.prototype, { - - isFullScreen: false, - - isNativeFullScreen: false, - - docStyleOverflow: null, - - isInIframe: false, - - buildfullscreen: function(player, controls, layers, media) { - - if (!player.isVideo) - return; - - player.isInIframe = (window.location != window.parent.location); - - // native events - if (mejs.MediaFeatures.hasTrueNativeFullScreen) { - - // chrome doesn't alays fire this in an iframe - var target = null; - - if (mejs.MediaFeatures.hasMozNativeFullScreen) { - target = $(document); - } else { - target = player.container; - } - - target.bind(mejs.MediaFeatures.fullScreenEventName, function(e) { - - if (mejs.MediaFeatures.isFullScreen()) { - player.isNativeFullScreen = true; - // reset the controls once we are fully in full screen - player.setControlsSize(); - } else { - player.isNativeFullScreen = false; - // when a user presses ESC - // make sure to put the player back into place - player.exitFullScreen(); - } - }); - } - - var t = this, - normalHeight = 0, - normalWidth = 0, - container = player.container, - fullscreenBtn = - $('<div class="mejs-button mejs-fullscreen-button">' + - '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '"></button>' + - '</div>') - .appendTo(controls); - - if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { - - fullscreenBtn.click(function() { - var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; - - if (isFullScreen) { - player.exitFullScreen(); - } else { - player.enterFullScreen(); - } - }); - - } else { - - var hideTimeout = null, - supportsPointerEvents = (function() { - // TAKEN FROM MODERNIZR - var element = document.createElement('x'), - documentElement = document.documentElement, - getComputedStyle = window.getComputedStyle, - supports; - if(!('pointerEvents' in element.style)){ - return false; - } - element.style.pointerEvents = 'auto'; - element.style.pointerEvents = 'x'; - documentElement.appendChild(element); - supports = getComputedStyle && - getComputedStyle(element, '').pointerEvents === 'auto'; - documentElement.removeChild(element); - return !!supports; - })(); - - //console.log('supportsPointerEvents', supportsPointerEvents); - - if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( - - // allows clicking through the fullscreen button and controls down directly to Flash - - /* - When a user puts his mouse over the fullscreen button, the controls are disabled - So we put a div over the video and another one on iether side of the fullscreen button - that caputre mouse movement - and restore the controls once the mouse moves outside of the fullscreen button - */ - - var fullscreenIsDisabled = false, - restoreControls = function() { - if (fullscreenIsDisabled) { - // hide the hovers - videoHoverDiv.hide(); - controlsLeftHoverDiv.hide(); - controlsRightHoverDiv.hide(); - - // restore the control bar - fullscreenBtn.css('pointer-events', ''); - t.controls.css('pointer-events', ''); - - // store for later - fullscreenIsDisabled = false; - } - }, - videoHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), - controlsLeftHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), - controlsRightHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), - positionHoverDivs = function() { - var style = {position: 'absolute', top: 0, left: 0}; //, backgroundColor: '#f00'}; - videoHoverDiv.css(style); - controlsLeftHoverDiv.css(style); - controlsRightHoverDiv.css(style); - - // over video, but not controls - videoHoverDiv - .width( t.container.width() ) - .height( t.container.height() - t.controls.height() ); - - // over controls, but not the fullscreen button - var fullScreenBtnOffset = fullscreenBtn.offset().left - t.container.offset().left; - fullScreenBtnWidth = fullscreenBtn.outerWidth(true); - - controlsLeftHoverDiv - .width( fullScreenBtnOffset ) - .height( t.controls.height() ) - .css({top: t.container.height() - t.controls.height()}); - - // after the fullscreen button - controlsRightHoverDiv - .width( t.container.width() - fullScreenBtnOffset - fullScreenBtnWidth ) - .height( t.controls.height() ) - .css({top: t.container.height() - t.controls.height(), - left: fullScreenBtnOffset + fullScreenBtnWidth}); - }; - - $(document).resize(function() { - positionHoverDivs(); - }); - - // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash - fullscreenBtn - .mouseover(function() { - - if (!t.isFullScreen) { - - var buttonPos = fullscreenBtn.offset(), - containerPos = player.container.offset(); - - // move the button in Flash into place - media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); - - // allows click through - fullscreenBtn.css('pointer-events', 'none'); - t.controls.css('pointer-events', 'none'); - - // show the divs that will restore things - videoHoverDiv.show(); - controlsRightHoverDiv.show(); - controlsLeftHoverDiv.show(); - positionHoverDivs(); - - fullscreenIsDisabled = true; - } - - }); - - // restore controls anytime the user enters or leaves fullscreen - media.addEventListener('fullscreenchange', function(e) { - restoreControls(); - }); - - - // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events - // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button - /* - $(document).mousemove(function(e) { - - // if the mouse is anywhere but the fullsceen button, then restore it all - if (fullscreenIsDisabled) { - - var fullscreenBtnPos = fullscreenBtn.offset(); - - - if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || - e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) - ) { - - fullscreenBtn.css('pointer-events', ''); - t.controls.css('pointer-events', ''); - - fullscreenIsDisabled = false; - } - } - }); - */ - - - } else { - - // the hover state will show the fullscreen button in Flash to hover up and click - - fullscreenBtn - .mouseover(function() { - - if (hideTimeout !== null) { - clearTimeout(hideTimeout); - delete hideTimeout; - } - - var buttonPos = fullscreenBtn.offset(), - containerPos = player.container.offset(); - - media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); - - }) - .mouseout(function() { - - if (hideTimeout !== null) { - clearTimeout(hideTimeout); - delete hideTimeout; - } - - hideTimeout = setTimeout(function() { - media.hideFullscreenButton(); - }, 1500); - - - }); - } - } - - player.fullscreenBtn = fullscreenBtn; - - $(document).bind('keydown',function (e) { - if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { - player.exitFullScreen(); - } - }); - - }, - enterFullScreen: function() { - - var t = this; - - // firefox+flash can't adjust plugin sizes without resetting :( - if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { - //t.media.setFullscreen(true); - //player.isFullScreen = true; - return; - } - - // store overflow - docStyleOverflow = document.documentElement.style.overflow; - // set it to not show scroll bars so 100% will work - document.documentElement.style.overflow = 'hidden'; - - // store sizing - normalHeight = t.container.height(); - normalWidth = t.container.width(); - - // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) - if (t.media.pluginType === 'native') { - if (mejs.MediaFeatures.hasTrueNativeFullScreen) { - - mejs.MediaFeatures.requestFullScreen(t.container[0]); - //return; - - if (t.isInIframe) { - // sometimes exiting from fullscreen doesn't work - // notably in Chrome <iframe>. Fixed in version 17 - setTimeout(function checkFullscreen() { - - if (t.isNativeFullScreen) { - - // check if the video is suddenly not really fullscreen - if ($(window).width() !== screen.width) { - // manually exit - t.exitFullScreen(); - } else { - // test again - setTimeout(checkFullscreen, 500); - } - } - - - }, 500); - } - - } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { - t.media.webkitEnterFullscreen(); - return; - } - } - - // check for iframe launch - if (t.isInIframe) { - var url = t.options.newWindowCallback(this); - - - if (url !== '') { - - // launch immediately - if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { - t.pause(); - window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); - return; - } else { - setTimeout(function() { - if (!t.isNativeFullScreen) { - t.pause(); - window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); - } - }, 250); - } - } - - } - - // full window code - - - - // make full size - t.container - .addClass('mejs-container-fullscreen') - .width('100%') - .height('100%'); - //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); - - // Only needed for safari 5.1 native full screen, can cause display issues elsewhere - // Actually, it seems to be needed for IE8, too - //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { - setTimeout(function() { - t.container.css({width: '100%', height: '100%'}); - t.setControlsSize(); - }, 500); - //} - - if (t.pluginType === 'native') { - t.$media - .width('100%') - .height('100%'); - } else { - t.container.find('object, embed, iframe') - .width('100%') - .height('100%'); - - //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { - t.media.setVideoSize($(window).width(),$(window).height()); - //} - } - - t.layers.children('div') - .width('100%') - .height('100%'); - - if (t.fullscreenBtn) { - t.fullscreenBtn - .removeClass('mejs-fullscreen') - .addClass('mejs-unfullscreen'); - } - - t.setControlsSize(); - t.isFullScreen = true; - }, - - exitFullScreen: function() { - - var t = this; - - // firefox can't adjust plugins - if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { - t.media.setFullscreen(false); - //player.isFullScreen = false; - return; - } - - // come outo of native fullscreen - if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { - mejs.MediaFeatures.cancelFullScreen(); - } - - // restore scroll bars to document - document.documentElement.style.overflow = docStyleOverflow; - - t.container - .removeClass('mejs-container-fullscreen') - .width(normalWidth) - .height(normalHeight); - //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); - - if (t.pluginType === 'native') { - t.$media - .width(normalWidth) - .height(normalHeight); - } else { - t.container.find('object embed') - .width(normalWidth) - .height(normalHeight); - - t.media.setVideoSize(normalWidth, normalHeight); - } - - t.layers.children('div') - .width(normalWidth) - .height(normalHeight); - - t.fullscreenBtn - .removeClass('mejs-unfullscreen') - .addClass('mejs-fullscreen'); - - t.setControlsSize(); - t.isFullScreen = false; - } - }); - -})(mejs.$); +(function($) { + $.extend(mejs.MepDefaults, { + usePluginFullScreen: true, + newWindowCallback: function() { return '';}, + fullscreenText: mejs.i18n.t('Fullscreen') + }); + + $.extend(MediaElementPlayer.prototype, { + + isFullScreen: false, + + isNativeFullScreen: false, + + docStyleOverflow: null, + + isInIframe: false, + + buildfullscreen: function(player, controls, layers, media) { + + if (!player.isVideo) + return; + + player.isInIframe = (window.location != window.parent.location); + + // native events + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + // chrome doesn't alays fire this in an iframe + var target = null; + + if (mejs.MediaFeatures.hasMozNativeFullScreen) { + target = $(document); + } else { + target = player.container; + } + + target.bind(mejs.MediaFeatures.fullScreenEventName, function(e) { + + if (mejs.MediaFeatures.isFullScreen()) { + player.isNativeFullScreen = true; + // reset the controls once we are fully in full screen + player.setControlsSize(); + } else { + player.isNativeFullScreen = false; + // when a user presses ESC + // make sure to put the player back into place + player.exitFullScreen(); + } + }); + } + + var t = this, + normalHeight = 0, + normalWidth = 0, + container = player.container, + fullscreenBtn = + $('<div class="mejs-button mejs-fullscreen-button">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '"></button>' + + '</div>') + .appendTo(controls); + + if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { + + fullscreenBtn.click(function() { + var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; + + if (isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + }); + + } else { + + var hideTimeout = null, + supportsPointerEvents = (function() { + // TAKEN FROM MODERNIZR + var element = document.createElement('x'), + documentElement = document.documentElement, + getComputedStyle = window.getComputedStyle, + supports; + if(!('pointerEvents' in element.style)){ + return false; + } + element.style.pointerEvents = 'auto'; + element.style.pointerEvents = 'x'; + documentElement.appendChild(element); + supports = getComputedStyle && + getComputedStyle(element, '').pointerEvents === 'auto'; + documentElement.removeChild(element); + return !!supports; + })(); + + //console.log('supportsPointerEvents', supportsPointerEvents); + + if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( + + // allows clicking through the fullscreen button and controls down directly to Flash + + /* + When a user puts his mouse over the fullscreen button, the controls are disabled + So we put a div over the video and another one on iether side of the fullscreen button + that caputre mouse movement + and restore the controls once the mouse moves outside of the fullscreen button + */ + + var fullscreenIsDisabled = false, + restoreControls = function() { + if (fullscreenIsDisabled) { + // hide the hovers + videoHoverDiv.hide(); + controlsLeftHoverDiv.hide(); + controlsRightHoverDiv.hide(); + + // restore the control bar + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + // store for later + fullscreenIsDisabled = false; + } + }, + videoHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), + controlsLeftHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), + controlsRightHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), + positionHoverDivs = function() { + var style = {position: 'absolute', top: 0, left: 0}; //, backgroundColor: '#f00'}; + videoHoverDiv.css(style); + controlsLeftHoverDiv.css(style); + controlsRightHoverDiv.css(style); + + // over video, but not controls + videoHoverDiv + .width( t.container.width() ) + .height( t.container.height() - t.controls.height() ); + + // over controls, but not the fullscreen button + var fullScreenBtnOffset = fullscreenBtn.offset().left - t.container.offset().left; + fullScreenBtnWidth = fullscreenBtn.outerWidth(true); + + controlsLeftHoverDiv + .width( fullScreenBtnOffset ) + .height( t.controls.height() ) + .css({top: t.container.height() - t.controls.height()}); + + // after the fullscreen button + controlsRightHoverDiv + .width( t.container.width() - fullScreenBtnOffset - fullScreenBtnWidth ) + .height( t.controls.height() ) + .css({top: t.container.height() - t.controls.height(), + left: fullScreenBtnOffset + fullScreenBtnWidth}); + }; + + $(document).resize(function() { + positionHoverDivs(); + }); + + // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash + fullscreenBtn + .mouseover(function() { + + if (!t.isFullScreen) { + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + // move the button in Flash into place + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); + + // allows click through + fullscreenBtn.css('pointer-events', 'none'); + t.controls.css('pointer-events', 'none'); + + // show the divs that will restore things + videoHoverDiv.show(); + controlsRightHoverDiv.show(); + controlsLeftHoverDiv.show(); + positionHoverDivs(); + + fullscreenIsDisabled = true; + } + + }); + + // restore controls anytime the user enters or leaves fullscreen + media.addEventListener('fullscreenchange', function(e) { + restoreControls(); + }); + + + // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events + // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button + /* + $(document).mousemove(function(e) { + + // if the mouse is anywhere but the fullsceen button, then restore it all + if (fullscreenIsDisabled) { + + var fullscreenBtnPos = fullscreenBtn.offset(); + + + if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || + e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) + ) { + + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + fullscreenIsDisabled = false; + } + } + }); + */ + + + } else { + + // the hover state will show the fullscreen button in Flash to hover up and click + + fullscreenBtn + .mouseover(function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); + + }) + .mouseout(function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + hideTimeout = setTimeout(function() { + media.hideFullscreenButton(); + }, 1500); + + + }); + } + } + + player.fullscreenBtn = fullscreenBtn; + + $(document).bind('keydown',function (e) { + if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { + player.exitFullScreen(); + } + }); + + }, + enterFullScreen: function() { + + var t = this; + + // firefox+flash can't adjust plugin sizes without resetting :( + if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { + //t.media.setFullscreen(true); + //player.isFullScreen = true; + return; + } + + // store overflow + docStyleOverflow = document.documentElement.style.overflow; + // set it to not show scroll bars so 100% will work + document.documentElement.style.overflow = 'hidden'; + + // store sizing + normalHeight = t.container.height(); + normalWidth = t.container.width(); + + // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) + if (t.media.pluginType === 'native') { + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + mejs.MediaFeatures.requestFullScreen(t.container[0]); + //return; + + if (t.isInIframe) { + // sometimes exiting from fullscreen doesn't work + // notably in Chrome <iframe>. Fixed in version 17 + setTimeout(function checkFullscreen() { + + if (t.isNativeFullScreen) { + + // check if the video is suddenly not really fullscreen + if ($(window).width() !== screen.width) { + // manually exit + t.exitFullScreen(); + } else { + // test again + setTimeout(checkFullscreen, 500); + } + } + + + }, 500); + } + + } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { + t.media.webkitEnterFullscreen(); + return; + } + } + + // check for iframe launch + if (t.isInIframe) { + var url = t.options.newWindowCallback(this); + + + if (url !== '') { + + // launch immediately + if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + return; + } else { + setTimeout(function() { + if (!t.isNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + } + }, 250); + } + } + + } + + // full window code + + + + // make full size + t.container + .addClass('mejs-container-fullscreen') + .width('100%') + .height('100%'); + //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); + + // Only needed for safari 5.1 native full screen, can cause display issues elsewhere + // Actually, it seems to be needed for IE8, too + //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + setTimeout(function() { + t.container.css({width: '100%', height: '100%'}); + t.setControlsSize(); + }, 500); + //} + + if (t.pluginType === 'native') { + t.$media + .width('100%') + .height('100%'); + } else { + t.container.find('object, embed, iframe') + .width('100%') + .height('100%'); + + //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.media.setVideoSize($(window).width(),$(window).height()); + //} + } + + t.layers.children('div') + .width('100%') + .height('100%'); + + if (t.fullscreenBtn) { + t.fullscreenBtn + .removeClass('mejs-fullscreen') + .addClass('mejs-unfullscreen'); + } + + t.setControlsSize(); + t.isFullScreen = true; + }, + + exitFullScreen: function() { + + var t = this; + + // firefox can't adjust plugins + if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { + t.media.setFullscreen(false); + //player.isFullScreen = false; + return; + } + + // come outo of native fullscreen + if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { + mejs.MediaFeatures.cancelFullScreen(); + } + + // restore scroll bars to document + document.documentElement.style.overflow = docStyleOverflow; + + t.container + .removeClass('mejs-container-fullscreen') + .width(normalWidth) + .height(normalHeight); + //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); + + if (t.pluginType === 'native') { + t.$media + .width(normalWidth) + .height(normalHeight); + } else { + t.container.find('object embed') + .width(normalWidth) + .height(normalHeight); + + t.media.setVideoSize(normalWidth, normalHeight); + } + + t.layers.children('div') + .width(normalWidth) + .height(normalHeight); + + t.fullscreenBtn + .removeClass('mejs-unfullscreen') + .addClass('mejs-fullscreen'); + + t.setControlsSize(); + t.isFullScreen = false; + } + }); + +})(mejs.$); + (function($) { // add extra default options $.extend(mejs.MepDefaults, { // this will automatically turn on a <track> @@ -2150,16 +2178,16 @@ //}); if (!player.options.alwaysShowControls) { // move with controls player.container - .bind('mouseenter', function () { + .bind('controlsshown', function () { // push captions above controls player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); }) - .bind('mouseleave', function () { + .bind('controlshidden', function () { if (!media.paused) { // move back to normal place player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); } }); @@ -2241,39 +2269,37 @@ t.loadNextTrack(); }; - if (track.isTranslation) { - // translate the first track - mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) { + $.ajax({ + url: track.src, + dataType: "text", + success: function(d) { - // store the new translation - track.entries = newOne; - + // parse the loaded file + if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { + track.entries = mejs.TrackFormatParser.dfxp.parse(d); + } else { + track.entries = mejs.TrackFormatParser.webvvt.parse(d); + } + after(); - }); - } else { - $.ajax({ - url: track.src, - success: function(d) { - - // parse the loaded file - track.entries = mejs.TrackFormatParser.parse(d); - after(); - - if (track.kind == 'chapters' && t.media.duration > 0) { - t.drawChapters(track); - } - }, - error: function() { - t.loadNextTrack(); + if (track.kind == 'chapters') { + t.media.addEventListener('play', function(e) { + if (t.media.duration > 0) { + t.displayChapters(track); + } + }, false); } - }); - } + }, + error: function() { + t.loadNextTrack(); + } + }); }, enableTrackButton: function(lang, label) { var t = this; @@ -2487,57 +2513,110 @@ =============================== Adapted from: http://www.delphiki.com/html5/playr */ mejs.TrackFormatParser = { - // match start "chapter-" (or anythingelse) - pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/, - pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, + webvvt: { + // match start "chapter-" (or anythingelse) + pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/, + pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, - split2: function (text, regex) { - // normal version for compliant browsers - // see below for IE fix - return text.split(regex); - }, - parse: function(trackText) { - var - i = 0, - lines = this.split2(trackText, /\r?\n/), - entries = {text:[], times:[]}, - timecode, - text; - - for(; i<lines.length; i++) { - // check for the line number - if (this.pattern_identifier.exec(lines[i])){ - // skip to the next line where the start --> end time code should be - i++; - timecode = this.pattern_timecode.exec(lines[i]); - - if (timecode && i<lines.length){ + parse: function(trackText) { + var + i = 0, + lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), + entries = {text:[], times:[]}, + timecode, + text; + for(; i<lines.length; i++) { + // check for the line number + if (this.pattern_identifier.exec(lines[i])){ + // skip to the next line where the start --> end time code should be i++; - // grab all the (possibly multi-line) text that follows - text = lines[i]; - i++; - while(lines[i] !== '' && i<lines.length){ - text = text + '\n' + lines[i]; + timecode = this.pattern_timecode.exec(lines[i]); + + if (timecode && i<lines.length){ i++; + // grab all the (possibly multi-line) text that follows + text = lines[i]; + i++; + while(lines[i] !== '' && i<lines.length){ + text = text + '\n' + lines[i]; + i++; + } + text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + // Text is in a different array so I can use .join + entries.text.push(text); + entries.times.push( + { + start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) == 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), + stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), + settings: timecode[5] + }); } - - // Text is in a different array so I can use .join - entries.text.push(text); - entries.times.push( - { - start: mejs.Utility.timeCodeToSeconds(timecode[1]), - stop: mejs.Utility.timeCodeToSeconds(timecode[3]), - settings: timecode[5] - }); } } + return entries; } + }, + // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 + dfxp: { + parse: function(trackText) { + trackText = $(trackText).filter("tt"); + var + i = 0, + container = trackText.children("div").eq(0), + lines = container.find("p"), + styleNode = trackText.find("#" + container.attr("style")), + styles, + begin, + end, + text, + entries = {text:[], times:[]}; - return entries; + + if (styleNode.length) { + var attributes = styleNode.removeAttr("id").get(0).attributes; + if (attributes.length) { + styles = {}; + for (i = 0; i < attributes.length; i++) { + styles[attributes[i].name.split(":")[1]] = attributes[i].value; + } + } + } + + for(i = 0; i<lines.length; i++) { + var style; + var _temp_times = { + start: null, + stop: null, + style: null + }; + if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); + if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); + if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); + if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); + if (styles) { + style = ""; + for (var _style in styles) { + style += _style + ":" + styles[_style] + ";"; + } + } + if (style) _temp_times.style = style; + if (_temp_times.start == 0) _temp_times.start = 0.200; + entries.times.push(_temp_times); + text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + entries.text.push(text); + if (entries.times.start == 0) entries.times.start = 2; + } + return entries; + } + }, + split2: function (text, regex) { + // normal version for compliant browsers + // see below for IE fix + return text.split(regex); } }; // test for browsers with bad String.split method. if ('x\n\ny'.split(/\n/gi).length != 3) { @@ -2560,198 +2639,233 @@ } } })(mejs.$); -/* -* ContextMenu Plugin -* -* -*/ - -(function($) { - -$.extend(mejs.MepDefaults, - { 'contextMenuItems': [ - // demo of a fullscreen option - { - render: function(player) { - - // check for fullscreen plugin - if (typeof player.enterFullScreen == 'undefined') - return null; - - if (player.isFullScreen) { - return "Turn off Fullscreen"; - } else { - return "Go Fullscreen"; - } - }, - click: function(player) { - if (player.isFullScreen) { - player.exitFullScreen(); - } else { - player.enterFullScreen(); - } - } - } - , - // demo of a mute/unmute button - { - render: function(player) { - if (player.media.muted) { - return "Unmute"; - } else { - return "Mute"; - } - }, - click: function(player) { - if (player.media.muted) { - player.setMuted(false); - } else { - player.setMuted(true); - } - } - }, - // separator - { - isSeparator: true - } - , - // demo of simple download video - { - render: function(player) { - return "Download Video"; - }, - click: function(player) { - window.location.href = player.media.currentSrc; - } - } - ]} -); - - - $.extend(MediaElementPlayer.prototype, { - buildcontextmenu: function(player, controls, layers, media) { - - // create context menu - player.contextMenu = $('<div class="mejs-contextmenu"></div>') - .appendTo($('body')) - .hide(); - - // create events for showing context menu - player.container.bind('contextmenu', function(e) { - if (player.isContextMenuEnabled) { - e.preventDefault(); - player.renderContextMenu(e.clientX-1, e.clientY-1); - return false; - } - }); - player.container.bind('click', function() { - player.contextMenu.hide(); - }); - player.contextMenu.bind('mouseleave', function() { - - //console.log('context hover out'); - player.startContextMenuTimer(); - - }); - }, - - isContextMenuEnabled: true, - enableContextMenu: function() { - this.isContextMenuEnabled = true; - }, - disableContextMenu: function() { - this.isContextMenuEnabled = false; - }, - - contextMenuTimeout: null, - startContextMenuTimer: function() { - //console.log('startContextMenuTimer'); - - var t = this; - - t.killContextMenuTimer(); - - t.contextMenuTimer = setTimeout(function() { - t.hideContextMenu(); - t.killContextMenuTimer(); - }, 750); - }, - killContextMenuTimer: function() { - var timer = this.contextMenuTimer; - - //console.log('killContextMenuTimer', timer); - - if (timer != null) { - clearTimeout(timer); - delete timer; - timer = null; - } - }, - - hideContextMenu: function() { - this.contextMenu.hide(); - }, - - renderContextMenu: function(x,y) { - - // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly - var t = this, - html = '', - items = t.options.contextMenuItems; - - for (var i=0, il=items.length; i<il; i++) { - - if (items[i].isSeparator) { - html += '<div class="mejs-contextmenu-separator"></div>'; - } else { - - var rendered = items[i].render(t); - - // render can return null if the item doesn't need to be used at the moment - if (rendered != null) { - html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; - } - } - } - - // position and show the context menu - t.contextMenu - .empty() - .append($(html)) - .css({top:y, left:x}) - .show(); - - // bind events - t.contextMenu.find('.mejs-contextmenu-item').each(function() { - - // which one is this? - var $dom = $(this), - itemIndex = parseInt( $dom.data('itemindex'), 10 ), - item = t.options.contextMenuItems[itemIndex]; - - // bind extra functionality? - if (typeof item.show != 'undefined') - item.show( $dom , t); - - // bind click action - $dom.click(function() { - // perform click action - if (typeof item.click != 'undefined') - item.click(t); - - // close - t.contextMenu.hide(); - }); - }); - - // stop the controls from hiding - setTimeout(function() { - t.killControlsTimer('rev3'); - }, 100); - - } - }); - +/* +* ContextMenu Plugin +* +* +*/ + +(function($) { + +$.extend(mejs.MepDefaults, + { 'contextMenuItems': [ + // demo of a fullscreen option + { + render: function(player) { + + // check for fullscreen plugin + if (typeof player.enterFullScreen == 'undefined') + return null; + + if (player.isFullScreen) { + return "Turn off Fullscreen"; + } else { + return "Go Fullscreen"; + } + }, + click: function(player) { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + , + // demo of a mute/unmute button + { + render: function(player) { + if (player.media.muted) { + return "Unmute"; + } else { + return "Mute"; + } + }, + click: function(player) { + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + }, + // separator + { + isSeparator: true + } + , + // demo of simple download video + { + render: function(player) { + return "Download Video"; + }, + click: function(player) { + window.location.href = player.media.currentSrc; + } + } + ]} +); + + + $.extend(MediaElementPlayer.prototype, { + buildcontextmenu: function(player, controls, layers, media) { + + // create context menu + player.contextMenu = $('<div class="mejs-contextmenu"></div>') + .appendTo($('body')) + .hide(); + + // create events for showing context menu + player.container.bind('contextmenu', function(e) { + if (player.isContextMenuEnabled) { + e.preventDefault(); + player.renderContextMenu(e.clientX-1, e.clientY-1); + return false; + } + }); + player.container.bind('click', function() { + player.contextMenu.hide(); + }); + player.contextMenu.bind('mouseleave', function() { + + //console.log('context hover out'); + player.startContextMenuTimer(); + + }); + }, + + isContextMenuEnabled: true, + enableContextMenu: function() { + this.isContextMenuEnabled = true; + }, + disableContextMenu: function() { + this.isContextMenuEnabled = false; + }, + + contextMenuTimeout: null, + startContextMenuTimer: function() { + //console.log('startContextMenuTimer'); + + var t = this; + + t.killContextMenuTimer(); + + t.contextMenuTimer = setTimeout(function() { + t.hideContextMenu(); + t.killContextMenuTimer(); + }, 750); + }, + killContextMenuTimer: function() { + var timer = this.contextMenuTimer; + + //console.log('killContextMenuTimer', timer); + + if (timer != null) { + clearTimeout(timer); + delete timer; + timer = null; + } + }, + + hideContextMenu: function() { + this.contextMenu.hide(); + }, + + renderContextMenu: function(x,y) { + + // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly + var t = this, + html = '', + items = t.options.contextMenuItems; + + for (var i=0, il=items.length; i<il; i++) { + + if (items[i].isSeparator) { + html += '<div class="mejs-contextmenu-separator"></div>'; + } else { + + var rendered = items[i].render(t); + + // render can return null if the item doesn't need to be used at the moment + if (rendered != null) { + html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; + } + } + } + + // position and show the context menu + t.contextMenu + .empty() + .append($(html)) + .css({top:y, left:x}) + .show(); + + // bind events + t.contextMenu.find('.mejs-contextmenu-item').each(function() { + + // which one is this? + var $dom = $(this), + itemIndex = parseInt( $dom.data('itemindex'), 10 ), + item = t.options.contextMenuItems[itemIndex]; + + // bind extra functionality? + if (typeof item.show != 'undefined') + item.show( $dom , t); + + // bind click action + $dom.click(function() { + // perform click action + if (typeof item.click != 'undefined') + item.click(t); + + // close + t.contextMenu.hide(); + }); + }); + + // stop the controls from hiding + setTimeout(function() { + t.killControlsTimer('rev3'); + }, 100); + + } + }); + +})(mejs.$); +/** + * Postroll plugin + */ +(function($) { + + $.extend(mejs.MepDefaults, { + postrollCloseText: mejs.i18n.t('Close') + }); + + // Postroll + $.extend(MediaElementPlayer.prototype, { + buildpostroll: function(player, controls, layers, media) { + var + t = this, + postrollLink = t.container.find('link[rel="postroll"]').attr('href'); + + if (typeof postrollLink !== 'undefined') { + player.postroll = + $('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + t.options.postrollCloseText + '</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide(); + + t.media.addEventListener('ended', function (e) { + $.ajax({ + dataType: 'html', + url: postrollLink, + success: function (data, textStatus) { + layers.find('.mejs-postroll-layer-content').html(data); + } + }); + player.postroll.show(); + }, false); + } + } + }); + })(mejs.$);