/*! * * MediaElementPlayer * http://mediaelementjs.com/ * * Creates a controller bar for HTML5 <video> add <audio> tags * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) * * Copyright 2010-2013, John Dyer (http://j.hn/) * 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: '', // When the video is ended, we can show the poster. showPosterWhenEnded: false, // 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); }, // set dimensions via JS instead of CSS setDimensions: true, // 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, // Display the video control hideVideoControlsOnLoad: 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) { player.play(); } else { player.pause(); } } }, { keys: [38], // UP action: function(player, media) { player.container.find('.mejs-volume-slider').css('display','block'); if (player.isVideo) { player.showControls(); player.startControlsTimer(); } var newVolume = Math.min(media.volume + 0.1, 1); media.setVolume(newVolume); } }, { keys: [40], // DOWN action: function(player, media) { player.container.find('.mejs-volume-slider').css('display','block'); if (player.isVideo) { player.showControls(); player.startControlsTimer(); } 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(); } } } }, { keys: [77], // M action: function(player, media) { player.container.find('.mejs-volume-slider').css('display','block'); if (player.isVideo) { player.showControls(); player.startControlsTimer(); } if (player.media.muted) { player.setMuted(false); } else { player.setMuted(true); } } } ] }; 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); // unique ID t.id = 'mep_' + mejs.mepIndex++; // add to player array (for focus events) mejs.players[t.id] = 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.play(); } } else if (mf.isAndroid && t.options.AndroidUseNativeControls) { // leave default player } else { // DESKTOP: use MediaElementPlayer controls // remove native controls t.$media.removeAttr('controls'); var videoPlayerTitle = t.isVideo ? mejs.i18n.t('Video Player') : mejs.i18n.t('Audio Player'); // insert description for screen readers $('<span class="mejs-offscreen">' + videoPlayerTitle + '</span>').insertBefore(t.$media); // build container t.container = $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '" tabindex="0" role="application" aria-label="' + videoPlayerTitle + '">'+ '<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) .focus(function ( e ) { if( !t.controlsAreVisible ) { t.showControls(true); var playButton = t.container.find('.mejs-playpause-button > button'); playButton.focus(); } }); // 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.width; meOptions.pluginHeight = t.height; } // create MediaElement shim mejs.MediaElement(t.$media[0], meOptions); if (typeof(t.container) != 'undefined' && t.controlsAreVisible){ // 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 || t.options.alwaysShowControls || t.keyboardAction) 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() { // 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; } } } 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 { // create callback here since it needs access to current // MediaElement object t.clickToPlayPauseCallback = function() { // if (t.options.clickToPlayPause) { if (t.media.paused) { t.play(); } else { t.pause(); } } }; // click to play/pause t.media.addEventListener('click', t.clickToPlayPauseCallback, false); // 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(); } if (!t.options.alwaysShowControls) { t.startControlsTimer(2500); } } }) .bind('mouseleave', function () { if (t.controlsEnabled) { if (!t.media.paused && !t.options.alwaysShowControls) { t.startControlsTimer(1000); } } }); } if(t.options.hideVideoControlsOnLoad) { t.hideControls(false); } // 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() { var playerIndex; // go through all other players for (playerIndex in mejs.players) { var p = mejs.players[playerIndex]; 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); // Fixing an Android stock browser bug, where "seeked" isn't fired correctly after ending the video and jumping to the beginning window.setTimeout(function(){ $(t.container).find('.mejs-overlay-loading').parent().hide(); }, 20); } catch (exp) { } } t.media.pause(); if (t.setProgressRail) { t.setProgressRail(); } if (t.setCurrentRail) { t.setCurrentRail(); } if (t.options.loop) { t.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); t.container.focusout(function (e) { if( e.relatedTarget ) { //FF is working on supporting focusout https://bugzilla.mozilla.org/show_bug.cgi?id=687787 var $target = $(e.relatedTarget); if (t.keyboardAction && $target.parents('.mejs-container').length === 0) { t.keyboardAction = false; t.hideControls(true); } } }); // 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) t.globalBind('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(); }); // This is a work-around for a bug in the YouTube iFrame player, which means // we can't use the play() API for the initial playback on iOS or Android; // user has to start playback directly by tapping on the iFrame. if (t.media.pluginType == 'youtube' && ( mf.isiOS || mf.isAndroid ) ) { t.container.find('.mejs-overlay-play').hide(); } } // force autoplay for HTML5 if (autoplay && media.pluginType == 'native') { t.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( !t.options.setDimensions ) { return false; } 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 = (function() { if (t.isVideo) { if (t.media.videoWidth && t.media.videoWidth > 0) { return t.media.videoWidth; } else if (t.media.getAttribute('width') !== null) { return t.media.getAttribute('width'); } else { return t.options.defaultVideoWidth; } } else { return t.options.defaultAudioWidth; } })(); var nativeHeight = (function() { if (t.isVideo) { if (t.media.videoHeight && t.media.videoHeight > 0) { return t.media.videoHeight; } else if (t.media.getAttribute('height') !== null) { return t.media.getAttribute('height'); } else { return t.options.defaultVideoHeight; } } else { return t.options.defaultAudioHeight; } })(); var parentWidth = t.container.parent().closest(':visible').width(), parentHeight = t.container.parent().closest(':visible').height(), newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight; // When we use percent, the newHeight can't be calculated so we get the container height if (isNaN(newHeight)) { newHeight = parentHeight; } if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { parentWidth = $(window).width(); newHeight = $(window).height(); } if ( newHeight && parentWidth ) { // set outer container size t.container .width(parentWidth) .height(newHeight); // set native <video> or <audio> and shims t.$media.add(t.container.find('.mejs-shim')) .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); } // special case for big play button so it doesn't go over the controls area var playLayer = t.layers.find('.mejs-overlay-play'), playButton = playLayer.find('.mejs-overlay-button'); playLayer.height(t.container.height() - t.controls.height()); playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' ); }, 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(), lastControl = others.last(), lastControlPosition = null; // skip calculation if hidden if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) { return; } // 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'), 10); } // attempt to autosize if (railWidth === 0 || !railWidth) { // find the size of all the other controls besides the rail others.each(function() { var $this = $(this); if ($this.css('position') != 'absolute' && $this.is(':visible')) { usedWidth += $(this).outerWidth(true); } }); // fit the rail into the remaining space railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); } // resize the rail, // but then check if the last control (say, the fullscreen button) got pushed down // this often happens when zoomed do { // outer area rail.width(railWidth); // dark space total.width(railWidth - (total.outerWidth(true) - total.width())); if (lastControl.css('position') != 'absolute') { lastControlPosition = lastControl.position(); railWidth--; } } while (lastControlPosition !== null && lastControlPosition.top > 0 && railWidth > 0); 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 ) { t.setPoster(posterUrl); } else { poster.hide(); } media.addEventListener('play',function() { poster.hide(); }, false); if(player.options.showPosterWhenEnded && player.options.autoRewind){ media.addEventListener('ended',function() { poster.show(); }, 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); posterDiv.css({'background-image' : 'url(' + 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) .bind('click', function() { // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video if (t.options.clickToPlayPause) { if (media.paused) { media.play(); } } }); /* 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(); // Firing the 'canplay' event after a timeout which isn't getting fired on some Android 4.1 devices (https://github.com/johndyer/mediaelement/issues/1305) if (mejs.MediaFeatures.isAndroid) { media.canplayTimeout = window.setTimeout( function() { if (document.createEvent) { var evt = document.createEvent('HTMLEvents'); evt.initEvent('canplay', true, true); return media.dispatchEvent(evt); } }, 300 ); } }, false); media.addEventListener('canplay',function() { loading.hide(); controls.find('.mejs-time-buffering').hide(); clearTimeout(media.canplayTimeout); // Clear timeout inside 'loadeddata' to prevent 'canplay' to fire twice }, 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); media.addEventListener('keydown', function(e) { t.onkeydown(player, media, e); }, false); }, buildkeyboard: function(player, controls, layers, media) { var t = this; t.container.keydown(function () { t.keyboardAction = true; }); // listen for key presses t.globalBind('keydown', function(e) { return t.onkeydown(player, media, e); }); // check if someone clicked outside a player region, then kill its focus t.globalBind('click', function(event) { player.hasFocus = $(event.target).closest('.mejs-container').length !== 0; }); }, onkeydown: function(player, media, 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]) { if (typeof(e.preventDefault) == "function") e.preventDefault(); keyAction.action(player, media, e.keyCode); return false; } } } } return true; }, 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')) ? 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.load(); this.media.play(); }, pause: function() { try { this.media.pause(); } catch (e) {} }, load: function() { if (!this.isLoaded) { this.media.load(); } this.isLoaded = true; }, 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, featureIndex, feature; // invoke features cleanup for (featureIndex in t.options.features) { feature = t.options.features[featureIndex]; if (t['clean' + feature]) { try { t['clean' + feature](t); } catch (e) { // TODO: report control error //throw e; // // } } } // grab video and put it back in place if (!t.isDynamic) { t.$media.prop('controls', true); // detach events from the video // TODO: detach event listeners better than this; // also detach ONLY the events attached by this plugin! t.$node.clone().insertBefore(t.container).show(); t.$node.remove(); } else { t.$node.insertBefore(t.container); } if (t.media.pluginType !== 'native') { t.media.remove(); } // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api. delete mejs.players[t.id]; if (typeof t.container == 'object') { t.container.remove(); } t.globalUnbind(); delete t.node.player; } }; (function(){ var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/; function splitEvents(events, id) { // add player ID as an event namespace so it's easier to unbind them all later var ret = {d: [], w: []}; $.each((events || '').split(' '), function(k, v){ var eventname = v + '.' + id; if (eventname.indexOf('.') === 0) { ret.d.push(eventname); ret.w.push(eventname); } else { ret[rwindow.test(v) ? 'w' : 'd'].push(eventname); } }); ret.d = ret.d.join(' '); ret.w = ret.w.join(' '); return ret; } mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) { var t = this; events = splitEvents(events, t.id); if (events.d) $(document).bind(events.d, data, callback); if (events.w) $(window).bind(events.w, data, callback); }; mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { var t = this; events = splitEvents(events, t.id); if (events.d) $(document).unbind(events.d, callback); if (events.w) $(window).unbind(events.w, callback); }; })(); // turn into jQuery plugin if (typeof $ != 'undefined') { $.fn.mediaelementplayer = function (options) { if (options === false) { this.each(function () { var player = $(this).data('mediaelementplayer'); if (player) { player.remove(); } $(this).removeData('mediaelementplayer'); }); } else { this.each(function () { $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options)); }); } return this; }; $(document).ready(function() { // auto enable using JSON attribute $('.mejs-player').mediaelementplayer(); }); } // push out to window window.MediaElementPlayer = mejs.MediaElementPlayer; })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { playText: mejs.i18n.t('Play'), pauseText: mejs.i18n.t('Pause') }); // PLAY/pause BUTTON $.extend(MediaElementPlayer.prototype, { buildplaypause: function(player, controls, layers, media) { var t = this, op = t.options, play = $('<div class="mejs-button mejs-playpause-button mejs-play" >' + '<button type="button" aria-controls="' + t.id + '" title="' + op.playText + '" aria-label="' + op.playText + '"></button>' + '</div>') .appendTo(controls) .click(function(e) { e.preventDefault(); if (media.paused) { media.play(); } else { media.pause(); } return false; }), play_btn = play.find('button'); function togglePlayPause(which) { if ('play' === which) { play.removeClass('mejs-play').addClass('mejs-pause'); play_btn.attr({ 'title': op.pauseText, 'aria-label': op.pauseText }); } else { play.removeClass('mejs-pause').addClass('mejs-play'); play_btn.attr({ 'title': op.playText, 'aria-label': op.playText }); } }; togglePlayPause('pse'); media.addEventListener('play',function() { togglePlayPause('play'); }, false); media.addEventListener('playing',function() { togglePlayPause('play'); }, false); media.addEventListener('pause',function() { togglePlayPause('pse'); }, false); media.addEventListener('paused',function() { togglePlayPause('pse'); }, false); } }); })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { stopText: 'Stop' }); // STOP BUTTON $.extend(MediaElementPlayer.prototype, { buildstop: function(player, controls, layers, media) { var t = this, stop = $('<div class="mejs-button mejs-stop-button mejs-stop">' + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' + '</div>') .appendTo(controls) .click(function() { if (!media.paused) { media.pause(); } if (media.currentTime > 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(); } }); } }); })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { progessHelpText: mejs.i18n.t( 'Use Left/Right Arrow keys to advance one second, Up/Down arrows to advance ten seconds.') }); // progress/loaded bar $.extend(MediaElementPlayer.prototype, { buildprogress: function(player, controls, layers, media) { $('<div class="mejs-time-rail">' + '<a href="javascript:void(0);" class="mejs-time-total mejs-time-slider">' + '<span class="mejs-offscreen">' + this.options.progessHelpText + '</span>' + '<span class="mejs-time-buffering"></span>' + '<span class="mejs-time-loaded"></span>' + '<span class="mejs-time-current"></span>' + '<span class="mejs-time-handle"></span>' + '<span class="mejs-time-float">' + '<span class="mejs-time-float-current">00:00</span>' + '<span class="mejs-time-float-corner"></span>' + '</span>' + '</a>' + '</div>') .appendTo(controls); controls.find('.mejs-time-buffering').hide(); var t = this, total = controls.find('.mejs-time-total'), loaded = controls.find('.mejs-time-loaded'), current = controls.find('.mejs-time-current'), handle = controls.find('.mejs-time-handle'), timefloat = controls.find('.mejs-time-float'), timefloatcurrent = controls.find('.mejs-time-float-current'), slider = controls.find('.mejs-time-slider'), handleMouseMove = function (e) { var offset = total.offset(), width = total.outerWidth(true), percentage = 0, newTime = 0, pos = 0, x; // mouse or touch position relative to the object if (e.originalEvent.changedTouches) { x = e.originalEvent.changedTouches[0].pageX; }else{ x = e.pageX; } 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 && newTime !== media.currentTime) { media.setCurrentTime(newTime); } // position floating time box if (!mejs.MediaFeatures.hasTouch) { timefloat.css('left', pos); timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); timefloat.show(); } } }, mouseIsDown = false, mouseIsOver = false, lastKeyPressTime = 0, startedPaused = false, autoRewindInitial = player.options.autoRewind; // Accessibility for slider var updateSlider = function (e) { var seconds = media.currentTime, timeSliderText = mejs.i18n.t('Time Slider'), time = mejs.Utility.secondsToTimeCode(seconds), duration = media.duration; slider.attr({ 'aria-label': timeSliderText, 'aria-valuemin': 0, 'aria-valuemax': duration, 'aria-valuenow': seconds, 'aria-valuetext': time, 'role': 'slider', 'tabindex': 0 }); }; var restartPlayer = function () { var now = new Date(); if (now - lastKeyPressTime >= 1000) { media.play(); } }; slider.bind('focus', function (e) { player.options.autoRewind = false; }); slider.bind('blur', function (e) { player.options.autoRewind = autoRewindInitial; }); slider.bind('keydown', function (e) { if ((new Date() - lastKeyPressTime) >= 1000) { startedPaused = media.paused; } var keyCode = e.keyCode, duration = media.duration, seekTime = media.currentTime; switch (keyCode) { case 37: // left seekTime -= 1; break; case 39: // Right seekTime += 1; break; case 38: // Up seekTime += Math.floor(duration * 0.1); break; case 40: // Down seekTime -= Math.floor(duration * 0.1); break; case 36: // Home seekTime = 0; break; case 35: // end seekTime = duration; break; case 10: // enter media.paused ? media.play() : media.pause(); return; case 13: // space media.paused ? media.play() : media.pause(); return; default: return; } seekTime = seekTime < 0 ? 0 : (seekTime >= duration ? duration : Math.floor(seekTime)); lastKeyPressTime = new Date(); if (!startedPaused) { media.pause(); } if (seekTime < media.duration && !startedPaused) { setTimeout(restartPlayer, 1100); } media.setCurrentTime(seekTime); e.preventDefault(); e.stopPropagation(); return false; }); // handle clicks //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); total .bind('mousedown touchstart', function (e) { // only handle left clicks or touch if (e.which === 1 || e.which === 0) { mouseIsDown = true; handleMouseMove(e); t.globalBind('mousemove.dur touchmove.dur', function(e) { handleMouseMove(e); }); t.globalBind('mouseup.dur touchend.dur', function (e) { mouseIsDown = false; timefloat.hide(); t.globalUnbind('.dur'); }); } }) .bind('mouseenter', function(e) { mouseIsOver = true; t.globalBind('mousemove.dur', function(e) { handleMouseMove(e); }); if (!mejs.MediaFeatures.hasTouch) { timefloat.show(); } }) .bind('mouseleave',function(e) { mouseIsOver = false; if (!mouseIsDown) { t.globalUnbind('.dur'); timefloat.hide(); } }); // loading media.addEventListener('progress', function (e) { player.setProgressRail(e); player.setCurrentRail(e); }, false); // current time media.addEventListener('timeupdate', function(e) { player.setProgressRail(e); player.setCurrentRail(e); updateSlider(e); }, false); // store for later use t.loaded = loaded; t.total = total; t.current = current; t.handle = handle; }, setProgressRail: function(e) { var t = this, target = (e !== undefined) ? e.target : t.media, percent = null; // newest HTML5 spec has buffered array (FF4, Webkit) if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { // TODO: account for a real array with multiple values (only Firefox 4 has this so far) percent = target.buffered.end(0) / target.duration; } // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() // to be anything other than 0. If the byte count is available we use this instead. // Browsers that support the else if do not seem to have the bufferedBytes value and // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. else if (target && target.bytesTotal !== undefined && target.bytesTotal > 0 && target.bufferedBytes !== undefined) { percent = target.bufferedBytes / target.bytesTotal; } // Firefox 3 with an Ogg file seems to go this way else if (e && e.lengthComputable && e.total !== 0) { percent = e.loaded / e.total; } // finally update the progress bar if (percent !== null) { percent = Math.min(1, Math.max(0, percent)); // update loaded bar if (t.loaded && t.total) { t.loaded.width(t.total.width() * percent); } } }, setCurrentRail: function() { var t = this; if (t.media.currentTime !== undefined && t.media.duration) { // update bar and handle if (t.total && t.handle) { var newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration), handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2); t.current.width(newWidth); t.handle.css('left', handlePos); } } } }); })(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" role="timer" aria-live="off">' + '<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.durationD && (t.options.duration > 0 || t.media.duration)) { t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); } } }); })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { muteText: mejs.i18n.t('Mute Toggle'), allyVolumeControlText: mejs.i18n.t('Use Up/Down Arrow keys to increase or decrease volume.'), hideVolumeOnTouchDevices: true, audioVolume: 'horizontal', videoVolume: 'vertical' }); $.extend(MediaElementPlayer.prototype, { buildvolume: function(player, controls, layers, media) { // Android and iOS don't support volume controls if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices) return; var t = this, mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, mute = (mode == 'horizontal') ? // horizontal version $('<div class="mejs-button mejs-volume-button mejs-mute">' + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ '</div>' + '<a href="javascript:void(0);" class="mejs-horizontal-volume-slider">' + // outer background '<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' + '<div class="mejs-horizontal-volume-total"></div>'+ // line background '<div class="mejs-horizontal-volume-current"></div>'+ // current volume '<div class="mejs-horizontal-volume-handle"></div>'+ // handle '</a>' ) .appendTo(controls) : // vertical version $('<div class="mejs-button mejs-volume-button mejs-mute">'+ '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ '<a href="javascript:void(0);" class="mejs-volume-slider">'+ // outer background '<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' + '<div class="mejs-volume-total"></div>'+ // line background '<div class="mejs-volume-current"></div>'+ // current volume '<div class="mejs-volume-handle"></div>'+ // handle '</a>'+ '</div>') .appendTo(controls), volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), 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') { volumeSlider.show(); positionVolumeHandle(volume, true); volumeSlider.hide(); return; } // correct to 0-1 volume = Math.max(0,volume); volume = Math.min(volume,1); // ajust mute button style if (volume === 0) { mute.removeClass('mejs-mute').addClass('mejs-unmute'); } else { mute.removeClass('mejs-unmute').addClass('mejs-mute'); } // top/left of full size volume slider background var totalPosition = volumeTotal.position(); // position slider if (mode == 'vertical') { var // height of the full size volume slider background totalHeight = volumeTotal.height(), // the new top position based on the current volume // 70% volume on 100px height == top:30px newTop = totalHeight - (totalHeight * volume); // handle 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 { var // height of the full size volume slider background totalWidth = volumeTotal.width(), // the new left position based on the current volume newLeft = totalWidth * volume; // handle volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); // rezize the current part of the volume bar volumeCurrent.width( Math.round(newLeft) ); } }, handleVolumeMove = function(e) { var volume = null, totalOffset = volumeTotal.offset(); // calculate the new volume based on the moust position if (mode === 'vertical') { var railHeight = volumeTotal.height(), totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), newY = e.pageY - totalOffset.top; volume = (railHeight - newY) / railHeight; // the controls just hide themselves (usually when mouse moves too far up) if (totalOffset.top === 0 || totalOffset.left === 0) { return; } } else { var railWidth = volumeTotal.width(), newX = e.pageX - totalOffset.left; volume = newX / railWidth; } // ensure the volume isn't outside 0-1 volume = Math.max(0,volume); volume = Math.min(volume,1); // position the slider and handle positionVolumeHandle(volume); // set the media object (this will trigger the volumechanged event) if (volume === 0) { media.setMuted(true); } else { media.setMuted(false); } media.setVolume(volume); }, mouseIsDown = false, mouseIsOver = false; // SLIDER mute .hover(function() { volumeSlider.show(); mouseIsOver = true; }, function() { mouseIsOver = false; if (!mouseIsDown && mode == 'vertical') { volumeSlider.hide(); } }); var updateVolumeSlider = function (e) { var volume = Math.floor(media.volume*100); volumeSlider.attr({ 'aria-label': mejs.i18n.t('volumeSlider'), 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': volume, 'aria-valuetext': volume+'%', 'role': 'slider', 'tabindex': 0 }); }; volumeSlider .bind('mouseover', function() { mouseIsOver = true; }) .bind('mousedown', function (e) { handleVolumeMove(e); t.globalBind('mousemove.vol', function(e) { handleVolumeMove(e); }); t.globalBind('mouseup.vol', function () { mouseIsDown = false; t.globalUnbind('.vol'); if (!mouseIsOver && mode == 'vertical') { volumeSlider.hide(); } }); mouseIsDown = true; return false; }) .bind('keydown', function (e) { var keyCode = e.keyCode; var volume = media.volume; switch (keyCode) { case 38: // Up volume += 0.1; break; case 40: // Down volume = volume - 0.1; break; default: return true; } mouseIsDown = false; positionVolumeHandle(volume); media.setVolume(volume); return false; }) .bind('blur', function () { volumeSlider.hide(); }); // MUTE button mute.find('button').click(function() { media.setMuted( !media.muted ); }); //Keyboard input mute.find('button').bind('focus', function () { volumeSlider.show(); }); // listen for volume change events from other sources media.addEventListener('volumechange', function(e) { if (!mouseIsDown) { if (media.muted) { positionVolumeHandle(0); mute.removeClass('mejs-mute').addClass('mejs-unmute'); } else { positionVolumeHandle(media.volume); mute.removeClass('mejs-unmute').addClass('mejs-mute'); } } updateVolumeSlider(e); }, false); if (t.container.is(':visible')) { // set initial volume positionVolumeHandle(player.options.startVolume); // mutes the media and sets the volume icon muted if the initial volume is set to 0 if (player.options.startVolume === 0) { media.setMuted(true); } // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements if (media.pluginType === 'native') { media.setVolume(player.options.startVolume); } } } }); })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { usePluginFullScreen: true, newWindowCallback: function() { return '';}, fullscreenText: mejs.i18n.t('Fullscreen') }); $.extend(MediaElementPlayer.prototype, { isFullScreen: false, isNativeFullScreen: false, 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 func = function(e) { if (player.isFullScreen) { 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(); } } }; player.globalBind(mejs.MediaFeatures.fullScreenEventName, func); } 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 + '" aria-label="' + 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; })(); // 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 for (var i in hoverDivs) { hoverDivs[i].hide(); } // restore the control bar fullscreenBtn.css('pointer-events', ''); t.controls.css('pointer-events', ''); // prevent clicks from pausing video t.media.removeEventListener('click', t.clickToPlayPauseCallback); // store for later fullscreenIsDisabled = false; } }, hoverDivs = {}, hoverDivNames = ['top', 'left', 'right', 'bottom'], i, len, positionHoverDivs = function() { var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left, fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top, fullScreenBtnWidth = fullscreenBtn.outerWidth(true), fullScreenBtnHeight = fullscreenBtn.outerHeight(true), containerWidth = t.container.width(), containerHeight = t.container.height(); for (i in hoverDivs) { hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'}); } // over video, but not controls hoverDivs['top'] .width( containerWidth ) .height( fullScreenBtnOffsetTop ); // over controls, but not the fullscreen button hoverDivs['left'] .width( fullScreenBtnOffsetLeft ) .height( fullScreenBtnHeight ) .css({top: fullScreenBtnOffsetTop}); // after the fullscreen button hoverDivs['right'] .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth ) .height( fullScreenBtnHeight ) .css({top: fullScreenBtnOffsetTop, left: fullScreenBtnOffsetLeft + fullScreenBtnWidth}); // under the fullscreen button hoverDivs['bottom'] .width( containerWidth ) .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop ) .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight}); }; t.globalBind('resize', function() { positionHoverDivs(); }); for (i = 0, len = hoverDivNames.length; i < len; i++) { hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide(); } // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash fullscreenBtn.on('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'); // restore click-to-play t.media.addEventListener('click', t.clickToPlayPauseCallback); // show the divs that will restore things for (i in hoverDivs) { hoverDivs[i].show(); } positionHoverDivs(); fullscreenIsDisabled = true; } }); // restore controls anytime the user enters or leaves fullscreen media.addEventListener('fullscreenchange', function(e) { t.isFullScreen = !t.isFullScreen; // don't allow plugin click to pause video - messes with // plugin's controls if (t.isFullScreen) { t.media.removeEventListener('click', t.clickToPlayPauseCallback); } else { t.media.addEventListener('click', t.clickToPlayPauseCallback); } 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 t.globalBind('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 .on('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); }) .on('mouseout', function() { if (hideTimeout !== null) { clearTimeout(hideTimeout); delete hideTimeout; } hideTimeout = setTimeout(function() { media.hideFullscreenButton(); }, 1500); }); } } player.fullscreenBtn = fullscreenBtn; t.globalBind('keydown',function (e) { if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { player.exitFullScreen(); } }); }, cleanfullscreen: function(player) { player.exitFullScreen(); }, containerSizeTimeout: null, 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; } // set it to not show scroll bars so 100% will work $(document.documentElement).addClass('mejs-fullscreen'); // 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) { var zoomMultiplier = window["devicePixelRatio"] || 1; // Use a percent error margin since devicePixelRatio is a float and not exact. var percentErrorMargin = 0.002; // 0.2% var windowWidth = zoomMultiplier * $(window).width(); var screenWidth = screen.width; var absDiff = Math.abs(screenWidth - windowWidth); var marginError = screenWidth * percentErrorMargin; // check if the video is suddenly not really fullscreen if (absDiff > marginError) { // 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) { t.containerSizeTimeout = setTimeout(function() { t.container.css({width: '100%', height: '100%'}); t.setControlsSize(); }, 500); //} if (t.media.pluginType === 'native') { t.$media .width('100%') .height('100%'); } else { t.container.find('.mejs-shim') .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; t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%'); t.container.find('.mejs-captions-position').css('bottom', '45px'); }, exitFullScreen: function() { var t = this; // Prevent container from attempting to stretch a second time clearTimeout(t.containerSizeTimeout); // 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).removeClass('mejs-fullscreen'); 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.media.pluginType === 'native') { t.$media .width(normalWidth) .height(normalHeight); } else { t.container.find('.mejs-shim') .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; t.container.find('.mejs-captions-text').css('font-size',''); t.container.find('.mejs-captions-position').css('bottom', ''); } }); })(mejs.$); (function($) { // Speed $.extend(mejs.MepDefaults, { speeds: ['2.00', '1.50', '1.25', '1.00', '0.75'], defaultSpeed: '1.00', speedChar: 'x' }); $.extend(MediaElementPlayer.prototype, { buildspeed: function(player, controls, layers, media) { var t = this; if (t.media.pluginType == 'native') { var speedButton = null, speedSelector = null, playbackSpeed = null, html = '<div class="mejs-button mejs-speed-button">' + '<button type="button">' + t.options.defaultSpeed + t.options.speedChar + '</button>' + '<div class="mejs-speed-selector">' + '<ul>'; if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) { t.options.speeds.push(t.options.defaultSpeed); } t.options.speeds.sort(function(a, b) { return parseFloat(b) - parseFloat(a); }); for (var i = 0, il = t.options.speeds.length; i<il; i++) { html += '<li>' + '<input type="radio" name="speed" ' + 'value="' + t.options.speeds[i] + '" ' + 'id="' + t.options.speeds[i] + '" ' + (t.options.speeds[i] == t.options.defaultSpeed ? ' checked' : '') + ' />' + '<label for="' + t.options.speeds[i] + '" ' + (t.options.speeds[i] == t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') + '>' + t.options.speeds[i] + t.options.speedChar + '</label>' + '</li>'; } html += '</ul></div></div>'; speedButton = $(html).appendTo(controls); speedSelector = speedButton.find('.mejs-speed-selector'); playbackspeed = t.options.defaultSpeed; speedSelector .on('click', 'input[type="radio"]', function() { var newSpeed = $(this).attr('value'); playbackspeed = newSpeed; media.playbackRate = parseFloat(newSpeed); speedButton.find('button').html('test' + newSpeed + t.options.speedChar); speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected'); }); speedSelector .height( speedButton.find('.mejs-speed-selector ul').outerHeight(true) + speedButton.find('.mejs-speed-translations').outerHeight(true)) .css('top', (-1 * speedSelector.height()) + 'px'); } } }); })(mejs.$); (function($) { // add extra default options $.extend(mejs.MepDefaults, { // this will automatically turn on a <track> startLanguage: '', tracksText: mejs.i18n.t('Captions/Subtitles'), // option to remove the [cc] button when no <track kind="subtitles"> are present hideCaptionsButtonWhenEmpty: true, // If true and we only have one track, change captions to popup toggleCaptionsButtonWhenOnlyOne: false, // #id or .class slidesSelector: '' }); $.extend(MediaElementPlayer.prototype, { hasChapters: false, buildtracks: function(player, controls, layers, media) { if (player.tracks.length === 0) return; var t = this, i, options = ''; if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide for (i = t.domNode.textTracks.length - 1; i >= 0; i--) { t.domNode.textTracks[i].mode = "hidden"; } } player.chapters = $('<div class="mejs-chapters mejs-layer"></div>') .prependTo(layers).hide(); player.captions = $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover" role="log" aria-live="assertive" aria-atomic="false"><span class="mejs-captions-text"></span></div></div>') .prependTo(layers).hide(); player.captionsText = player.captions.find('.mejs-captions-text'); player.captionsButton = $('<div class="mejs-button mejs-captions-button">'+ '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+ '<div class="mejs-captions-selector">'+ '<ul>'+ '<li>'+ '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+ '</li>' + '</ul>'+ '</div>'+ '</div>') .appendTo(controls); var subtitleCount = 0; for (i=0; i<player.tracks.length; i++) { if (player.tracks[i].kind == 'subtitles') { subtitleCount++; } } // if only one language then just make the button a toggle if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){ // click player.captionsButton.on('click',function() { if (player.selectedTrack === null) { lang = player.tracks[0].srclang; } else { lang = 'none'; } player.setTrack(lang); }); } else { // hover or keyboard focus player.captionsButton.on( 'mouseenter focusin', function() { $(this).find('.mejs-captions-selector').css('visibility','visible'); }) // handle clicks to the language radio buttons .on('click','input[type=radio]',function() { lang = this.value; player.setTrack(lang); }); player.captionsButton.on( 'mouseleave focusout', function() { $(this).find(".mejs-captions-selector").css("visibility","hidden"); }); } if (!player.options.alwaysShowControls) { // move with controls player.container .bind('controlsshown', function () { // push captions above controls player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); }) .bind('controlshidden', function () { if (!media.paused) { // move back to normal place player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); } }); } else { player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); } player.trackToLoad = -1; player.selectedTrack = null; player.isLoadingTrack = false; // add to list for (i=0; i<player.tracks.length; i++) { if (player.tracks[i].kind == 'subtitles') { player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); } } // start loading tracks player.loadNextTrack(); media.addEventListener('timeupdate',function(e) { player.displayCaptions(); }, false); if (player.options.slidesSelector !== '') { player.slidesContainer = $(player.options.slidesSelector); media.addEventListener('timeupdate',function(e) { player.displaySlides(); }, false); } media.addEventListener('loadedmetadata', function(e) { player.displayChapters(); }, false); player.container.hover( function () { // chapters if (player.hasChapters) { player.chapters.css('visibility','visible'); player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); } }, function () { if (player.hasChapters && !media.paused) { player.chapters.fadeOut(200, function() { $(this).css('visibility','hidden'); $(this).css('display','block'); }); } }); // check for autoplay if (player.node.getAttribute('autoplay') !== null) { player.chapters.css('visibility','hidden'); } }, setTrack: function(lang){ var t = this, i; if (lang == 'none') { t.selectedTrack = null; t.captionsButton.removeClass('mejs-captions-enabled'); } else { for (i=0; i<t.tracks.length; i++) { if (t.tracks[i].srclang == lang) { if (t.selectedTrack === null) t.captionsButton.addClass('mejs-captions-enabled'); t.selectedTrack = t.tracks[i]; t.captions.attr('lang', t.selectedTrack.srclang); t.displayCaptions(); break; } } } }, loadNextTrack: function() { var t = this; t.trackToLoad++; if (t.trackToLoad < t.tracks.length) { t.isLoadingTrack = true; t.loadTrack(t.trackToLoad); } else { // add done? t.isLoadingTrack = false; t.checkForTracks(); } }, loadTrack: function(index){ var t = this, track = t.tracks[index], after = function() { track.isLoaded = true; // create button //t.addTrackButton(track.srclang); t.enableTrackButton(track.srclang, track.label); t.loadNextTrack(); }; $.ajax({ url: track.src, dataType: "text", success: function(d) { // 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.webvtt.parse(d); } after(); if (track.kind == 'chapters') { t.media.addEventListener('play', function(e) { if (t.media.duration > 0) { t.displayChapters(track); } }, false); } if (track.kind == 'slides') { t.setupSlides(track); } }, error: function() { t.loadNextTrack(); } }); }, enableTrackButton: function(lang, label) { var t = this; if (label === '') { label = mejs.language.codes[lang] || lang; } t.captionsButton .find('input[value=' + lang + ']') .prop('disabled',false) .siblings('label') .html( label ); // auto select if (t.options.startLanguage == lang) { $('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click'); } t.adjustLanguageBox(); }, addTrackButton: function(lang, label) { var t = this; if (label === '') { label = mejs.language.codes[lang] || lang; } t.captionsButton.find('ul').append( $('<li>'+ '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ '</li>') ); t.adjustLanguageBox(); // remove this from the dropdownlist (if it exists) t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); }, adjustLanguageBox:function() { var t = this; // adjust the size of the outer box t.captionsButton.find('.mejs-captions-selector').height( t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + t.captionsButton.find('.mejs-captions-translations').outerHeight(true) ); }, checkForTracks: function() { var t = this, hasSubtitles = false; // check if any subtitles if (t.options.hideCaptionsButtonWhenEmpty) { for (i=0; i<t.tracks.length; i++) { if (t.tracks[i].kind == 'subtitles') { hasSubtitles = true; break; } } if (!hasSubtitles) { t.captionsButton.hide(); t.setControlsSize(); } } }, displayCaptions: function() { if (typeof this.tracks == 'undefined') return; var t = this, i, track = t.selectedTrack; if (track !== null && track.isLoaded) { for (i=0; i<track.entries.times.length; i++) { if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) { // Set the line before the timecode as a class so the cue can be targeted if needed t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || '')); t.captions.show().height(0); return; // exit out if one is visible; } } t.captions.hide(); } else { t.captions.hide(); } }, setupSlides: function(track) { var t = this; t.slides = track; t.slides.entries.imgs = [t.slides.entries.text.length]; t.showSlide(0); }, showSlide: function(index) { if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') { return; } var t = this, url = t.slides.entries.text[index], img = t.slides.entries.imgs[index]; if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') { t.slides.entries.imgs[index] = img = $('<img src="' + url + '">') .on('load', function() { img.appendTo(t.slidesContainer) .hide() .fadeIn() .siblings(':visible') .fadeOut(); }); } else { if (!img.is(':visible') && !img.is(':animated')) { // img.fadeIn() .siblings(':visible') .fadeOut(); } } }, displaySlides: function() { if (typeof this.slides == 'undefined') return; var t = this, slides = t.slides, i; for (i=0; i<slides.entries.times.length; i++) { if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){ t.showSlide(i); return; // exit out if one is visible; } } }, displayChapters: function() { var t = this, i; for (i=0; i<t.tracks.length; i++) { if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { t.drawChapters(t.tracks[i]); t.hasChapters = true; break; } } }, drawChapters: function(chapters) { var t = this, i, dur, //width, //left, percent = 0, usedPercent = 0; t.chapters.empty(); for (i=0; i<chapters.entries.times.length; i++) { dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; percent = Math.floor(dur / t.media.duration * 100); if (percent + usedPercent > 100 || // too large i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in { percent = 100 - usedPercent; } //width = Math.floor(t.width * dur / t.media.duration); //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); //if (left + width > t.width) { // width = t.width - left; //} t.chapters.append( $( '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + '</div>' + '</div>')); usedPercent += percent; } t.chapters.find('div.mejs-chapter').click(function() { t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); if (t.media.paused) { t.media.play(); } }); t.chapters.show(); } }); mejs.language = { codes: { af:'Afrikaans', sq:'Albanian', ar:'Arabic', be:'Belarusian', bg:'Bulgarian', ca:'Catalan', zh:'Chinese', 'zh-cn':'Chinese Simplified', 'zh-tw':'Chinese Traditional', hr:'Croatian', cs:'Czech', da:'Danish', nl:'Dutch', en:'English', et:'Estonian', fl:'Filipino', fi:'Finnish', fr:'French', gl:'Galician', de:'German', el:'Greek', ht:'Haitian Creole', iw:'Hebrew', hi:'Hindi', hu:'Hungarian', is:'Icelandic', id:'Indonesian', ga:'Irish', it:'Italian', ja:'Japanese', ko:'Korean', lv:'Latvian', lt:'Lithuanian', mk:'Macedonian', ms:'Malay', mt:'Maltese', no:'Norwegian', fa:'Persian', pl:'Polish', pt:'Portuguese', // 'pt-pt':'Portuguese (Portugal)', ro:'Romanian', ru:'Russian', sr:'Serbian', sk:'Slovak', sl:'Slovenian', es:'Spanish', sw:'Swahili', sv:'Swedish', tl:'Tagalog', th:'Thai', tr:'Turkish', uk:'Ukrainian', vi:'Vietnamese', cy:'Welsh', yi:'Yiddish' } }; /* Parses WebVTT format which should be formatted as ================================ WEBVTT 1 00:00:01,1 --> 00:00:05,000 A line of text 2 00:01:15,1 --> 00:02:05,000 A second line of text =============================== Adapted from: http://www.delphiki.com/html5/playr */ mejs.TrackFormatParser = { webvtt: { pattern_timecode: /^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, parse: function(trackText) { var i = 0, lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), entries = {text:[], times:[]}, timecode, text, identifier; for(; i<lines.length; i++) { timecode = this.pattern_timecode.exec(lines[i]); if (timecode && i<lines.length) { if ((i - 1) >= 0 && lines[i - 1] !== '') { identifier = lines[i - 1]; } 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( { identifier: identifier, start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), settings: timecode[5] }); } identifier = ''; } 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:[]}; 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) { // add super slow IE8 and below version mejs.TrackFormatParser.split2 = function(text, regex) { var parts = [], chunk = '', i; for (i=0; i<text.length; i++) { chunk += text.substring(i,i+1); if (regex.test(chunk)) { parts.push(chunk.replace(regex, '')); chunk = ''; } } parts.push(chunk); return parts; }; } })(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 mejs.i18n.t('Turn off Fullscreen'); } else { return mejs.i18n.t('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 mejs.i18n.t('Unmute'); } else { return mejs.i18n.t('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 mejs.i18n.t('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() { // player.startContextMenuTimer(); }); }, cleancontextmenu: function(player) { player.contextMenu.remove(); }, isContextMenuEnabled: true, enableContextMenu: function() { this.isContextMenuEnabled = true; }, disableContextMenu: function() { this.isContextMenuEnabled = false; }, contextMenuTimeout: null, startContextMenuTimer: function() { // var t = this; t.killContextMenuTimer(); t.contextMenuTimer = setTimeout(function() { t.hideContextMenu(); t.killContextMenuTimer(); }, 750); }, killContextMenuTimer: function() { var timer = this.contextMenuTimer; // 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.$);