Mercurial > hg > env-test-daniele
view build/mediaelement-and-player.js @ 25:4a4bd554b4c1 tip
Closing this sub branch.
author | Daniele Barchiesi <daniele.barchiesi@eecs.qmul.ac.uk> |
---|---|
date | Mon, 25 Mar 2013 14:02:54 +0000 |
parents | 032bc65ebafc |
children |
line wrap: on
line source
/*! * 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-2011, John Dyer (http://j.hn) * Dual licensed under the MIT or GPL Version 2 licenses. * */ // Namespace var mejs = mejs || {}; // version number mejs.version = '2.1.9'; // 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/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg']} //,{version: [11,0], types: ['video/webm']} // for future reference ] }; /* 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, path = '', name = '', script, scripts = document.getElementsByTagName('script'); for (; i < scripts.length; i++) { script = scripts[i].src; for (j = 0; j < scriptNames.length; j++) { name = scriptNames[j]; if (script.indexOf(name) > -1) { path = script.substring(0, script.indexOf(name)); break; } } if (path !== '') { break; } } return path; }, secondsToTimeCode: function(seconds,forceHours) { seconds = Math.round(seconds); var hours, minutes = Math.floor(seconds / 60); if (minutes >= 60) { hours = Math.floor(minutes / 60); minutes = minutes % 60; } hours = hours === undefined ? "00" : (hours >= 10) ? hours : "0" + hours; minutes = (minutes >= 10) ? minutes : "0" + minutes; seconds = Math.floor(seconds % 60); seconds = (seconds >= 10) ? seconds : "0" + seconds; return ((hours > 0 || forceHours === true) ? hours + ":" :'') + minutes + ":" + seconds; }, timeCodeToSeconds: function(timecode){ var tab = timecode.split(':'); return tab[0]*60*60 + tab[1]*60 + parseFloat(tab[2].replace(',','.')); } }; // 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 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) this.isiPad = (ua.match(/ipad/i) !== null); this.isiPhone = (ua.match(/iphone/i) !== null); this.isAndroid = (ua.match(/android/i) !== null); this.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); this.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1); this.isChrome = (ua.match(/chrome/gi) !== null); this.isFirefox = (ua.match(/firefox/gi) !== null); // 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]); } // detect native JavaScript fullscreen (Safari only, Chrome fails) this.hasNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); if (this.isChrome) { this.hasNativeFullScreen = false; } // OS X 10.5 can't do this even if it says it can :( if (this.hasNativeFullScreen && ua.match(/mac os x 10_5/i)) { this.hasNativeFullScreen = 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) { 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; } } } }, 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 = {}; }; // 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, // 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) { this.pluginApi.playMedia(); this.paused = false; } }, load: function () { if (this.pluginApi != null) { this.pluginApi.loadMedia(); this.paused = false; } }, pause: function () { if (this.pluginApi != null) { this.pluginApi.pauseMedia(); this.paused = true; } }, stop: function () { if (this.pluginApi != null) { 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 true; } } } } return false; }, // 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(url); } } } }, setCurrentTime: function (time) { if (this.pluginApi != null) { this.pluginApi.setCurrentTime(time); this.currentTime = time; } }, setVolume: function (volume) { if (this.pluginApi != null) { this.pluginApi.setVolume(volume); this.volume = volume; } }, setMuted: function (muted) { if (this.pluginApi != null) { this.pluginApi.setMuted(muted); this.muted = muted; } }, // additional non-HTML5 methods setVideoSize: function (width, height) { if ( this.pluginElement.style) { this.pluginElement.style.width = width + 'px'; this.pluginElement.style.height = height + 'px'; } if (this.pluginApi != null) { this.pluginApi.setVideoSize(width, height); } }, setFullscreen: function (fullscreen) { if (this.pluginApi != null) { this.pluginApi.setFullscreen(fullscreen); } }, // 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 (i = 0; i < callbacks.length; i++) { if (callbacks[i] === callback) { this.events[eventName].splice(i, 1); return true; } } return false; }, dispatchEvent: function (eventName) { var i, args, callbacks = this.events[eventName]; if (callbacks) { args = Array.prototype.slice.call(arguments, 1); for (i = 0; i < callbacks.length; i++) { callbacks[i].apply(null, args); } } } // end: fake events }; // 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; }, // when Flash/Silverlight is ready, it calls out to this method initPlugin: function (id) { var pluginMediaElement = this.pluginMediaElements[id], htmlMediaElement = this.htmlMediaElements[id]; // 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]; pluginMediaElement.ended = false; pluginMediaElement.paused = true; // 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.type, e); } }; /* Default options */ mejs.MediaElementDefaults = { // allows testing on HTML5, flash, silverlight // auto: attempts to detect what the browser can do // 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'], // shows debug errors on screen enablePluginDebug: 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', // turns on the smoothing filter in Flash enablePluginSmoothing: false, // 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, // 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, 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 = mejs.MediaElementDefaults, htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'), supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'), playback = {method:'', url:''}, poster = htmlMediaElement.getAttribute('poster'), autoplay = htmlMediaElement.getAttribute('autoplay'), preload = htmlMediaElement.getAttribute('preload'), controls = htmlMediaElement.getAttribute('controls'), prop; // extend options for (prop in o) { options[prop] = o[prop]; } // check for real poster 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, isVideo, supportsMediaTag); if (playback.method == 'native') { // second fix for android if (mejs.MediaFeatures.isBustedAndroid) { htmlMediaElement.src = playback.url; htmlMediaElement.addEventListener('click', function() { htmlMediaElement.play(); }, true); } // add methods to native HTMLMediaElement return this.updateNative( htmlMediaElement, options, autoplay, preload, playback); } else if (playback.method !== '') { // create plugin to mimic HTMLMediaElement return this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls); } else { // boo, no HTML5, no Flash, no Silverlight. this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster ); } }, determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) { var mediaFiles = [], i, j, k, l, n, type, result = { method: '', url: ''}, src = htmlMediaElement.getAttribute('src'), pluginName, pluginVersions, pluginInfo; // clean up src attr if (src == 'undefined' || src == '' || src === null) src = null; // STEP 1: Get URL and type from <video src> or <source src> // supplied type overrides all HTML if (typeof (options.type) != 'undefined' && options.type !== '') { mediaFiles.push({type:options.type, url:src}); // test for src attribute first } else if (src !== null) { type = this.checkType(src, htmlMediaElement.getAttribute('type'), isVideo); 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.checkType(src, n.getAttribute('type'), isVideo); mediaFiles.push({type:type, url:src}); } } } // 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' : ''; }; } // test for native playback first if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) { for (i=0; i<mediaFiles.length; i++) { // normal check if (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/, '') !== '') { result.method = 'native'; result.url = mediaFiles[i].url; return result; } } } // if native playback didn't work, then test plugins if (options.mode === 'auto' || 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 if (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 == pluginInfo.types[l]) { result.method = pluginName; result.url = mediaFiles[i].url; return result; } } } } } } } // what if there's nothing to play? just grab the first available if (result.method === '') { result.url = mediaFiles[0].url; } return result; }, checkType: function(url, type, isVideo) { var ext; // if no type is supplied, fake it with the extension if (url && !type) { ext = url.substring(url.lastIndexOf('.') + 1); return ((isVideo) ? 'video' : 'audio') + '/' + ext; } 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; } } }, createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) { var errorContainer = document.createElement('div'); errorContainer.className = 'me-cannotplay'; try { errorContainer.style.width = htmlMediaElement.width + 'px'; errorContainer.style.height = htmlMediaElement.height + 'px'; } catch (e) {} errorContainer.innerHTML = (poster !== '') ? '<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' : '<a href="' + downloadUrl + '"><span>Download File</span></a>'; htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); htmlMediaElement.style.display = 'none'; options.error(htmlMediaElement); }, createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) { var width = 1, height = 1, pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++), pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl), container = document.createElement('div'), specialIEContainer, node, initVars; // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) node = htmlMediaElement.parentNode; while (node !== null && node.tagName.toLowerCase() != 'body') { if (node.parentNode.tagName.toLowerCase() == 'p') { node.parentNode.parentNode.insertBefore(node, node.parentNode); break; } node = node.parentNode; } if (isVideo) { width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; } 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'; htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); // flash/silverlight vars initVars = [ 'id=' + pluginid, 'isvideo=' + ((isVideo) ? "true" : "false"), 'autoplay=' + ((autoplay) ? "true" : "false"), 'preload=' + preload, 'width=' + width, 'startvolume=' + options.startVolume, 'timerrate=' + options.timerRate, 'height=' + height]; if (mediaUrl !== null) { if (pluginType == 'flash') { initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl)); } else { initVars.push('file=' + mediaUrl); } } if (options.enablePluginDebug) { initVars.push('debug=true'); } if (options.enablePluginSmoothing) { initVars.push('smoothing=true'); } if (controls) { initVars.push('controls=true'); // shows controls in the plugin if desired } switch (pluginType) { case 'silverlight': container.innerHTML = '<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' + '<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="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + 'id="' + pluginid + '" width="' + width + '" height="' + height + '">' + '<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="always" />' + '<param name="allowFullScreen" value="true" />' + '</object>'; } else { container.innerHTML = '<embed id="' + pluginid + '" name="' + pluginid + '" ' + 'play="true" ' + 'loop="false" ' + 'quality="high" ' + 'bgcolor="#000000" ' + 'wmode="transparent" ' + 'allowScriptAccess="always" ' + 'allowFullScreen="true" ' + 'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' + 'src="' + options.pluginPath + options.flashName + '" ' + 'flashvars="' + initVars.join('&') + '" ' + 'width="' + width + '" ' + 'height="' + height + '"></embed>'; } break; } // hide original element htmlMediaElement.style.display = 'none'; // FYI: options.success will be fired by the MediaPluginBridge return pluginMediaElement; }, updateNative: function(htmlMediaElement, options, autoplay, preload, playback) { // add methods to video object to bring it into parity with Flash Object for (var 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; } }; window.mejs = mejs; window.MediaElement = mejs.MediaElement; /*! * 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-2011, John Dyer (http://j.hn/) * Dual licensed under the MIT or GPL Version 2 licenses. * */ if (typeof jQuery != 'undefined') { mejs.$ = jQuery; } else if (typeof ender != 'undefined') { mejs.$ = ender; } (function ($) { // default player values mejs.MepDefaults = { // url to poster (to fix iOS 3.x) poster: '', // default if the <video width> is not specified defaultVideoWidth: 480, // default if the <video height> is not specified defaultVideoHeight: 270, // if set, overrides <video width> videoWidth: -1, // if set, overrides <video height> videoHeight: -1, // width of audio player audioWidth: 400, // height of audio player audioHeight: 30, // initial volume when the player starts (overrided by user cookie) startVolume: 0.8, // useful for <audio> player loops loop: false, // resize to media dimensions enableAutosize: true, // forces the hour marker (##:00:00) alwaysShowHours: false, // Hide controls when playing and mouse is not over the video alwaysShowControls: false, // force iPad's native controls iPadUseNativeControls: true, // features to show features: ['playpause','current','progress','duration','tracks','volume','fullscreen'] }; mejs.mepIndex = 0; // 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, mf = mejs.MediaFeatures; // create options t.options = $.extend({},mejs.MepDefaults,o); // these will be reset after the MediaElement.success fires t.$media = t.$node = $(node); t.node = t.media = t.$media[0]; // check for existing player if (typeof t.node.player != 'undefined') { return t.node.player; } else { // attach player to DOM node for reference t.node.player = t; } t.isVideo = (t.media.tagName.toLowerCase() === 'video'); /* FUTURE WORK = create player without existing <video> or <audio> node // if not a video or audio tag, then we'll dynamically create it if (tagName == 'video' || tagName == 'audio') { t.$media = $($node); } else if (o.tagName !== '' && o.src !== '') { // create a new node if (o.mode == 'auto' || o.mode == 'native') { $media = $(o.tagName); if (typeof o.src == 'string') { $media.attr('src',o.src); } else if (typeof o.src == 'object') { // create source nodes for (var x in o.src) { $media.append($('<source src="' + o.src[x].src + '" type="' + o.src[x].type + '" />')); } } if (o.type != '') { $media.attr('type',o.type); } if (o.poster != '') { $media.attr('poster',o.poster); } if (o.videoWidth > 0) { $media.attr('width',o.videoWidth); } if (o.videoHeight > 0) { $media.attr('height',o.videoHeight); } $node.clear(); $node.append($media); t.$media = $media; } else if (o.mode == 'shim') { $media = $(); // doesn't want a media node // let MediaElement object handle this } } else { // fail? return; } */ t.init(); return t; }; // actual player mejs.MediaElementPlayer.prototype = { 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);} }); // use native controls in iPad, iPhone, and Android if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) { // add controls and stop t.$media.attr('controls', 'controls'); // attempt to fix iOS 3 bug t.$media.removeAttr('poster'); // override Apple's autoplay override for iPads if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { t.media.load(); t.media.play(); } } else if (mf.isAndroid && t.isVideo) { // leave default player } else { // DESKTOP: use MediaElementPlayer controls // remove native controls t.$media.removeAttr('controls'); // unique ID t.id = 'mep_' + mejs.mepIndex++; // build container t.container = $('<div id="' + t.id + '" class="mejs-container">'+ '<div class="mejs-inner">'+ '<div class="mejs-mediaelement"></div>'+ '<div class="mejs-layers"></div>'+ '<div class="mejs-controls"></div>'+ '<div class="mejs-clear"></div>'+ '</div>' + '</div>') .addClass(t.$media[0].className) .insertBefore(t.$media); // move the <video/video> tag into the right spot t.container.find('.mejs-mediaelement').append(t.$media); // find parts t.controls = t.container.find('.mejs-controls'); t.layers = t.container.find('.mejs-layers'); // determine the size if (t.isVideo) { // priority = videoWidth (forced), width attribute, defaultVideoWidth t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth; t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight; } else { t.width = t.options.audioWidth; t.height = t.options.audioHeight; } // set the size, while we wait for the plugins to load below t.setPlayerSize(t.width, t.height); // create MediaElementShim meOptions.pluginWidth = t.height; meOptions.pluginHeight = t.width; } // create MediaElement shim mejs.MediaElement(t.$media[0], meOptions); }, // Sets up all controls and events meReady: function(media, domNode) { var t = this, mf = mejs.MediaFeatures, f, 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.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) { // two built in features t.buildposter(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 (f in t.options.features) { feature = t.options.features[f]; 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) { // show/hide controls t.container .bind('mouseenter', function () { if (!t.options.alwaysShowControls) { t.controls.css('visibility','visible'); t.controls.stop(true, true).fadeIn(200); } }) .bind('mouseleave', function () { if (!t.media.paused && !t.options.alwaysShowControls) { t.controls.stop(true, true).fadeOut(200, function() { $(this).css('visibility','hidden'); $(this).css('display','block'); }); } }); // check for autoplay if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) { t.controls.css('visibility','hidden'); } // 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); } } // ended for all t.media.addEventListener('ended', function (e) { t.media.setCurrentTime(0); t.media.pause(); if (t.setProgressRail) t.setProgressRail(); if (t.setCurrentRail) t.setCurrentRail(); if (t.options.loop) { t.media.play(); } else if (!t.options.alwaysShowControls) { t.controls.css('visibility','visible'); } }, true); // resize on the first play t.media.addEventListener('loadedmetadata', function(e) { if (t.updateDuration) { t.updateDuration(); } if (t.updateCurrent) { t.updateCurrent(); } t.setControlsSize(); }, true); // webkit has trouble doing this without a delay setTimeout(function () { t.setControlsSize(); t.setPlayerSize(t.width, t.height); }, 50); } if (t.options.success) { t.options.success(t.media, t.domNode); } }, handleError: function(e) { // Tell user that the file cannot be played if (this.options.error) { this.options.error(e); } }, setPlayerSize: function(width,height) { var t = this; // ie9 appears to need this (jQuery bug?) t.width = parseInt(width, 10); t.height = parseInt(height, 10); t.container .width(t.width) .height(t.height); t.layers.children('.mejs-layer') .width(t.width) .height(t.height); }, setControlsSize: function() { var t = this, usedWidth = 0, railWidth = 0, rail = t.controls.find('.mejs-time-rail'), total = t.controls.find('.mejs-time-total'), current = t.controls.find('.mejs-time-current'), loaded = t.controls.find('.mejs-time-loaded'); others = rail.siblings(); // find the size of all the other controls besides the rail others.each(function() { if ($(this).css('position') != 'absolute') { usedWidth += $(this).outerWidth(true); } }); // fit the rail into the remaining space railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false)); // outer area rail.width(railWidth); // dark space total.width(railWidth - (total.outerWidth(true) - total.width())); if (t.setProgressRail) t.setProgressRail(); if (t.setCurrentRail) t.setCurrentRail(); }, buildposter: function(player, controls, layers, media) { var poster = $('<div class="mejs-poster mejs-layer">'+ '<img />'+ '</div>') .appendTo(layers), posterUrl = player.$media.attr('poster'), posterImg = poster.find('img').width(player.width).height(player.height); // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) if (player.options.poster != '') { posterImg.attr('src',player.options.poster); // second, try the real poster } else if (posterUrl !== '' && posterUrl != null) { posterImg.attr('src',posterUrl); } else { poster.remove(); } media.addEventListener('play',function() { poster.hide(); }, false); }, buildoverlays: function(player, controls, layers, media) { if (!player.isVideo) return; var loading = $('<div class="mejs-overlay mejs-layer">'+ '<div class="mejs-overlay-loading"><span></span></div>'+ '</div>') .hide() // start out hidden .appendTo(layers), error = $('<div class="mejs-overlay mejs-layer">'+ '<div class="mejs-overlay-error"></div>'+ '</div>') .hide() // start out hidden .appendTo(layers), // this needs to come last so it's on top bigPlay = $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ '<div class="mejs-overlay-button"></div>'+ '</div>') .appendTo(layers) .click(function() { if (media.paused) { media.play(); } else { media.pause(); } }); // show/hide big play button media.addEventListener('play',function() { bigPlay.hide(); error.hide(); }, false); media.addEventListener('pause',function() { bigPlay.show(); }, false); // show/hide loading media.addEventListener('loadstart',function() { // for some reason Chrome is firing this event if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') return; loading.show(); }, false); media.addEventListener('canplay',function() { loading.hide(); }, false); // error handling media.addEventListener('error',function() { loading.hide(); error.show(); error.find('mejs-overlay-error').html("Error loading this resource"); }, false); }, findTracks: function() { var t = this, tracktags = t.$media.find('track'); // store for use by plugins t.tracks = []; tracktags.each(function() { t.tracks.push({ srclang: $(this).attr('srclang').toLowerCase(), src: $(this).attr('src'), kind: $(this).attr('kind'), entries: [], isLoaded: false }); }); }, changeSkin: function(className) { this.container[0].className = 'mejs-container ' + className; this.setPlayerSize(); this.setControlsSize(); }, play: function() { this.media.play(); }, pause: function() { this.media.pause(); }, load: function() { this.media.load(); }, setMuted: function(muted) { this.media.setMuted(muted); }, setCurrentTime: function(time) { this.media.setCurrentTime(time); }, getCurrentTime: function() { return this.media.currentTime; }, setVolume: function(volume) { this.media.setVolume(volume); }, getVolume: function() { return this.media.volume; }, setSrc: function(src) { this.media.setSrc(src); } }; // turn into jQuery plugin if (typeof jQuery != 'undefined') { jQuery.fn.mediaelementplayer = function (options) { return this.each(function () { new mejs.MediaElementPlayer(this, options); }); }; } // push out to window window.MediaElementPlayer = mejs.MediaElementPlayer; })(mejs.$); (function($) { // PLAY/pause BUTTON MediaElementPlayer.prototype.buildplaypause = function(player, controls, layers, media) { var play = $('<div class="mejs-button mejs-playpause-button mejs-play" type="button">' + '<button type="button"></button>' + '</div>') .appendTo(controls) .click(function(e) { e.preventDefault(); if (media.paused) { media.play(); } else { media.pause(); } return false; }); media.addEventListener('play',function() { play.removeClass('mejs-play').addClass('mejs-pause'); }, false); media.addEventListener('playing',function() { play.removeClass('mejs-play').addClass('mejs-pause'); }, false); media.addEventListener('pause',function() { play.removeClass('mejs-pause').addClass('mejs-play'); }, false); media.addEventListener('paused',function() { play.removeClass('mejs-pause').addClass('mejs-play'); }, false); } })(mejs.$); (function($) { // STOP BUTTON MediaElementPlayer.prototype.buildstop = function(player, controls, layers, media) { var stop = $('<div class="mejs-button mejs-stop-button mejs-stop">' + '<button type="button"></button>' + '</div>') .appendTo(controls) .click(function() { if (!media.paused) { media.pause(); } if (media.currentTime > 0) { media.setCurrentTime(0); controls.find('.mejs-time-current').width('0px'); controls.find('.mejs-time-handle').css('left', '0px'); controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); layers.find('.mejs-poster').show(); } }); } })(mejs.$); (function($) { // progress/loaded bar MediaElementPlayer.prototype.buildprogress = function(player, controls, layers, media) { $('<div class="mejs-time-rail">'+ '<span class="mejs-time-total">'+ '<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); 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'), handleMouseMove = function (e) { // mouse position relative to the object var x = e.pageX, offset = total.offset(), width = total.outerWidth(), percentage = 0, newTime = 0; if (x > offset.left && x <= width + offset.left && media.duration) { percentage = ((x - offset.left) / width); newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; // seek to where the mouse is if (mouseIsDown) { media.setCurrentTime(newTime); } // position floating time box var pos = x - offset.left; timefloat.css('left', pos); timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); } }, mouseIsDown = false, mouseIsOver = false; // handle clicks //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); total .bind('mousedown', function (e) { mouseIsDown = true; handleMouseMove(e); return false; }); controls.find('.mejs-time-rail') .bind('mouseenter', function(e) { mouseIsOver = true; }) .bind('mouseleave',function(e) { mouseIsOver = false; }); $(document) .bind('mouseup', function (e) { mouseIsDown = false; //handleMouseMove(e); }) .bind('mousemove', function (e) { if (mouseIsDown || mouseIsOver) { handleMouseMove(e); } }); // 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); }, false); // store for later use t.loaded = loaded; t.total = total; t.current = current; t.handle = handle; } MediaElementPlayer.prototype.setProgressRail = function(e) { var t = this, target = (e != undefined) ? e.target : t.media, percent = null; // newest HTML5 spec has buffered array (FF4, Webkit) if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { // TODO: account for a real array with multiple values (only Firefox 4 has this so far) percent = target.buffered.end(0) / target.duration; } // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() // to be anything other than 0. If the byte count is available we use this instead. // Browsers that support the else if do not seem to have the bufferedBytes value and // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { percent = target.bufferedBytes / target.bytesTotal; } // Firefox 3 with an Ogg file seems to go this way else if (e && e.lengthComputable && e.total != 0) { percent = e.loaded/e.total; } // finally update the progress bar if (percent !== null) { percent = Math.min(1, Math.max(0, percent)); // update loaded bar if (t.loaded && t.total) { t.loaded.width(t.total.width() * percent); } } } MediaElementPlayer.prototype.setCurrentRail = function() { var t = this; if (t.media.currentTime != undefined && t.media.duration) { // update bar and handle if (t.total && t.handle) { var newWidth = t.total.width() * t.media.currentTime / t.media.duration, handlePos = newWidth - (t.handle.outerWidth(true) / 2); t.current.width(newWidth); t.handle.css('left', handlePos); } } } })(mejs.$); (function($) { // current and duration 00:00 / 00:00 MediaElementPlayer.prototype.buildcurrent = function(player, controls, layers, media) { var t = this; $('<div class="mejs-time">'+ '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ '</div>') .appendTo(controls); t.currenttime = t.controls.find('.mejs-currenttime'); media.addEventListener('timeupdate',function() { player.updateCurrent(); }, false); }; MediaElementPlayer.prototype.buildduration = function(player, controls, layers, media) { var t = this; if (controls.children().last().find('.mejs-currenttime').length > 0) { $(' <span> | </span> '+ '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>') .appendTo(controls.find('.mejs-time')); } else { // add class to current time controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); $('<div class="mejs-time mejs-duration-container">'+ '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ '</div>') .appendTo(controls); } t.durationD = t.controls.find('.mejs-duration'); media.addEventListener('timeupdate',function() { player.updateDuration(); }, false); }; MediaElementPlayer.prototype.updateCurrent = function() { var t = this; if (t.currenttime) { t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime | 0, t.options.alwaysShowHours || t.media.duration > 3600 )); } } MediaElementPlayer.prototype.updateDuration = function() { var t = this; if (t.media.duration && t.durationD) { t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours)); } }; })(mejs.$); (function($) { MediaElementPlayer.prototype.buildvolume = function(player, controls, layers, media) { var mute = $('<div class="mejs-button mejs-volume-button mejs-mute">'+ '<button type="button"></button>'+ '<div class="mejs-volume-slider">'+ // outer background '<div class="mejs-volume-total"></div>'+ // line background '<div class="mejs-volume-current"></div>'+ // current volume '<div class="mejs-volume-handle"></div>'+ // handle '</div>'+ '</div>') .appendTo(controls), volumeSlider = mute.find('.mejs-volume-slider'), volumeTotal = mute.find('.mejs-volume-total'), volumeCurrent = mute.find('.mejs-volume-current'), volumeHandle = mute.find('.mejs-volume-handle'), positionVolumeHandle = function(volume) { var top = volumeTotal.height() - (volumeTotal.height() * volume); // handle volumeHandle.css('top', top - (volumeHandle.height() / 2)); // show the current visibility volumeCurrent.height(volumeTotal.height() - top + parseInt(volumeTotal.css('top').replace(/px/,''),10)); volumeCurrent.css('top', top); }, handleVolumeMove = function(e) { var railHeight = volumeTotal.height(), totalOffset = volumeTotal.offset(), totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), newY = e.pageY - totalOffset.top, volume = (railHeight - newY) / railHeight // TODO: handle vertical and horizontal CSS // only allow it to move within the rail if (newY < 0) newY = 0; else if (newY > railHeight) newY = railHeight; // move the handle to match the mouse volumeHandle.css('top', newY - (volumeHandle.height() / 2) + totalTop ); // show the current visibility volumeCurrent.height(railHeight-newY); volumeCurrent.css('top',newY+totalTop); // set mute status if (volume == 0) { media.setMuted(true); mute.removeClass('mejs-mute').addClass('mejs-unmute'); } else { media.setMuted(false); mute.removeClass('mejs-unmute').addClass('mejs-mute'); } volume = Math.max(0,volume); volume = Math.min(volume,1); // set the volume media.setVolume(volume); }, mouseIsDown = false; // SLIDER volumeSlider .bind('mousedown', function (e) { handleVolumeMove(e); mouseIsDown = true; return false; }); $(document) .bind('mouseup', function (e) { mouseIsDown = false; }) .bind('mousemove', function (e) { if (mouseIsDown) { handleVolumeMove(e); } }); // MUTE button mute.find('button').click(function() { if (media.muted) { media.setMuted(false); mute.removeClass('mejs-unmute').addClass('mejs-mute'); positionVolumeHandle(1); } else { media.setMuted(true); mute.removeClass('mejs-mute').addClass('mejs-unmute'); positionVolumeHandle(0); } }); // listen for volume change events from other sources media.addEventListener('volumechange', function(e) { if (!mouseIsDown) { positionVolumeHandle(e.target.volume); } }, true); // set initial volume positionVolumeHandle(player.options.startVolume); // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements if (media.pluginType === 'native') { media.setVolume(player.options.startVolume); } } })(mejs.$); (function($) { mejs.MediaElementDefaults.forcePluginFullScreen = false; MediaElementPlayer.prototype.isFullScreen = false; MediaElementPlayer.prototype.buildfullscreen = function(player, controls, layers, media) { if (!player.isVideo) return; // native events if (mejs.MediaFeatures.hasNativeFullScreen) { player.container.bind('webkitfullscreenchange', function(e) { if (document.webkitIsFullScreen) { // reset the controls once we are fully in full screen player.setControlsSize(); } else { // when a user presses ESC // make sure to put the player back into place player.exitFullScreen(); } }); } var normalHeight = 0, normalWidth = 0, container = player.container, docElement = document.documentElement, docStyleOverflow, parentWindow = window.parent, parentiframes, iframe, fullscreenBtn = $('<div class="mejs-button mejs-fullscreen-button"><button type="button"></button></div>') .appendTo(controls) .click(function() { var isFullScreen = (mejs.MediaFeatures.hasNativeFullScreen) ? document.webkitIsFullScreen : player.isFullScreen; if (isFullScreen) { player.exitFullScreen(); } else { player.enterFullScreen(); } }); player.enterFullScreen = function() { // firefox can't adjust plugin sizes without resetting :( if (player.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || player.options.forcePluginFullScreen)) { media.setFullscreen(true); //player.isFullScreen = true; return; } // attempt to set fullscreen if (mejs.MediaFeatures.hasNativeFullScreen) { player.container[0].webkitRequestFullScreen(); } // store overflow docStyleOverflow = docElement.style.overflow; // set it to not show scroll bars so 100% will work docElement.style.overflow = 'hidden'; // store normalHeight = player.container.height(); normalWidth = player.container.width(); // make full size container .addClass('mejs-container-fullscreen') .width('100%') .height('100%') .css('z-index', 1000); //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); if (player.pluginType === 'native') { player.$media .width('100%') .height('100%'); } else { container.find('object embed') .width('100%') .height('100%'); player.media.setVideoSize($(window).width(),$(window).height()); } layers.children('div') .width('100%') .height('100%'); fullscreenBtn .removeClass('mejs-fullscreen') .addClass('mejs-unfullscreen'); player.setControlsSize(); player.isFullScreen = true; }; player.exitFullScreen = function() { // firefox can't adjust plugins if (player.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { media.setFullscreen(false); //player.isFullScreen = false; return; } // come outo of native fullscreen if (mejs.MediaFeatures.hasNativeFullScreen && document.webkitIsFullScreen) { document.webkitCancelFullScreen(); } // restore scroll bars to document docElement.style.overflow = docStyleOverflow; container .removeClass('mejs-container-fullscreen') .width(normalWidth) .height(normalHeight) .css('z-index', 1); //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); if (player.pluginType === 'native') { player.$media .width(normalWidth) .height(normalHeight); } else { container.find('object embed') .width(normalWidth) .height(normalHeight); player.media.setVideoSize(normalWidth, normalHeight); } layers.children('div') .width(normalWidth) .height(normalHeight); fullscreenBtn .removeClass('mejs-unfullscreen') .addClass('mejs-fullscreen'); player.setControlsSize(); player.isFullScreen = false; }; $(window).bind('resize',function (e) { player.setControlsSize(); }); $(document).bind('keydown',function (e) { if (player.isFullScreen && e.keyCode == 27) { player.exitFullScreen(); } }); } })(mejs.$); (function($) { // add extra default options $.extend(mejs.MepDefaults, { // this will automatically turn on a <track> startLanguage: '', // a list of languages to auto-translate via Google translations: [], // a dropdownlist of automatic translations translationSelector: false, // key for tranlsations googleApiKey: '' }); $.extend(MediaElementPlayer.prototype, { buildtracks: function(player, controls, layers, media) { if (!player.isVideo) return; if (player.tracks.length == 0) return; var i, options = ''; 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"><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" ></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">None</label>'+ '</li>' + '</ul>'+ '</div>'+ '</button>') .appendTo(controls) // handle clicks to the language radio buttons .delegate('input[type=radio]','click',function() { lang = this.value; if (lang == 'none') { player.selectedTrack = null; } else { for (i=0; i<player.tracks.length; i++) { if (player.tracks[i].srclang == lang) { player.selectedTrack = player.tracks[i]; player.captions.attr('lang', player.selectedTrack.srclang); player.displayCaptions(); break; } } } }); //.bind('mouseenter', function() { // player.captionsButton.find('.mejs-captions-selector').css('visibility','visible') //}); if (!player.options.alwaysShowControls) { // move with controls player.container .bind('mouseenter', function () { // push captions above controls player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); }) .bind('mouseleave', 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 user-defined translations if (player.tracks.length > 0 && player.options.translations.length > 0) { for (i=0; i<player.options.translations.length; i++) { player.tracks.push({ srclang: player.options.translations[i].toLowerCase(), src: null, kind: 'subtitles', entries: [], isLoaded: false, isTranslation: true }); } } // 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].isTranslation); } } player.loadNextTrack(); media.addEventListener('timeupdate',function(e) { player.displayCaptions(); }, false); media.addEventListener('loadedmetadata', function(e) { player.displayChapters(); }, false); player.container.hover( function () { // chapters player.chapters.css('visibility','visible'); player.chapters.fadeIn(200); }, function () { if (!media.paused) { player.chapters.fadeOut(200, function() { $(this).css('visibility','hidden'); $(this).css('display','block'); }); } }); // check for autoplay if (player.node.getAttribute('autoplay') !== null) { player.chapters.css('visibility','hidden'); } // auto selector if (player.options.translationSelector) { for (i in mejs.language.codes) { options += '<option value="' + i + '">' + mejs.language.codes[i] + '</option>'; } player.container.find('.mejs-captions-selector ul').before($( '<select class="mejs-captions-translations">' + '<option value="">--Add Translation--</option>' + options + '</select>' )); // add clicks player.container.find('.mejs-captions-translations').change(function() { var option = $(this); lang = option.val(); // add this language to the tracks list if (lang != '') { player.tracks.push({ srclang: lang, src: null, entries: [], isLoaded: false, isTranslation: true }); if (!player.isLoadingTrack) { player.trackToLoad--; player.addTrackButton(lang,true); player.options.startLanguage = lang; player.loadNextTrack(); } } }); } }, 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; } }, loadTrack: function(index){ var t = this, track = t.tracks[index], after = function() { track.isLoaded = true; // create button //t.addTrackButton(track.srclang); t.enableTrackButton(track.srclang); t.loadNextTrack(); }; if (track.isTranslation) { // translate the first track mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) { // store the new translation track.entries = newOne; after(); }); } else { $.ajax({ url: track.src, success: function(d) { // parse the loaded file track.entries = mejs.TrackFormatParser.parse(d); after(); if (track.kind == 'chapters' && t.media.duration > 0) { t.drawChapters(track); } }, error: function() { t.loadNextTrack(); } }); } }, enableTrackButton: function(lang) { var t = this; t.captionsButton .find('input[value=' + lang + ']') .prop('disabled',false) .siblings('label') .html( mejs.language.codes[lang] || lang ); // auto select if (t.options.startLanguage == lang) { $('#' + t.id + '_captions_' + lang).click(); } t.adjustLanguageBox(); }, addTrackButton: function(lang, isTranslation) { var t = this, l = 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 + '">' + l + ((isTranslation) ? ' (translating)' : ' (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) ); }, 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){ t.captionsText.html(track.entries.text[i]); t.captions.show(); return; // exit out if one is visible; } } t.captions.hide(); } else { t.captions.hide(); } }, 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]); break; } } }, drawChapters: function(chapters) { var t = this, i, dur, //width, //left, percent = 0, usedPercent = 0; t.chapters.empty(); for (i=0; i<chapters.entries.times.length; i++) { dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; percent = Math.floor(dur / t.media.duration * 100); if (percent + usedPercent > 100 || // too large i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in { percent = 100 - usedPercent; } //width = Math.floor(t.width * dur / t.media.duration); //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); //if (left + width > t.width) { // width = t.width - left; //} t.chapters.append( $( '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + '</div>' + '</div>')); usedPercent += percent; } t.chapters.find('div.mejs-chapter').click(function() { t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); if (t.media.paused) { t.media.play(); } }); t.chapters.show(); } }); mejs.language = { codes: { af:'Afrikaans', sq:'Albanian', ar:'Arabic', be:'Belarusian', bg:'Bulgarian', ca:'Catalan', zh:'Chinese', 'zh-cn':'Chinese Simplified', 'zh-tw':'Chinese Traditional', hr:'Croatian', cs:'Czech', da:'Danish', nl:'Dutch', en:'English', et:'Estonian', tl:'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 WebVVT 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 = { pattern_identifier: /^[0-9]+$/, pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}(,[0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}(,[0-9]{3})?)(.*)$/, split2: function (text, regex) { // normal version for compliant browsers // see below for IE fix return text.split(regex); }, parse: function(trackText) { var i = 0, lines = this.split2(trackText, /\r?\n/), entries = {text:[], times:[]}, timecode, text; for(; i<lines.length; i++) { // check for the line number if (this.pattern_identifier.exec(lines[i])){ // skip to the next line where the start --> end time code should be i++; timecode = this.pattern_timecode.exec(lines[i]); if (timecode && i<lines.length){ 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 is in a different array so I can use .join entries.text.push(text); entries.times.push( { start: mejs.Utility.timeCodeToSeconds(timecode[1]), stop: mejs.Utility.timeCodeToSeconds(timecode[3]), settings: timecode[5] }); } } } return entries; }, translateTrackText: function(trackData, fromLang, toLang, googleApiKey, callback) { var entries = {text:[], times:[]}, lines, i this.translateText( trackData.text.join(' <a></a>'), fromLang, toLang, googleApiKey, function(result) { // split on separators lines = result.split('<a></a>'); // create new entries for (i=0;i<trackData.text.length; i++) { // add translated line entries.text[i] = lines[i]; // copy existing times entries.times[i] = { start: trackData.times[i].start, stop: trackData.times[i].stop, settings: trackData.times[i].settings }; } callback(entries); }); }, translateText: function(text, fromLang, toLang, googleApiKey, callback) { var separatorIndex, chunks = [], chunk, maxlength = 1000, result = '', nextChunk= function() { if (chunks.length > 0) { chunk = chunks.shift(); mejs.TrackFormatParser.translateChunk(chunk, fromLang, toLang, googleApiKey, function(r) { if (r != 'undefined') { result += r; } nextChunk(); }); } else { callback(result); } }; // split into chunks while (text.length > 0) { if (text.length > maxlength) { separatorIndex = text.lastIndexOf('.', maxlength); chunks.push(text.substring(0, separatorIndex)); text = text.substring(separatorIndex+1); } else { chunks.push(text); text = ''; } } // start handling the chunks nextChunk(); }, translateChunk: function(text, fromLang, toLang, googleApiKey, callback) { var data = { q: text, langpair: fromLang + '|' + toLang, v: '1.0' }; if (googleApiKey !== '' && googleApiKey !== null) { data.key = googleApiKey; } $.ajax({ url: 'https://ajax.googleapis.com/ajax/services/language/translate', // 'https://www.google.com/uds/Gtranslate', //'https://ajax.googleapis.com/ajax/services/language/translate', // data: data, type: 'GET', dataType: 'jsonp', success: function(d) { callback(d.responseData.translatedText); }, error: function(e) { callback(null); } }); } }; // 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.$);