?
Path : /home/admin/public_html/old/media/media/js/ |
Current File : /home/admin/public_html/old/media/media/js/mediaelement-and-player.js |
/*! * * MediaElement.js * HTML5 <video> and <audio> shim and player * http://mediaelementjs.com/ * * Creates a JavaScript object that mimics HTML5 MediaElement API * for browsers that don't understand HTML5 or can't play the provided codec * Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 * * Copyright 2010-2014, John Dyer (http://j.hn) * License: MIT * */ // Namespace var mejs = mejs || {}; // version number mejs.version = '2.20.1'; // player number (for missing, same id attr) mejs.meIndex = 0; // media types accepted by plugins mejs.plugins = { silverlight: [ {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} ], flash: [ {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/rtmp','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg', 'video/dailymotion', 'video/x-dailymotion', 'application/x-mpegURL']} // 'video/youtube', 'video/x-youtube', // ,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) ], youtube: [ {version: null, types: ['video/youtube', 'video/x-youtube', 'audio/youtube', 'audio/x-youtube']} ], vimeo: [ {version: null, types: ['video/vimeo', 'video/x-vimeo']} ] }; /* Utility methods */ mejs.Utility = { encodeUrl: function(url) { return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); }, escapeHTML: function(s) { return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); }, absolutizeUrl: function(url) { var el = document.createElement('div'); el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; return el.firstChild.href; }, getScriptPath: function(scriptNames) { var i = 0, j, codePath = '', testname = '', slashPos, filenamePos, scriptUrl, scriptPath, scriptFilename, scripts = document.getElementsByTagName('script'), il = scripts.length, jl = scriptNames.length; // go through all <script> tags for (; i < il; i++) { scriptUrl = scripts[i].src; slashPos = scriptUrl.lastIndexOf('/'); if (slashPos > -1) { scriptFilename = scriptUrl.substring(slashPos + 1); scriptPath = scriptUrl.substring(0, slashPos + 1); } else { scriptFilename = scriptUrl; scriptPath = ''; } // see if any <script> tags have a file name that matches the for (j = 0; j < jl; j++) { testname = scriptNames[j]; filenamePos = scriptFilename.indexOf(testname); if (filenamePos > -1) { codePath = scriptPath; break; } } // if we found a path, then break and return it if (codePath !== '') { break; } } // send the best path back return codePath; }, /* * Calculate the time format to use. We have a default format set in the * options but it can be imcomplete. We ajust it according to the media * duration. * * We support format like 'hh:mm:ss:ff'. */ calculateTimeFormat: function(time, options, fps) { if (time < 0) { time = 0; } if(typeof fps == 'undefined') { fps = 25; } var format = options.timeFormat, firstChar = format[0], firstTwoPlaces = (format[1] == format[0]), separatorIndex = firstTwoPlaces? 2: 1, separator = ':', hours = Math.floor(time / 3600) % 24, minutes = Math.floor(time / 60) % 60, seconds = Math.floor(time % 60), frames = Math.floor(((time % 1)*fps).toFixed(3)), lis = [ [frames, 'f'], [seconds, 's'], [minutes, 'm'], [hours, 'h'] ]; // Try to get the separator from the format if (format.length < separatorIndex) { separator = format[separatorIndex]; } var required = false; for (var i=0, len=lis.length; i < len; i++) { if (format.indexOf(lis[i][1]) !== -1) { required=true; } else if (required) { var hasNextValue = false; for (var j=i; j < len; j++) { if (lis[j][0] > 0) { hasNextValue = true; break; } } if (! hasNextValue) { break; } if (!firstTwoPlaces) { format = firstChar + format; } format = lis[i][1] + separator + format; if (firstTwoPlaces) { format = lis[i][1] + format; } firstChar = lis[i][1]; } } options.currentTimeFormat = format; }, /* * Prefix the given number by zero if it is lower than 10. */ twoDigitsString: function(n) { if (n < 10) { return '0' + n; } return String(n); }, secondsToTimeCode: function(time, options) { if (time < 0) { time = 0; } // Maintain backward compatibility with method signature before v2.18. if (typeof options !== 'object') { var format = 'm:ss'; format = arguments[1] ? 'hh:mm:ss' : format; // forceHours format = arguments[2] ? format + ':ff' : format; // showFrameCount options = { currentTimeFormat: format, framesPerSecond: arguments[3] || 25 }; } var fps = options.framesPerSecond; if(typeof fps === 'undefined') { fps = 25; } var format = options.currentTimeFormat, hours = Math.floor(time / 3600) % 24, minutes = Math.floor(time / 60) % 60, seconds = Math.floor(time % 60), frames = Math.floor(((time % 1)*fps).toFixed(3)); lis = [ [frames, 'f'], [seconds, 's'], [minutes, 'm'], [hours, 'h'] ]; var res = format; for (i=0,len=lis.length; i < len; i++) { res = res.replace(lis[i][1]+lis[i][1], this.twoDigitsString(lis[i][0])); res = res.replace(lis[i][1], lis[i][0]); } return res; }, timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ if (typeof showFrameCount == 'undefined') { showFrameCount=false; } else if(typeof fps == 'undefined') { fps = 25; } var tc_array = hh_mm_ss_ff.split(":"), tc_hh = parseInt(tc_array[0], 10), tc_mm = parseInt(tc_array[1], 10), tc_ss = parseInt(tc_array[2], 10), tc_ff = 0, tc_in_seconds = 0; if (showFrameCount) { tc_ff = parseInt(tc_array[3])/fps; } tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; return tc_in_seconds; }, convertSMPTEtoSeconds: function (SMPTE) { if (typeof SMPTE != 'string') return false; SMPTE = SMPTE.replace(',', '.'); var secs = 0, decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0, multiplier = 1; SMPTE = SMPTE.split(':').reverse(); for (var i = 0; i < SMPTE.length; i++) { multiplier = 1; if (i > 0) { multiplier = Math.pow(60, i); } secs += Number(SMPTE[i]) * multiplier; } return Number(secs.toFixed(decimalLen)); }, /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ removeSwf: function(id) { var obj = document.getElementById(id); if (obj && /object|embed/i.test(obj.nodeName)) { if (mejs.MediaFeatures.isIE) { obj.style.display = "none"; (function(){ if (obj.readyState == 4) { mejs.Utility.removeObjectInIE(id); } else { setTimeout(arguments.callee, 10); } })(); } else { obj.parentNode.removeChild(obj); } } }, removeObjectInIE: function(id) { var obj = document.getElementById(id); if (obj) { for (var i in obj) { if (typeof obj[i] == "function") { obj[i] = null; } } obj.parentNode.removeChild(obj); } } }; // Core detector, plugins are added below mejs.PluginDetector = { // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); hasPluginVersion: function(plugin, v) { var pv = this.plugins[plugin]; v[1] = v[1] || 0; v[2] = v[2] || 0; return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; }, // cached values nav: window.navigator, ua: window.navigator.userAgent.toLowerCase(), // stored version numbers plugins: [], // runs detectPlugin() and stores the version number addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); }, // get the version number from the mimetype (all but IE) or ActiveX (IE) detectPlugin: function(pluginName, mimeType, activeX, axDetect) { var version = [0,0,0], description, i, ax; // Firefox, Webkit, Opera if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { description = this.nav.plugins[pluginName].description; if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); for (i=0; i<version.length; i++) { version[i] = parseInt(version[i].match(/\d+/), 10); } } // Internet Explorer / ActiveX } else if (typeof(window.ActiveXObject) != 'undefined') { try { ax = new ActiveXObject(activeX); if (ax) { version = axDetect(ax); } } catch (e) { } } return version; } }; // Add Flash detection mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { // adapted from SWFObject var version = [], d = ax.GetVariable("$version"); if (d) { d = d.split(" ")[1].split(","); version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; } return version; }); // Add Silverlight detection mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { // Silverlight cannot report its version number to IE // but it does have a isVersionSupported function, so we have to loop through it to get a version number. // adapted from http://www.silverlightversion.com/ var v = [0,0,0,0], loopMatch = function(ax, v, i, n) { while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ v[i]+=n; } v[i] -= n; }; loopMatch(ax, v, 0, 1); loopMatch(ax, v, 1, 1); loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) loopMatch(ax, v, 2, 1000); loopMatch(ax, v, 2, 100); loopMatch(ax, v, 2, 10); loopMatch(ax, v, 2, 1); loopMatch(ax, v, 3, 1); return v; }); // add adobe acrobat /* PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { var version = [], d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); if (d) { version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; } return version; }); */ // necessary detection (fixes for <IE9) mejs.MediaFeatures = { init: function() { var t = this, d = document, nav = mejs.PluginDetector.nav, ua = mejs.PluginDetector.ua.toLowerCase(), i, v, html5Elements = ['source','track','audio','video']; // detect browsers (only the ones that have some kind of quirk we need to work around) t.isiPad = (ua.match(/ipad/i) !== null); t.isiPhone = (ua.match(/iphone/i) !== null); t.isiOS = t.isiPhone || t.isiPad; t.isAndroid = (ua.match(/android/i) !== null); t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); t.isBustedNativeHTTPS = (location.protocol === 'https:' && (ua.match(/android [12]\./) !== null || ua.match(/macintosh.* version.* safari/) !== null)); t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1 || nav.appName.toLowerCase().match(/trident/gi) !== null); t.isChrome = (ua.match(/chrome/gi) !== null); t.isChromium = (ua.match(/chromium/gi) !== null); t.isFirefox = (ua.match(/firefox/gi) !== null); t.isWebkit = (ua.match(/webkit/gi) !== null); t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit && !t.isIE; t.isOpera = (ua.match(/opera/gi) !== null); t.hasTouch = ('ontouchstart' in window); // && window.ontouchstart != null); // this breaks iOS 7 // Borrowed from `Modernizr.svgasimg`, sources: // - https://github.com/Modernizr/Modernizr/issues/687 // - https://github.com/Modernizr/Modernizr/pull/1209/files t.svgAsImg = !!document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1'); // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection for (i=0; i<html5Elements.length; i++) { v = document.createElement(html5Elements[i]); } t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); // Fix for IE9 on Windows 7N / Windows 7KN (Media Player not installer) try{ v.canPlayType("video/mp4"); }catch(e){ t.supportsMediaTag = false; } t.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; })(); // Older versions of Firefox can't move plugins around without it resetting, t.hasFirefoxPluginMovingProblem = false; // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) // iOS t.hasiOSFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); // W3C t.hasNativeFullscreen = (typeof v.requestFullscreen !== 'undefined'); // webkit/firefox/IE11+ t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); t.hasMsNativeFullScreen = (typeof v.msRequestFullscreen !== 'undefined'); t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen || t.hasMsNativeFullScreen); t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen; // Enabled? if (t.hasMozNativeFullScreen) { t.nativeFullScreenEnabled = document.mozFullScreenEnabled; } else if (t.hasMsNativeFullScreen) { t.nativeFullScreenEnabled = document.msFullscreenEnabled; } if (t.isChrome) { t.hasiOSFullScreen = false; } if (t.hasTrueNativeFullScreen) { t.fullScreenEventName = ''; if (t.hasWebkitNativeFullScreen) { t.fullScreenEventName = 'webkitfullscreenchange'; } else if (t.hasMozNativeFullScreen) { t.fullScreenEventName = 'mozfullscreenchange'; } else if (t.hasMsNativeFullScreen) { t.fullScreenEventName = 'MSFullscreenChange'; } t.isFullScreen = function() { if (t.hasMozNativeFullScreen) { return d.mozFullScreen; } else if (t.hasWebkitNativeFullScreen) { return d.webkitIsFullScreen; } else if (t.hasMsNativeFullScreen) { return d.msFullscreenElement !== null; } } t.requestFullScreen = function(el) { if (t.hasWebkitNativeFullScreen) { el.webkitRequestFullScreen(); } else if (t.hasMozNativeFullScreen) { el.mozRequestFullScreen(); } else if (t.hasMsNativeFullScreen) { el.msRequestFullscreen(); } } t.cancelFullScreen = function() { if (t.hasWebkitNativeFullScreen) { document.webkitCancelFullScreen(); } else if (t.hasMozNativeFullScreen) { document.mozCancelFullScreen(); } else if (t.hasMsNativeFullScreen) { document.msExitFullscreen(); } } } // OS X 10.5 can't do this even if it says it can :( if (t.hasiOSFullScreen && ua.match(/mac os x 10_5/i)) { t.hasNativeFullScreen = false; t.hasiOSFullScreen = false; } } }; mejs.MediaFeatures.init(); /* extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) */ mejs.HtmlMediaElement = { pluginType: 'native', isFullScreen: false, setCurrentTime: function (time) { this.currentTime = time; }, setMuted: function (muted) { this.muted = muted; }, setVolume: function (volume) { this.volume = volume; }, // for parity with the plugin versions stop: function () { this.pause(); }, // This can be a url string // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] setSrc: function (url) { // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? var existingSources = this.getElementsByTagName('source'); while (existingSources.length > 0){ this.removeChild(existingSources[0]); } if (typeof url == 'string') { this.src = url; } else { var i, media; for (i=0; i<url.length; i++) { media = url[i]; if (this.canPlayType(media.type)) { this.src = media.src; break; } } } }, setVideoSize: function (width, height) { this.width = width; this.height = height; } }; /* Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] */ mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { this.id = pluginid; this.pluginType = pluginType; this.src = mediaUrl; this.events = {}; this.attributes = {}; }; // JavaScript values and ExternalInterface methods that match HTML5 video properties methods // http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html mejs.PluginMediaElement.prototype = { // special pluginElement: null, pluginType: '', isFullScreen: false, // not implemented :( playbackRate: -1, defaultPlaybackRate: -1, seekable: [], played: [], // HTML5 read-only properties paused: true, ended: false, seeking: false, duration: 0, error: null, tagName: '', // HTML5 get/set properties, but only set (updated by event handlers) muted: false, volume: 1, currentTime: 0, // HTML5 methods play: function () { if (this.pluginApi != null) { if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { this.pluginApi.playVideo(); } else { this.pluginApi.playMedia(); } this.paused = false; } }, load: function () { if (this.pluginApi != null) { if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { } else { this.pluginApi.loadMedia(); } this.paused = false; } }, pause: function () { if (this.pluginApi != null) { if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { this.pluginApi.pauseVideo(); } else { this.pluginApi.pauseMedia(); } this.paused = true; } }, stop: function () { if (this.pluginApi != null) { if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { this.pluginApi.stopVideo(); } else { this.pluginApi.stopMedia(); } this.paused = true; } }, canPlayType: function(type) { var i, j, pluginInfo, pluginVersions = mejs.plugins[this.pluginType]; for (i=0; i<pluginVersions.length; i++) { pluginInfo = pluginVersions[i]; // test if user has the correct plugin version if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { // test for plugin playback types for (j=0; j<pluginInfo.types.length; j++) { // find plugin that can play the type if (type == pluginInfo.types[j]) { return 'probably'; } } } } return ''; }, positionFullscreenButton: function(x,y,visibleAndAbove) { if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { this.pluginApi.positionFullscreenButton(Math.floor(x),Math.floor(y),visibleAndAbove); } }, hideFullscreenButton: function() { if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { this.pluginApi.hideFullscreenButton(); } }, // custom methods since not all JavaScript implementations support get/set // This can be a url string // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] setSrc: function (url) { if (typeof url == 'string') { this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); this.src = mejs.Utility.absolutizeUrl(url); } else { var i, media; for (i=0; i<url.length; i++) { media = url[i]; if (this.canPlayType(media.type)) { this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); this.src = mejs.Utility.absolutizeUrl(media.src); break; } } } }, setCurrentTime: function (time) { if (this.pluginApi != null) { if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { this.pluginApi.seekTo(time); } else { this.pluginApi.setCurrentTime(time); } this.currentTime = time; } }, setVolume: function (volume) { if (this.pluginApi != null) { // same on YouTube and MEjs if (this.pluginType == 'youtube') { this.pluginApi.setVolume(volume * 100); } else { this.pluginApi.setVolume(volume); } this.volume = volume; } }, setMuted: function (muted) { if (this.pluginApi != null) { if (this.pluginType == 'youtube') { if (muted) { this.pluginApi.mute(); } else { this.pluginApi.unMute(); } this.muted = muted; this.dispatchEvent({type:'volumechange'}); } else { this.pluginApi.setMuted(muted); } this.muted = muted; } }, // additional non-HTML5 methods setVideoSize: function (width, height) { //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { if (this.pluginElement && this.pluginElement.style) { this.pluginElement.style.width = width + 'px'; this.pluginElement.style.height = height + 'px'; } if (this.pluginApi != null && this.pluginApi.setVideoSize) { this.pluginApi.setVideoSize(width, height); } //} }, setFullscreen: function (fullscreen) { if (this.pluginApi != null && this.pluginApi.setFullscreen) { this.pluginApi.setFullscreen(fullscreen); } }, enterFullScreen: function() { if (this.pluginApi != null && this.pluginApi.setFullscreen) { this.setFullscreen(true); } }, exitFullScreen: function() { if (this.pluginApi != null && this.pluginApi.setFullscreen) { this.setFullscreen(false); } }, // start: fake events addEventListener: function (eventName, callback, bubble) { this.events[eventName] = this.events[eventName] || []; this.events[eventName].push(callback); }, removeEventListener: function (eventName, callback) { if (!eventName) { this.events = {}; return true; } var callbacks = this.events[eventName]; if (!callbacks) return true; if (!callback) { this.events[eventName] = []; return true; } for (var i = 0; i < callbacks.length; i++) { if (callbacks[i] === callback) { this.events[eventName].splice(i, 1); return true; } } return false; }, dispatchEvent: function (event) { var i, args, callbacks = this.events[event.type]; if (callbacks) { for (i = 0; i < callbacks.length; i++) { callbacks[i].apply(this, [event]); } } }, // end: fake events // fake DOM attribute methods hasAttribute: function(name){ return (name in this.attributes); }, removeAttribute: function(name){ delete this.attributes[name]; }, getAttribute: function(name){ if (this.hasAttribute(name)) { return this.attributes[name]; } return ''; }, setAttribute: function(name, value){ this.attributes[name] = value; }, remove: function() { mejs.Utility.removeSwf(this.pluginElement.id); mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id); } }; // Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties mejs.MediaPluginBridge = { pluginMediaElements:{}, htmlMediaElements:{}, registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { this.pluginMediaElements[id] = pluginMediaElement; this.htmlMediaElements[id] = htmlMediaElement; }, unregisterPluginElement: function (id) { delete this.pluginMediaElements[id]; delete this.htmlMediaElements[id]; }, // when Flash/Silverlight is ready, it calls out to this method initPlugin: function (id) { var pluginMediaElement = this.pluginMediaElements[id], htmlMediaElement = this.htmlMediaElements[id]; if (pluginMediaElement) { // find the javascript bridge switch (pluginMediaElement.pluginType) { case "flash": pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); break; case "silverlight": pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; break; } if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { pluginMediaElement.success(pluginMediaElement, htmlMediaElement); } } }, // receives events from Flash/Silverlight and sends them out as HTML5 media events // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html fireEvent: function (id, eventName, values) { var e, i, bufferedTime, pluginMediaElement = this.pluginMediaElements[id]; if(!pluginMediaElement){ return; } // fake event object to mimic real HTML media event. e = { type: eventName, target: pluginMediaElement }; // attach all values to element and event object for (i in values) { pluginMediaElement[i] = values[i]; e[i] = values[i]; } // fake the newer W3C buffered TimeRange (loaded and total have been removed) bufferedTime = values.bufferedTime || 0; e.target.buffered = e.buffered = { start: function(index) { return 0; }, end: function (index) { return bufferedTime; }, length: 1 }; pluginMediaElement.dispatchEvent(e); } }; /* Default options */ mejs.MediaElementDefaults = { // allows testing on HTML5, flash, silverlight // auto: attempts to detect what the browser can do // auto_plugin: prefer plugins and then attempt native HTML5 // native: forces HTML5 playback // shim: disallows HTML5, will attempt either Flash or Silverlight // none: forces fallback view mode: 'auto', // remove or reorder to change plugin priority and availability plugins: ['flash','silverlight','youtube','vimeo'], // shows debug errors on screen enablePluginDebug: false, // use plugin for browsers that have trouble with Basic Authentication on HTTPS sites httpsBasicAuthSite: false, // overrides the type specified, useful for dynamic instantiation type: '', // path to Flash and Silverlight plugins pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), // name of flash file flashName: 'flashmediaelement.swf', // streamer for RTMP streaming flashStreamer: '', // set to 'always' for CDN version flashScriptAccess: 'sameDomain', // turns on the smoothing filter in Flash enablePluginSmoothing: false, // enabled pseudo-streaming (seek) on .mp4 files enablePseudoStreaming: false, // start query parameter sent to server for pseudo-streaming pseudoStreamingStartQueryParam: 'start', // name of silverlight file silverlightName: 'silverlightmediaelement.xap', // default if the <video width> is not specified defaultVideoWidth: 480, // default if the <video height> is not specified defaultVideoHeight: 270, // overrides <video width> pluginWidth: -1, // overrides <video height> pluginHeight: -1, // additional plugin variables in 'key=value' form pluginVars: [], // rate in milliseconds for Flash and Silverlight to fire the timeupdate event // larger number is less accurate, but less strain on plugin->JavaScript bridge timerRate: 250, // initial volume for player startVolume: 0.8, success: function () { }, error: function () { } }; /* Determines if a browser supports the <video> or <audio> element and returns either the native element or a Flash/Silverlight version that mimics HTML5 MediaElement */ mejs.MediaElement = function (el, o) { return mejs.HtmlMediaElementShim.create(el,o); }; mejs.HtmlMediaElementShim = { create: function(el, o) { var options = {}, htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, tagName = htmlMediaElement.tagName.toLowerCase(), isMediaTag = (tagName === 'audio' || tagName === 'video'), src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), poster = htmlMediaElement.getAttribute('poster'), autoplay = htmlMediaElement.getAttribute('autoplay'), preload = htmlMediaElement.getAttribute('preload'), controls = htmlMediaElement.getAttribute('controls'), playback, prop; // extend options for (prop in mejs.MediaElementDefaults) { options[prop] = mejs.MediaElementDefaults[prop]; } for (prop in o) { options[prop] = o[prop]; } // clean up attributes src = (typeof src == 'undefined' || src === null || src == '') ? null : src; poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); // test for HTML5 and plugin capabilities playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; if (playback.method == 'native') { // second fix for android if (mejs.MediaFeatures.isBustedAndroid) { htmlMediaElement.src = playback.url; htmlMediaElement.addEventListener('click', function() { htmlMediaElement.play(); }, false); } // add methods to native HTMLMediaElement return this.updateNative(playback, options, autoplay, preload); } else if (playback.method !== '') { // create plugin to mimic HTMLMediaElement return this.createPlugin( playback, options, poster, autoplay, preload, controls); } else { // boo, no HTML5, no Flash, no Silverlight. this.createErrorMessage( playback, options, poster ); return this; } }, determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { var mediaFiles = [], i, j, k, l, n, type, result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, pluginName, pluginVersions, pluginInfo, dummy, media; // STEP 1: Get URL and type from <video src> or <source src> // supplied type overrides <video type> and <source type> if (typeof options.type != 'undefined' && options.type !== '') { // accept either string or array of types if (typeof options.type == 'string') { mediaFiles.push({type:options.type, url:src}); } else { for (i=0; i<options.type.length; i++) { mediaFiles.push({type:options.type[i], url:src}); } } // test for src attribute first } else if (src !== null) { type = this.formatType(src, htmlMediaElement.getAttribute('type')); mediaFiles.push({type:type, url:src}); // then test for <source> elements } else { // test <source> types to see if they are usable for (i = 0; i < htmlMediaElement.childNodes.length; i++) { n = htmlMediaElement.childNodes[i]; if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { src = n.getAttribute('src'); type = this.formatType(src, n.getAttribute('type')); media = n.getAttribute('media'); if (!media || !window.matchMedia || (window.matchMedia && window.matchMedia(media).matches)) { mediaFiles.push({type:type, url:src}); } } } } // in the case of dynamicly created players // check for audio types if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { result.isVideo = false; } // STEP 2: Test for playback method // special case for Android which sadly doesn't implement the canPlayType function (always returns '') if (mejs.MediaFeatures.isBustedAndroid) { htmlMediaElement.canPlayType = function(type) { return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; }; } // special case for Chromium to specify natively supported video codecs (i.e. WebM and Theora) if (mejs.MediaFeatures.isChromium) { htmlMediaElement.canPlayType = function(type) { return (type.match(/video\/(webm|ogv|ogg)/gi) !== null) ? 'maybe' : ''; }; } // test for native playback first if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native') && !(mejs.MediaFeatures.isBustedNativeHTTPS && options.httpsBasicAuthSite === true)) { if (!isMediaTag) { // create a real HTML5 Media Element dummy = document.createElement( result.isVideo ? 'video' : 'audio'); htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); htmlMediaElement.style.display = 'none'; // use this one from now on result.htmlMediaElement = htmlMediaElement = dummy; } for (i=0; i<mediaFiles.length; i++) { // normal check if (mediaFiles[i].type == "video/m3u8" || htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '' // special case for m4a supported by detecting mp4 support || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/m4a/,'mp4')).replace(/no/, '') !== '') { result.method = 'native'; result.url = mediaFiles[i].url; break; } } if (result.method === 'native') { if (result.url !== null) { htmlMediaElement.src = result.url; } // if `auto_plugin` mode, then cache the native result but try plugins. if (options.mode !== 'auto_plugin') { return result; } } } // if native playback didn't work, then test plugins if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') { for (i=0; i<mediaFiles.length; i++) { type = mediaFiles[i].type; // test all plugins in order of preference [silverlight, flash] for (j=0; j<options.plugins.length; j++) { pluginName = options.plugins[j]; // test version of plugin (for future features) pluginVersions = mejs.plugins[pluginName]; for (k=0; k<pluginVersions.length; k++) { pluginInfo = pluginVersions[k]; // test if user has the correct plugin version // for youtube/vimeo if (pluginInfo.version == null || mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { // test for plugin playback types for (l=0; l<pluginInfo.types.length; l++) { // find plugin that can play the type if (type.toLowerCase() == pluginInfo.types[l].toLowerCase()) { result.method = pluginName; result.url = mediaFiles[i].url; return result; } } } } } } } // at this point, being in 'auto_plugin' mode implies that we tried plugins but failed. // if we have native support then return that. if (options.mode === 'auto_plugin' && result.method === 'native') { return result; } // what if there's nothing to play? just grab the first available if (result.method === '' && mediaFiles.length > 0) { result.url = mediaFiles[0].url; } return result; }, formatType: function(url, type) { // if no type is supplied, fake it with the extension if (url && !type) { return this.getTypeFromFile(url); } else { // only return the mime part of the type in case the attribute contains the codec // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` if (type && ~type.indexOf(';')) { return type.substr(0, type.indexOf(';')); } else { return type; } } }, getTypeFromFile: function(url) { url = url.split('?')[0]; var ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(), av = /(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video/' : 'audio/'; return this.getTypeFromExtension(ext, av); }, getTypeFromExtension: function(ext, av) { av = av || ''; switch (ext) { case 'mp4': case 'm4v': case 'm4a': case 'f4v': case 'f4a': return av + 'mp4'; case 'flv': return av + 'x-flv'; case 'webm': case 'webma': case 'webmv': return av + 'webm'; case 'ogg': case 'oga': case 'ogv': return av + 'ogg'; case 'm3u8': return 'application/x-mpegurl'; case 'ts': return av + 'mp2t'; default: return av + ext; } }, createErrorMessage: function(playback, options, poster) { var htmlMediaElement = playback.htmlMediaElement, errorContainer = document.createElement('div'), errorContent = options.customError; errorContainer.className = 'me-cannotplay'; try { errorContainer.style.width = htmlMediaElement.width + 'px'; errorContainer.style.height = htmlMediaElement.height + 'px'; } catch (e) {} if (!errorContent) { errorContent = '<a href="' + playback.url + '">'; if (poster !== '') { errorContent += '<img src="' + poster + '" width="100%" height="100%" alt="" />'; } errorContent += '<span>' + mejs.i18n.t('Download File') + '</span></a>'; } errorContainer.innerHTML = errorContent; htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); htmlMediaElement.style.display = 'none'; options.error(htmlMediaElement); }, createPlugin:function(playback, options, poster, autoplay, preload, controls) { var htmlMediaElement = playback.htmlMediaElement, width = 1, height = 1, pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), container = document.createElement('div'), specialIEContainer, node, initVars; // copy tagName from html media element pluginMediaElement.tagName = htmlMediaElement.tagName // copy attributes from html media element to plugin media element for (var i = 0; i < htmlMediaElement.attributes.length; i++) { var attribute = htmlMediaElement.attributes[i]; if (attribute.specified) { pluginMediaElement.setAttribute(attribute.name, attribute.value); } } // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) node = htmlMediaElement.parentNode; while (node !== null && node.tagName != null && node.tagName.toLowerCase() !== 'body' && node.parentNode != null && node.parentNode.tagName != null && node.parentNode.constructor != null && node.parentNode.constructor.name === "ShadowRoot") { if (node.parentNode.tagName.toLowerCase() === 'p') { node.parentNode.parentNode.insertBefore(node, node.parentNode); break; } node = node.parentNode; } if (playback.isVideo) { width = (options.pluginWidth > 0) ? options.pluginWidth : (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; height = (options.pluginHeight > 0) ? options.pluginHeight : (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; // in case of '%' make sure it's encoded width = mejs.Utility.encodeUrl(width); height = mejs.Utility.encodeUrl(height); } else { if (options.enablePluginDebug) { width = 320; height = 240; } } // register plugin pluginMediaElement.success = options.success; mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); // add container (must be added to DOM before inserting HTML for IE) container.className = 'me-plugin'; container.id = pluginid + '_container'; if (playback.isVideo) { htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); } else { document.body.insertBefore(container, document.body.childNodes[0]); } // flash/silverlight vars initVars = [ 'id=' + pluginid, 'jsinitfunction=' + "mejs.MediaPluginBridge.initPlugin", 'jscallbackfunction=' + "mejs.MediaPluginBridge.fireEvent", 'isvideo=' + ((playback.isVideo) ? "true" : "false"), 'autoplay=' + ((autoplay) ? "true" : "false"), 'preload=' + preload, 'width=' + width, 'startvolume=' + options.startVolume, 'timerrate=' + options.timerRate, 'flashstreamer=' + options.flashStreamer, 'height=' + height, 'pseudostreamstart=' + options.pseudoStreamingStartQueryParam]; if (playback.url !== null) { if (playback.method == 'flash') { initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); } else { initVars.push('file=' + playback.url); } } if (options.enablePluginDebug) { initVars.push('debug=true'); } if (options.enablePluginSmoothing) { initVars.push('smoothing=true'); } if (options.enablePseudoStreaming) { initVars.push('pseudostreaming=true'); } if (controls) { initVars.push('controls=true'); // shows controls in the plugin if desired } if (options.pluginVars) { initVars = initVars.concat(options.pluginVars); } switch (playback.method) { case 'silverlight': container.innerHTML = '<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + '<param name="initParams" value="' + initVars.join(',') + '" />' + '<param name="windowless" value="true" />' + '<param name="background" value="black" />' + '<param name="minRuntimeVersion" value="3.0.0.0" />' + '<param name="autoUpgrade" value="true" />' + '<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + '</object>'; break; case 'flash': if (mejs.MediaFeatures.isIE) { specialIEContainer = document.createElement('div'); container.appendChild(specialIEContainer); specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + 'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + '<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + '<param name="flashvars" value="' + initVars.join('&') + '" />' + '<param name="quality" value="high" />' + '<param name="bgcolor" value="#000000" />' + '<param name="wmode" value="transparent" />' + '<param name="allowScriptAccess" value="' + options.flashScriptAccess + '" />' + '<param name="allowFullScreen" value="true" />' + '<param name="scale" value="default" />' + '</object>'; } else { container.innerHTML = '<embed id="' + pluginid + '" name="' + pluginid + '" ' + 'play="true" ' + 'loop="false" ' + 'quality="high" ' + 'bgcolor="#000000" ' + 'wmode="transparent" ' + 'allowScriptAccess="' + options.flashScriptAccess + '" ' + 'allowFullScreen="true" ' + 'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + 'src="' + options.pluginPath + options.flashName + '" ' + 'flashvars="' + initVars.join('&') + '" ' + 'width="' + width + '" ' + 'height="' + height + '" ' + 'scale="default"' + 'class="mejs-shim"></embed>'; } break; case 'youtube': var videoId; // youtu.be url from share button if (playback.url.lastIndexOf("youtu.be") != -1) { videoId = playback.url.substr(playback.url.lastIndexOf('/')+1); if (videoId.indexOf('?') != -1) { videoId = videoId.substr(0, videoId.indexOf('?')); } } else { videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); } youtubeSettings = { container: container, containerId: container.id, pluginMediaElement: pluginMediaElement, pluginId: pluginid, videoId: videoId, height: height, width: width }; // favor iframe version of YouTube if (window.postMessage) { mejs.YouTubeApi.enqueueIframe(youtubeSettings); } else if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { mejs.YouTubeApi.createFlash(youtubeSettings, options); } break; // DEMO Code. Does NOT work. case 'vimeo': var player_id = pluginid + "_player"; pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); container.innerHTML ='<iframe src="//player.vimeo.com/video/' + pluginMediaElement.vimeoid + '?api=1&portrait=0&byline=0&title=0&player_id=' + player_id + '" width="' + width +'" height="' + height +'" frameborder="0" class="mejs-shim" id="' + player_id + '" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'; if (typeof($f) == 'function') { // froogaloop available var player = $f(container.childNodes[0]); player.addEvent('ready', function() { player.playVideo = function() { player.api( 'play' ); } player.stopVideo = function() { player.api( 'unload' ); } player.pauseVideo = function() { player.api( 'pause' ); } player.seekTo = function( seconds ) { player.api( 'seekTo', seconds ); } player.setVolume = function( volume ) { player.api( 'setVolume', volume ); } player.setMuted = function( muted ) { if( muted ) { player.lastVolume = player.api( 'getVolume' ); player.api( 'setVolume', 0 ); } else { player.api( 'setVolume', player.lastVolume ); delete player.lastVolume; } } function createEvent(player, pluginMediaElement, eventName, e) { var event = { type: eventName, target: pluginMediaElement }; if (eventName == 'timeupdate') { pluginMediaElement.currentTime = event.currentTime = e.seconds; pluginMediaElement.duration = event.duration = e.duration; } pluginMediaElement.dispatchEvent(event); } player.addEvent('play', function() { createEvent(player, pluginMediaElement, 'play'); createEvent(player, pluginMediaElement, 'playing'); }); player.addEvent('pause', function() { createEvent(player, pluginMediaElement, 'pause'); }); player.addEvent('finish', function() { createEvent(player, pluginMediaElement, 'ended'); }); player.addEvent('playProgress', function(e) { createEvent(player, pluginMediaElement, 'timeupdate', e); }); pluginMediaElement.pluginElement = container; pluginMediaElement.pluginApi = player; // init mejs mejs.MediaPluginBridge.initPlugin(pluginid); }); } else { console.warn("You need to include froogaloop for vimeo to work"); } break; } // hide original element htmlMediaElement.style.display = 'none'; // prevent browser from autoplaying when using a plugin htmlMediaElement.removeAttribute('autoplay'); // FYI: options.success will be fired by the MediaPluginBridge return pluginMediaElement; }, updateNative: function(playback, options, autoplay, preload) { var htmlMediaElement = playback.htmlMediaElement, m; // add methods to video object to bring it into parity with Flash Object for (m in mejs.HtmlMediaElement) { htmlMediaElement[m] = mejs.HtmlMediaElement[m]; } /* Chrome now supports preload="none" if (mejs.MediaFeatures.isChrome) { // special case to enforce preload attribute (Chrome doesn't respect this) if (preload === 'none' && !autoplay) { // forces the browser to stop loading (note: fails in IE9) htmlMediaElement.src = ''; htmlMediaElement.load(); htmlMediaElement.canceledPreload = true; htmlMediaElement.addEventListener('play',function() { if (htmlMediaElement.canceledPreload) { htmlMediaElement.src = playback.url; htmlMediaElement.load(); htmlMediaElement.play(); htmlMediaElement.canceledPreload = false; } }, false); // for some reason Chrome forgets how to autoplay sometimes. } else if (autoplay) { htmlMediaElement.load(); htmlMediaElement.play(); } } */ // fire success code options.success(htmlMediaElement, htmlMediaElement); return htmlMediaElement; } }; /* - test on IE (object vs. embed) - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) - fullscreen? */ // YouTube Flash and Iframe API mejs.YouTubeApi = { isIframeStarted: false, isIframeLoaded: false, loadIframeApi: function() { if (!this.isIframeStarted) { var tag = document.createElement('script'); tag.src = "//www.youtube.com/player_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); this.isIframeStarted = true; } }, iframeQueue: [], enqueueIframe: function(yt) { if (this.isLoaded) { this.createIframe(yt); } else { this.loadIframeApi(); this.iframeQueue.push(yt); } }, createIframe: function(settings) { var pluginMediaElement = settings.pluginMediaElement, player = new YT.Player(settings.containerId, { height: settings.height, width: settings.width, videoId: settings.videoId, playerVars: {controls:0,wmode:'transparent'}, events: { 'onReady': function() { // wrapper to match player.setVideoSize = function(width, height) { player.setSize(width, height); } // hook up iframe object to MEjs settings.pluginMediaElement.pluginApi = player; settings.pluginMediaElement.pluginElement = document.getElementById(settings.containerId); // init mejs mejs.MediaPluginBridge.initPlugin(settings.pluginId); // create timer setInterval(function() { mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); }, 250); }, 'onStateChange': function(e) { mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); } } }); }, createEvent: function (player, pluginMediaElement, eventName) { var event = { type: eventName, target: pluginMediaElement }; if (player && player.getDuration) { // time pluginMediaElement.currentTime = event.currentTime = player.getCurrentTime(); pluginMediaElement.duration = event.duration = player.getDuration(); // state event.paused = pluginMediaElement.paused; event.ended = pluginMediaElement.ended; // sound event.muted = player.isMuted(); event.volume = player.getVolume() / 100; // progress event.bytesTotal = player.getVideoBytesTotal(); event.bufferedBytes = player.getVideoBytesLoaded(); // fake the W3C buffered TimeRange var bufferedTime = event.bufferedBytes / event.bytesTotal * event.duration; event.target.buffered = event.buffered = { start: function(index) { return 0; }, end: function (index) { return bufferedTime; }, length: 1 }; } // send event up the chain pluginMediaElement.dispatchEvent(event); }, iFrameReady: function() { this.isLoaded = true; this.isIframeLoaded = true; while (this.iframeQueue.length > 0) { var settings = this.iframeQueue.pop(); this.createIframe(settings); } }, // FLASH! flashPlayers: {}, createFlash: function(settings) { this.flashPlayers[settings.pluginId] = settings; /* settings.container.innerHTML = '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + '<param name="allowScriptAccess" value="sameDomain">' + '<param name="wmode" value="transparent">' + '</object>'; */ var specialIEContainer, youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; if (mejs.MediaFeatures.isIE) { specialIEContainer = document.createElement('div'); settings.container.appendChild(specialIEContainer); specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + 'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' + '<param name="movie" value="' + youtubeUrl + '" />' + '<param name="wmode" value="transparent" />' + '<param name="allowScriptAccess" value="' + options.flashScriptAccess + '" />' + '<param name="allowFullScreen" value="true" />' + '</object>'; } else { settings.container.innerHTML = '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + '<param name="allowScriptAccess" value="' + options.flashScriptAccess + '">' + '<param name="wmode" value="transparent">' + '</object>'; } }, flashReady: function(id) { var settings = this.flashPlayers[id], player = document.getElementById(id), pluginMediaElement = settings.pluginMediaElement; // hook up and return to MediaELementPlayer.success pluginMediaElement.pluginApi = pluginMediaElement.pluginElement = player; mejs.MediaPluginBridge.initPlugin(id); // load the youtube video player.cueVideoById(settings.videoId); var callbackName = settings.containerId + '_callback'; window[callbackName] = function(e) { mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); } player.addEventListener('onStateChange', callbackName); setInterval(function() { mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); }, 250); mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay'); }, handleStateChange: function(youTubeState, player, pluginMediaElement) { switch (youTubeState) { case -1: // not started pluginMediaElement.paused = true; pluginMediaElement.ended = true; mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); break; case 0: pluginMediaElement.paused = false; pluginMediaElement.ended = true; mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); break; case 1: pluginMediaElement.paused = false; pluginMediaElement.ended = false; mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); break; case 2: pluginMediaElement.paused = true; pluginMediaElement.ended = false; mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); break; case 3: // buffering mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); break; case 5: // cued? break; } } } // IFRAME window.onYouTubePlayerAPIReady = function() { mejs.YouTubeApi.iFrameReady(); }; // FLASH window.onYouTubePlayerReady = function(id) { mejs.YouTubeApi.flashReady(id); }; window.mejs = mejs; window.MediaElement = mejs.MediaElement; /* * Adds Internationalization and localization to mediaelement. * * This file does not contain translations, you have to add them manually. * The schema is always the same: me-i18n-locale-[IETF-language-tag].js * * Examples are provided both for german and chinese translation. * * * What is the concept beyond i18n? * http://en.wikipedia.org/wiki/Internationalization_and_localization * * What langcode should i use? * http://en.wikipedia.org/wiki/IETF_language_tag * https://tools.ietf.org/html/rfc5646 * * * License? * * The i18n file uses methods from the Drupal project (drupal.js): * - i18n.methods.t() (modified) * - i18n.methods.checkPlain() (full copy) * * The Drupal project is (like mediaelementjs) licensed under GPLv2. * - http://drupal.org/licensing/faq/#q1 * - https://github.com/johndyer/mediaelement * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * * @author * Tim Latz (latz.tim@gmail.com) * * * @params * - context - document, iframe .. * - exports - CommonJS, window .. * */ ;(function(context, exports, undefined) { "use strict"; var i18n = { "locale": { // Ensure previous values aren't overwritten. "language" : (exports.i18n && exports.i18n.locale.language) || '', "strings" : (exports.i18n && exports.i18n.locale.strings) || {} }, "ietf_lang_regex" : /^(x\-)?[a-z]{2,}(\-\w{2,})?(\-\w{2,})?$/, "methods" : {} }; // start i18n /** * Get language, fallback to browser's language if empty * * IETF: RFC 5646, https://tools.ietf.org/html/rfc5646 * Examples: en, zh-CN, cmn-Hans-CN, sr-Latn-RS, es-419, x-private */ i18n.getLanguage = function () { var language = i18n.locale.language || window.navigator.userLanguage || window.navigator.language; return i18n.ietf_lang_regex.exec(language) ? language : null; //(WAS: convert to iso 639-1 (2-letters, lower case)) //return language.substr(0, 2).toLowerCase(); }; // i18n fixes for compatibility with WordPress if ( typeof mejsL10n != 'undefined' ) { i18n.locale.language = mejsL10n.language; } /** * Encode special characters in a plain-text string for display as HTML. */ i18n.methods.checkPlain = function (str) { var character, regex, replace = { '&': '&', '"': '"', '<': '<', '>': '>' }; str = String(str); for (character in replace) { if (replace.hasOwnProperty(character)) { regex = new RegExp(character, 'g'); str = str.replace(regex, replace[character]); } } return str; }; /** * Translate strings to the page language or a given language. * * * @param str * A string containing the English string to translate. * * @param options * - 'context' (defaults to the default context): The context the source string * belongs to. * * @return * The translated string, escaped via i18n.methods.checkPlain() */ i18n.methods.t = function (str, options) { // Fetch the localized version of the string. if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { str = i18n.locale.strings[options.context][str]; } return i18n.methods.checkPlain(str); }; /** * Wrapper for i18n.methods.t() * * @see i18n.methods.t() * @throws InvalidArgumentException */ i18n.t = function(str, options) { if (typeof str === 'string' && str.length > 0) { // check every time due language can change for // different reasons (translation, lang switcher ..) var language = i18n.getLanguage(); options = options || { "context" : language }; return i18n.methods.t(str, options); } else { throw { "name" : 'InvalidArgumentException', "message" : 'First argument is either not a string or empty.' }; } }; // end i18n exports.i18n = i18n; }(document, mejs)); // i18n fixes for compatibility with WordPress ;(function(exports, undefined) { "use strict"; if ( typeof mejsL10n != 'undefined' ) { exports[mejsL10n.language] = mejsL10n.strings; } }(mejs.i18n.locale.strings)); /*! * * 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 Zepto != 'undefined') { mejs.$ = Zepto; // define `outerWidth` method which has not been realized in Zepto Zepto.fn.outerWidth = function(includeMargin) { var width = $(this).width(); if (includeMargin) { width += parseInt($(this).css('margin-right'), 10); width += parseInt($(this).css('margin-left'), 10); } return width } } 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, /* * Time format to use. Default: 'mm:ss' * Supported units: * h: hour * m: minute * s: second * f: frame count * When using 'hh', 'mm', 'ss' or 'ff' we always display 2 digits. * If you use 'h', 'm', 's' or 'f' we display 1 digit if possible. * * Example to display 75 seconds: * Format 'mm:ss': 01:15 * Format 'm:ss': 1:15 * Format 'm:s': 1:15 */ timeFormat: '', // 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) { media.play(); } else { media.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]; if(!t.node) { return } // check for existing player if (typeof t.node.player != 'undefined') { return t.node.player; } // 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); if (!t.options.timeFormat) { // Generate the time format according to options t.options.timeFormat = 'mm:ss'; if (t.options.alwaysShowHours) { t.options.timeFormat = 'hh:mm:ss'; } if (t.options.showTimecodeFrameCount) { t.options.timeFormat += ':ff'; } } mejs.Utility.calculateTimeFormat(0, t.options, t.options.framesPerSecond || 25); // 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.svgAsImg ? '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 t.container.find('.mejs-mediaelement').append(t.$media); // needs to be assigned here, after iOS remap t.node.player = t; // 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 .removeClass('mejs-offscreen') .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') .removeClass('mejs-offscreen') .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); } else { t.controls .removeClass('mejs-offscreen') .css('display','block'); // any additional controls people might add and want to hide t.container.find('.mejs-control') .removeClass('mejs-offscreen') .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) .addClass('mejs-offscreen') .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) .addClass('mejs-offscreen') .css('display','block'); }); } else { // hide main controls t.controls .addClass('mejs-offscreen') .css('display','block'); // hide others t.container.find('.mejs-control') .addClass('mejs-offscreen') .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', 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); // Only change the time format when necessary var duration = null; t.media.addEventListener('timeupdate',function() { if (duration !== this.duration) { duration = this.duration; mejs.Utility.calculateTimeFormat(duration, t.options, t.options.framesPerSecond || 25); } }, 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; if (t.controls) { 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') !== 'none' && t.$node.css('max-width') !== 't.width') || (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().length > 0 && 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); } }, setControlsSize: function() { var t = this, usedWidth = 0, railWidth = 0, rail = t.controls.find('.mejs-time-rail'), total = t.controls.find('.mejs-time-total'), 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.length ? lastControl.position() : null; railWidth--; } } while (lastControlPosition !== null && lastControlPosition.top > 0 && railWidth > 0); t.container.trigger('controlsresize'); }, 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%" alt="" />').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(e) { t.handleError(e); loading.hide(); bigPlay.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(event) { player.hasFocus = $(event.target).closest('.mejs-container').length !== 0; return t.onkeydown(player, media, event); }); // 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; t.container.prev('.mejs-offscreen').remove(); // 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; }, rebuildtracks: function(){ var t = this; t.findTracks(); t.buildtracks(t, t.controls, t.layers, t.media); }, resetSize: function(){ var t = this; // webkit has trouble doing this without a delay setTimeout(function () { // t.setPlayerSize(t.width, t.height); t.setControlsSize(); }, 50); } }; (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; var doc = t.node ? t.node.ownerDocument : document; events = splitEvents(events, t.id); if (events.d) $(doc).bind(events.d, data, callback); if (events.w) $(window).bind(events.w, data, callback); }; mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { var t = this; var doc = t.node ? t.node.ownerDocument : document; events = splitEvents(events, t.id); if (events.d) $(doc).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; $('<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, player.options)); controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0, player.options)); 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">' + '<span 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>' + '</span>' + '</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.width(), percentage = 0, newTime = 0, pos = 0, x; // mouse or touch position relative to the object if (e.originalEvent && e.originalEvent.changedTouches) { x = e.originalEvent.changedTouches[0].pageX; } else if (e.changedTouches) { // for Zepto x = e.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, player.options) ); 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, player.options), 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); t.container.on('controlsresize', function() { player.setProgressRail(); player.setCurrentRail(); }); // 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) { // account for a real array with multiple values - always read the end of the last buffer percent = target.buffered.end(target.buffered.length - 1) / 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">' + mejs.Utility.secondsToTimeCode(0, player.options) + '</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">' + mejs.Utility.secondsToTimeCode(t.options.duration, t.options) + '</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">' + mejs.Utility.secondsToTimeCode(t.options.duration, t.options) + '</span>' + '</div>') .appendTo(controls); } t.durationD = t.controls.find('.mejs-duration'); media.addEventListener('timeupdate',function() { player.updateDuration(); }, false); }, updateCurrent: function() { var t = this; var currentTime = t.media.currentTime; if (isNaN(currentTime)) { currentTime = 0; } if (t.currenttime) { t.currenttime.html(mejs.Utility.secondsToTimeCode(currentTime, t.options)); } }, updateDuration: function() { var t = this; var duration = t.media.duration; if (t.options.duration > 0) { duration = t.options.duration; } if (isNaN(duration)) { duration = 0; } //Toggle the long video class if the video is longer than an hour. t.container.toggleClass("mejs-long-video", duration > 3600); if (t.durationD && duration > 0) { t.durationD.html(mejs.Utility.secondsToTimeCode(duration, t.options)); } } }); })(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'); mute.children('button').attr('title', mejs.i18n.t('Unmute')).attr('aria-label', mejs.i18n.t('Unmute')); } else { mute.removeClass('mejs-unmute').addClass('mejs-mute'); mute.children('button').attr('title', mejs.i18n.t('Mute')).attr('aria-label', mejs.i18n.t('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(), 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; }); // 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); // 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); } t.container.on('controlsresize', function() { positionVolumeHandle(media.volume); }); } }); })(mejs.$); (function($) { $.extend(mejs.MepDefaults, { usePluginFullScreen: true, newWindowCallback: function() { return '';}, fullscreenText: mejs.i18n.t('Fullscreen') }); $.extend(MediaElementPlayer.prototype, { isFullScreen: false, isNativeFullScreen: false, isInIframe: false, // Possible modes // (1) 'native-native' HTML5 video + browser fullscreen (IE10+, etc.) // (2) 'plugin-native' plugin video + browser fullscreen (fails in some versions of Firefox) // (3) 'fullwindow' Full window (retains all UI) // usePluginFullScreen = true // (4) 'plugin-click' Flash 1 - click through with pointer events // (5) 'plugin-hover' Flash 2 - hover popup in flash (IE6-8) fullscreenMode: '', buildfullscreen: function(player, controls, layers, media) { if (!player.isVideo) return; player.isInIframe = (window.location != window.parent.location); // detect on start media.addEventListener('play', function() { player.detectFullscreenMode(); }); // build button var t = this, hideTimeout = null, 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) .on('click', function() { // toggle fullscreen var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; if (isFullScreen) { player.exitFullScreen(); } else { player.enterFullScreen(); } }) .on('mouseover', function() { // very old browsers with a plugin if (t.fullscreenMode == 'plugin-hover') { 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 (t.fullscreenMode == 'plugin-hover') { if (hideTimeout !== null) { clearTimeout(hideTimeout); delete hideTimeout; } hideTimeout = setTimeout(function() { media.hideFullscreenButton(); }, 1500); } }); player.fullscreenBtn = fullscreenBtn; t.globalBind('keydown',function (e) { if (e.keyCode == 27 && ((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen)) { player.exitFullScreen(); } }); t.normalHeight = 0; t.normalWidth = 0; // setup native fullscreen event if (mejs.MediaFeatures.hasTrueNativeFullScreen) { // chrome doesn't alays fire this in an iframe var fullscreenChanged = 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, fullscreenChanged); } }, detectFullscreenMode: function() { var t = this, mode = '', features = mejs.MediaFeatures; if (features.hasTrueNativeFullScreen && t.media.pluginType === 'native') { mode = 'native-native'; } else if (features.hasTrueNativeFullScreen && t.media.pluginType !== 'native' && !features.hasFirefoxPluginMovingProblem) { mode = 'plugin-native'; } else if (t.usePluginFullScreen) { if (mejs.MediaFeatures.supportsPointerEvents) { mode = 'plugin-click'; // this needs some special setup t.createPluginClickThrough(); } else { mode = 'plugin-hover'; } } else { mode = 'fullwindow'; } t.fullscreenMode = mode; return mode; }, isPluginClickThroughCreated: false, createPluginClickThrough: function() { var t = this; // don't build twice if (t.isPluginClickThroughCreated) { return; } // allows clicking through the fullscreen button and controls down directly to Flash /* When a user puts his mouse over the fullscreen button, we disable the controls so that mouse events can go down to flash (pointer-events) We then put a divs over the video and on either side of the fullscreen button to capture 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 t.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 t.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; } } }); t.isPluginClickThroughCreated = true; }, cleanfullscreen: function(player) { player.exitFullScreen(); }, containerSizeTimeout: null, enterFullScreen: function() { var t = this; if (mejs.MediaFeatures.hasiOSFullScreen) { t.media.webkitEnterFullscreen(); return; } // set it to not show scroll bars so 100% will work $(document.documentElement).addClass('mejs-fullscreen'); // store sizing t.normalHeight = t.container.height(); t.normalWidth = t.container.width(); // attempt to do true fullscreen if (t.fullscreenMode === 'native-native' || t.fullscreenMode === 'plugin-native') { 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. percentErrorMargin = 0.002, // 0.2% windowWidth = zoomMultiplier * $(window).width(), screenWidth = screen.width, // ** 13twelve // Screen width is sort of useless: http://www.quirksmode.org/blog/archives/2013/11/screenwidth_is.html // My rMBP ignores devicePixelRatio when returning the values, so fullscreen would always fail the "suddenly not fullscreen" test // Theory: the gap between reported values should give us an indication of browser behavior with screen.width and devicePixelRatio zoomedWindowWidth = zoomMultiplier * windowWidth; if (Math.abs(screenWidth-windowWidth) > Math.abs(screenWidth-zoomedWindowWidth)) { // screen.width is likely true pixels, not CSS pixels, so we need to use the zoomed window width for comparison windowWidth = zoomedWindowWidth; } // ** / 13twelve var absDiff = Math.abs(screenWidth - windowWidth), 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); } } }, 1000); } } else if (t.fullscreeMode == 'fullwindow') { // move into position } // 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%'); setTimeout(function() { var win = $(window), winW = win.width(), winH = win.height(); t.media.setVideoSize(winW,winH); }, 500); } 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'); t.container.trigger('enteredfullscreen'); }, 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 out 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(t.normalWidth) .height(t.normalHeight); if (t.media.pluginType === 'native') { t.$media .width(t.normalWidth) .height(t.normalHeight); } else { t.container.find('.mejs-shim') .width(t.normalWidth) .height(t.normalHeight); t.media.setVideoSize(t.normalWidth, t.normalHeight); } t.layers.children('div') .width(t.normalWidth) .height(t.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', ''); t.container.trigger('exitedfullscreen'); } }); })(mejs.$); (function($) { // Speed $.extend(mejs.MepDefaults, { // We also support to pass object like this: // [{name: 'Slow', value: '0.75'}, {name: 'Normal', value: '1.00'}, ...] 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, inputId = null; var speeds = []; var defaultInArray = false; for (var i=0, len=t.options.speeds.length; i < len; i++) { var s = t.options.speeds[i]; if (typeof(s) === 'string'){ speeds.push({ name: s + t.options.speedChar, value: s }); if(s === t.options.defaultSpeed) { defaultInArray = true; } } else { speeds.push(s); if(s.value === t.options.defaultSpeed) { defaultInArray = true; } } } if (!defaultInArray) { speeds.push({ name: t.options.defaultSpeed + t.options.speedChar, value: t.options.defaultSpeed }); } speeds.sort(function(a, b) { return parseFloat(b.value) - parseFloat(a.value); }); var getSpeedNameFromValue = function(value) { for(i=0,len=speeds.length; i <len; i++) { if (speeds[i].value === value) { return speeds[i].name; } } }; var html = '<div class="mejs-button mejs-speed-button">' + '<button type="button">' + getSpeedNameFromValue(t.options.defaultSpeed) + '</button>' + '<div class="mejs-speed-selector">' + '<ul>'; for (i = 0, il = speeds.length; i<il; i++) { inputId = t.id + '-speed-' + speeds[i].value; html += '<li>' + '<input type="radio" name="speed" ' + 'value="' + speeds[i].value + '" ' + 'id="' + inputId + '" ' + (speeds[i].value === t.options.defaultSpeed ? ' checked' : '') + ' />' + '<label for="' + inputId + '" ' + (speeds[i].value === t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') + '>' + speeds[i].name + '</label>' + '</li>'; } html += '</ul></div></div>'; speedButton = $(html).appendTo(controls); speedSelector = speedButton.find('.mejs-speed-selector'); playbackSpeed = t.options.defaultSpeed; media.addEventListener('loadedmetadata', function(e) { if (playbackSpeed) { media.playbackRate = parseFloat(playbackSpeed); } }, true); speedSelector .on('click', 'input[type="radio"]', function() { var newSpeed = $(this).attr('value'); playbackSpeed = newSpeed; media.playbackRate = parseFloat(newSpeed); speedButton.find('button').html(getSpeedNameFromValue(newSpeed)); speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected'); }); speedButton .one( 'mouseenter focusin', function() { 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'), // By default, no WAI-ARIA live region - don't make a // screen reader speak captions over an audio track. tracksAriaLive: false, // 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, cleartracks: function(player, controls, layers, media){ if(player) { if(player.captions) player.captions.remove(); if(player.chapters) player.chapters.remove(); if(player.captionsText) player.captionsText.remove(); if(player.captionsButton) player.captionsButton.remove(); } }, buildtracks: function(player, controls, layers, media) { if (player.tracks.length === 0) return; var t = this, attr = t.options.tracksAriaLive ? 'role="log" aria-live="assertive" aria-atomic="false"' : '', i; 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"; } } t.cleartracks(player, controls, layers, media); 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" ' + attr + '><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').removeClass('mejs-offscreen'); }) // 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").addClass("mejs-offscreen"); }); } 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.removeClass('mejs-offscreen'); player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); } }, function () { if (player.hasChapters && !media.paused) { player.chapters.fadeOut(200, function() { $(this).addClass('mejs-offscreen'); $(this).css('display','block'); }); } }); t.container.on('controlsresize', function() { t.adjustLanguageBox(); }); // check for autoplay if (player.node.getAttribute('autoplay') !== null) { player.chapters.addClass('mejs-offscreen'); } }, 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; 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.removeTrackButton(track.srclang); 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(); }, removeTrackButton: function(lang) { var t = this; t.captionsButton.find('input[value=' + lang + ']').closest('li').remove(); 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' && t.tracks[i].isLoaded) { 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, t.options) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop, t.options) + '</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, 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.$); (function($) { // skip back button $.extend(mejs.MepDefaults, { skipBackInterval: 30, // %1 will be replaced with skipBackInterval in this string skipBackText: mejs.i18n.t('Skip back %1 seconds') }); $.extend(MediaElementPlayer.prototype, { buildskipback: function(player, controls, layers, media) { var t = this, // Replace %1 with skip back interval backText = t.options.skipBackText.replace('%1', t.options.skipBackInterval), // create the loop button loop = $('<div class="mejs-button mejs-skip-back-button">' + '<button type="button" aria-controls="' + t.id + '" title="' + backText + '" aria-label="' + backText + '">' + t.options.skipBackInterval + '</button>' + '</div>') // append it to the toolbar .appendTo(controls) // add a click toggle event .click(function() { media.setCurrentTime(Math.max(media.currentTime - t.options.skipBackInterval, 0)); $(this).find('button').blur(); }); } }); })(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.$);