5806 lines
No EOL
159 KiB
JavaScript
Executable file
5806 lines
No EOL
159 KiB
JavaScript
Executable file
/*!
|
|
*
|
|
* 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.21.2';
|
|
|
|
|
|
// 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);
|
|
}
|
|
},
|
|
determineScheme: function(url) {
|
|
if (url && url.indexOf("://") != -1) {
|
|
return url.substr(0, url.indexOf("://")+3);
|
|
}
|
|
return "//"; // let user agent figure this out
|
|
}
|
|
};
|
|
|
|
|
|
// 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') {
|
|
if( this.pluginApi.getPlayerState() == 1 ) {
|
|
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);
|
|
}
|
|
};
|
|
|
|
/*
|
|
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) : '';
|
|
playback.scheme = mejs.Utility.determineScheme(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'), scheme: ''},
|
|
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;
|
|
|
|
// 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]);
|
|
}
|
|
|
|
if (playback.method === 'flash' || playback.method === 'silverlight') {
|
|
|
|
// flash/silverlight vars
|
|
initVars = [
|
|
'id=' + pluginid,
|
|
'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);
|
|
}
|
|
|
|
// call from plugin
|
|
window[pluginid + '_init'] = function() {
|
|
switch (pluginMediaElement.pluginType) {
|
|
case 'flash':
|
|
pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(pluginid);
|
|
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);
|
|
}
|
|
}
|
|
|
|
// event call from plugin
|
|
window[pluginid + '_event'] = function(eventName, values) {
|
|
|
|
var
|
|
e,
|
|
i,
|
|
bufferedTime;
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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 + '?' + (new Date().getTime()) + '" />' +
|
|
'<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,
|
|
scheme: playback.scheme
|
|
};
|
|
|
|
// 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="' + playback.scheme + '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]),
|
|
playerState = -1;
|
|
|
|
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;
|
|
}
|
|
}
|
|
// parity with YT player
|
|
player.getPlayerState = function() {
|
|
return playerState;
|
|
}
|
|
|
|
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() {
|
|
playerState = 1;
|
|
createEvent(player, pluginMediaElement, 'play');
|
|
createEvent(player, pluginMediaElement, 'playing');
|
|
});
|
|
|
|
player.addEvent('pause', function() {
|
|
playerState = 2;
|
|
createEvent(player, pluginMediaElement, 'pause');
|
|
});
|
|
|
|
player.addEvent('finish', function() {
|
|
playerState = 0;
|
|
createEvent(player, pluginMediaElement, 'ended');
|
|
});
|
|
|
|
player.addEvent('playProgress', function(e) {
|
|
createEvent(player, pluginMediaElement, 'timeupdate', e);
|
|
});
|
|
|
|
player.addEvent('seek', function(e) {
|
|
playerState = 3;
|
|
createEvent(player, pluginMediaElement, 'seeked', e);
|
|
});
|
|
|
|
player.addEvent('loadProgress', function(e) {
|
|
playerState = 3;
|
|
createEvent(player, pluginMediaElement, 'progress', e);
|
|
});
|
|
|
|
pluginMediaElement.pluginElement = container;
|
|
pluginMediaElement.pluginApi = player;
|
|
|
|
pluginMediaElement.success(pluginMediaElement, pluginMediaElement.pluginElement);
|
|
});
|
|
}
|
|
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');
|
|
|
|
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(yt) {
|
|
if (!this.isIframeStarted) {
|
|
var tag = document.createElement('script');
|
|
tag.src = yt.scheme + "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(yt);
|
|
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
|
|
pluginMediaElement.success(pluginMediaElement, pluginMediaElement.pluginElement);
|
|
|
|
// 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="' + settings.scheme + '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 = settings.scheme + '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="' + settings.scheme + '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;
|
|
|
|
settings.success(pluginMediaElement, pluginMediaElement.pluginElement);
|
|
|
|
// 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);
|
|
|
|
// make sure to fill in and resize the controls (e.g., 00:00 => 01:13:15
|
|
if (t.updateDuration) {
|
|
t.updateDuration();
|
|
}
|
|
if (t.updateCurrent) {
|
|
t.updateCurrent();
|
|
}
|
|
t.setControlsSize();
|
|
|
|
}
|
|
}, false);
|
|
|
|
t.container.focusout(function (e) {
|
|
if( e.relatedTarget ) { //FF is working on supporting focusout https://bugzilla.mozilla.org/show_bug.cgi?id=687787
|
|
var $target = $(e.relatedTarget);
|
|
if (t.keyboardAction && $target.parents('.mejs-container').length === 0) {
|
|
t.keyboardAction = false;
|
|
t.hideControls(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
// webkit has trouble doing this without a delay
|
|
setTimeout(function () {
|
|
t.setPlayerSize(t.width, t.height);
|
|
t.setControlsSize();
|
|
}, 50);
|
|
|
|
// adjust controls whenever window sizes (used to be in fullscreen only)
|
|
t.globalBind('resize', function() {
|
|
|
|
// don't resize for fullscreen mode
|
|
if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) {
|
|
t.setPlayerSize(t.width, t.height);
|
|
}
|
|
|
|
// always adjust controls
|
|
t.setControlsSize();
|
|
});
|
|
|
|
// This is a work-around for a bug in the YouTube iFrame player, which means
|
|
// we can't use the play() API for the initial playback on iOS or Android;
|
|
// user has to start playback directly by tapping on the iFrame.
|
|
if (t.media.pluginType == 'youtube' && ( mf.isiOS || mf.isAndroid ) ) {
|
|
t.container.find('.mejs-overlay-play').hide();
|
|
t.container.find('.mejs-poster').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.toFixed(2) > 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
|
|
&& $(event.target).closest('.mejs-container').attr('id') === player.$media.closest('.mejs-container').attr('id');
|
|
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, e);
|
|
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;
|
|
}
|
|
|
|
console.log('x is', x)
|
|
|
|
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;
|
|
|
|
console.log('percentage is', percentage);
|
|
|
|
console.log('new time is', newTime);
|
|
|
|
// 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,
|
|
seekForward = player.options.defaultSeekForwardInterval(duration),
|
|
seekBackward = player.options.defaultSeekBackwardInterval(duration);
|
|
|
|
switch (keyCode) {
|
|
case 37: // left
|
|
case 40: // Down
|
|
seekTime -= seekBackward;
|
|
break;
|
|
case 39: // Right
|
|
case 38: // Up
|
|
seekTime += seekForward;
|
|
break;
|
|
case 36: // Home
|
|
seekTime = 0;
|
|
break;
|
|
case 35: // end
|
|
seekTime = duration;
|
|
break;
|
|
case 32: // space
|
|
case 13: // enter
|
|
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('Volume Slider'),
|
|
'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 = Math.min(volume + 0.1, 1);
|
|
break;
|
|
case 40: // Down
|
|
volume = Math.max(0, 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 percentErrorMargin = 0.002, // 0.2%
|
|
windowWidth = $(window).width(),
|
|
screenWidth = screen.width,
|
|
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.$); |