PageSourceSearch

https://lenobl.ru/static/js/libs/mediajs/mediaelement-and-player.661d0c117aa4.js

js lenobl.ru collected 2026-09-24 08:26:53 UTC 189,295 bytes, 6,655 lines download raw bytes

1/*!
2 *
3 * MediaElement.js
4 * HTML5 <video> and <audio> shim and player
5 * http://mediaelementjs.com/
6 *
7 * Creates a JavaScript object that mimics HTML5 MediaElement API
8 * for browsers that don't understand HTML5 or can't play the provided codec
9 * Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3
10 *
11 * Copyright 2010-2014, John Dyer (http://j.hn)
12 * License: MIT
13 *
14 */
15// Namespace
16var mejs = mejs || {};
17
18// version number
19mejs.version = '2.23.4';
20
21
22// player number (for missing, same id attr)
23mejs.meIndex = 0;
24
25// media types accepted by plugins
26mejs.plugins = {
27	silverlight: [
28		{version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']}
29	],
30	flash: [
31		{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/mp4', 'audio/mpeg', 'video/dailymotion', 'video/x-dailymotion', 'application/x-mpegURL', 'audio/ogg']}
32		// 'video/youtube', 'video/x-youtube', 
33		// ,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!)
34	],
35	youtube: [
36		{version: null, types: ['video/youtube', 'video/x-youtube', 'audio/youtube', 'audio/x-youtube']}
37	],
38	vimeo: [
39		{version: null, types: ['video/vimeo', 'video/x-vimeo']}
40	]
41};
42
43/*
44Utility methods
45*/
46mejs.Utility = {
47	encodeUrl: function(url) {
48		return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26');
49	},
50	escapeHTML: function(s) {
51		return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
52	},
53	absolutizeUrl: function(url) {
54		var el = document.createElement('div');
55		el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>';
56		return el.firstChild.href;
57	},
58	getScriptPath: function(scriptNames) {
59		var
60			i = 0,
61			j,
62			codePath = '',
63			testname = '',
64			slashPos,
65			filenamePos,
66			scriptUrl,
67			scriptPath,			
68			scriptFilename,
69			scripts = document.getElementsByTagName('script'),
70			il = scripts.length,
71			jl = scriptNames.length;
72			
73		// go through all <script> tags
74		for (; i < il; i++) {
75			scriptUrl = scripts[i].src;
76			slashPos = scriptUrl.lastIndexOf('/');
77			if (slashPos > -1) {
78				scriptFilename = scriptUrl.substring(slashPos + 1);
79				scriptPath = scriptUrl.substring(0, slashPos + 1);
80			} else {
81				scriptFilename = scriptUrl;
82				scriptPath = '';			
83			}
84			
85			// see if any <script> tags have a file name that matches the 
86			for (j = 0; j < jl; j++) {
87				testname = scriptNames[j];
88				filenamePos = scriptFilename.indexOf(testname);
89				if (filenamePos > -1) {
90					codePath = scriptPath;
91					break;
92				}
93			}
94			
95			// if we found a path, then break and return it
96			if (codePath !== '') {
97				break;
98			}
99		}
100		
101		// send the best path back
102		return codePath;
103	},
104	/*
105	 * Calculate the time format to use. We have a default format set in the
106	 * options but it can be imcomplete. We ajust it according to the media
107	 * duration.
108	 *
109	 * We support format like 'hh:mm:ss:ff'.
110	 */
111	calculateTimeFormat: function(time, options, fps) {
112		if (time < 0) {
113			time = 0;
114		}
115
116		if(typeof fps == 'undefined') {
117		    fps = 25;
118		}
119
120		var format = options.timeFormat,
121			firstChar = format[0],
122			firstTwoPlaces = (format[1] == format[0]),
123			separatorIndex = firstTwoPlaces? 2: 1,
124			separator = ':',
125			hours = Math.floor(time / 3600) % 24,
126			minutes = Math.floor(time / 60) % 60,
127			seconds = Math.floor(time % 60),
128			frames = Math.floor(((time % 1)*fps).toFixed(3)),
129			lis = [
130				[frames, 'f'],
131				[seconds, 's'],
132				[minutes, 'm'],
133				[hours, 'h']
134			];
135
136		// Try to get the separator from the format
137		if (format.length < separatorIndex) {
138			separator = format[separatorIndex];
139		}
140
141		var required = false;
142
143		for (var i=0, len=lis.length; i < len; i++) {
144			if (format.indexOf(lis[i][1]) !== -1) {
145				required=true;
146			}
147			else if (required) {
148				var hasNextValue = false;
149				for (var j=i; j < len; j++) {
150					if (lis[j][0] > 0) {
151						hasNextValue = true;
152						break;
153					}
154				}
155
156				if (! hasNextValue) {
157					break;
158				}
159
160				if (!firstTwoPlaces) {
161					format = firstChar + format;
162				}
163				format = lis[i][1] + separator + format;
164				if (firstTwoPlaces) {
165					format = lis[i][1] + format;
166				}
167				firstChar = lis[i][1];
168			}
169		}
170		options.currentTimeFormat = format;
171	},
172	/*
173	 * Prefix the given number by zero if it is lower than 10.
174	 */
175	twoDigitsString: function(n) {
176		if (n < 10) {
177			return '0' + n;
178		}
179		return String(n);
180	},
181	secondsToTimeCode: function(time, options) {
182		if (time < 0) {
183			time = 0;
184		}
185
186		// Maintain backward compatibility with method signature before v2.18.
187		if (typeof options !== 'object') {
188			var format = 'm:ss';
189			format = arguments[1] ? 'hh:mm:ss' : format; // forceHours
190			format = arguments[2] ? format + ':ff' : format; // showFrameCount
191
192			options = {
193				currentTimeFormat: format,
194				framesPerSecond: arguments[3] || 25
195			};
196		}
197
198		var fps = options.framesPerSecond;
199		if(typeof fps === 'undefined') {
200			fps = 25;
201		}
202
203		var format = options.currentTimeFormat,
204			hours = Math.floor(time / 3600) % 24,
205			minutes = Math.floor(time / 60) % 60,
206			seconds = Math.floor(time % 60),
207			frames = Math.floor(((time % 1)*fps).toFixed(3));
208			lis = [
209				[frames, 'f'],
210				[seconds, 's'],
211				[minutes, 'm'],
212				[hours, 'h']
213			];
214
215		var res = format;
216		for (i=0,len=lis.length; i < len; i++) {
217			res = res.replace(lis[i][1]+lis[i][1], this.twoDigitsString(lis[i][0]));
218			res = res.replace(lis[i][1], lis[i][0]);
219		}
220		return res;
221	},
222	
223	timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){
224		if (typeof showFrameCount == 'undefined') {
225		    showFrameCount=false;
226		} else if(typeof fps == 'undefined') {
227		    fps = 25;
228		}
229	
230		var tc_array = hh_mm_ss_ff.split(":"),
231			tc_hh = parseInt(tc_array[0], 10),
232			tc_mm = parseInt(tc_array[1], 10),
233			tc_ss = parseInt(tc_array[2], 10),
234			tc_ff = 0,
235			tc_in_seconds = 0;
236		
237		if (showFrameCount) {
238		    tc_ff = parseInt(tc_array[3])/fps;
239		}
240		
241		tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff;
242		
243		return tc_in_seconds;
244	},
245	
246
247	convertSMPTEtoSeconds: function (SMPTE) {
248		if (typeof SMPTE != 'string') 
249			return false;
250
251		SMPTE = SMPTE.replace(',', '.');
252		
253		var secs = 0,
254			decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0,
255			multiplier = 1;
256		
257		SMPTE = SMPTE.split(':').reverse();
258		
259		for (var i = 0; i < SMPTE.length; i++) {
260			multiplier = 1;
261			if (i > 0) {
262				multiplier = Math.pow(60, i); 
263			}
264			secs += Number(SMPTE[i]) * multiplier;
265		}
266		return Number(secs.toFixed(decimalLen));
267	},	
268	
269	/* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */
270	removeSwf: function(id) {
271		var obj = document.getElementById(id);
272		if (obj && /object|embed/i.test(obj.nodeName)) {
273			if (mejs.MediaFeatures.isIE) {
274				obj.style.display = "none";
275				(function(){
276					if (obj.readyState == 4) {
277						mejs.Utility.removeObjectInIE(id);
278					} else {
279						setTimeout(arguments.callee, 10);
280					}
281				})();
282			} else {
283				obj.parentNode.removeChild(obj);
284			}
285		}
286	},
287	removeObjectInIE: function(id) {
288		var obj = document.getElementById(id);
289		if (obj) {
290			for (var i in obj) {
291				if (typeof obj[i] == "function") {
292					obj[i] = null;
293				}
294			}
295			obj.parentNode.removeChild(obj);
296		}		
297	},
298    determineScheme: function(url) {
299        if (url && url.indexOf("://") != -1) {
300            return url.substr(0, url.indexOf("://")+3);
301        }
302        return "//"; // let user agent figure this out
303    },
304
305	// taken from underscore
306	debounce: function(func, wait, immediate) {
307		var timeout;
308		return function() {
309			var context = this, args = arguments;
310			var later = function() {
311				timeout = null;
312				if (!immediate) func.apply(context, args);
313			};
314			var callNow = immediate && !timeout;
315			clearTimeout(timeout);
316			timeout = setTimeout(later, wait);
317			if (callNow) func.apply(context, args);
318		};
319	},
320
321	/**
322	* Returns true if targetNode appears after sourceNode in the dom.
323	* @param {HTMLElement} sourceNode - the source node for comparison
324	* @param {HTMLElement} targetNode - the node to compare against sourceNode
325	*/
326	isNodeAfter: function(sourceNode, targetNode) {
327		return !!(
328			sourceNode &&
329			targetNode &&
330			typeof sourceNode.compareDocumentPosition === 'function' &&
331			sourceNode.compareDocumentPosition(targetNode) & Node.DOCUMENT_POSITION_PRECEDING
332		);
333	}
334};
335
336
337// Core detector, plugins are added below
338mejs.PluginDetector = {
339
340	// main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]);
341	hasPluginVersion: function(plugin, v) {
342		var pv = this.plugins[plugin];
343		v[1] = v[1] || 0;
344		v[2] = v[2] || 0;
345		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;
346	},
347
348	// cached values
349	nav: window.navigator,
350	ua: window.navigator.userAgent.toLowerCase(),
351
352	// stored version numbers
353	plugins: [],
354
355	// runs detectPlugin() and stores the version number
356	addPlugin: function(p, pluginName, mimeType, activeX, axDetect) {
357		this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect);
358	},
359
360	// get the version number from the mimetype (all but IE) or ActiveX (IE)
361	detectPlugin: function(pluginName, mimeType, activeX, axDetect) {
362
363		var version = [0,0,0],
364			description,
365			i,
366			ax;
367
368		// Firefox, Webkit, Opera
369		if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') {
370			description = this.nav.plugins[pluginName].description;
371			if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) {
372				version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.');
373				for (i=0; i<version.length; i++) {
374					version[i] = parseInt(version[i].match(/\d+/), 10);
375				}
376			}
377		// Internet Explorer / ActiveX
378		} else if (typeof(window.ActiveXObject) != 'undefined') {
379			try {
380				ax = new ActiveXObject(activeX);
381				if (ax) {
382					version = axDetect(ax);
383				}
384			}
385			catch (e) { }
386		}
387		return version;
388	}
389};
390
391// Add Flash detection
392mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) {
393	// adapted from SWFObject
394	var version = [],
395		d = ax.GetVariable("$version");
396	if (d) {
397		d = d.split(" ")[1].split(",");
398		version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
399	}
400	return version;
401});
402
403// Add Silverlight detection
404mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) {
405	// Silverlight cannot report its version number to IE
406	// but it does have a isVersionSupported function, so we have to loop through it to get a version number.
407	// adapted from http://www.silverlightversion.com/
408	var v = [0,0,0,0],
409		loopMatch = function(ax, v, i, n) {
410			while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){
411				v[i]+=n;
412			}
413			v[i] -= n;
414		};
415	loopMatch(ax, v, 0, 1);
416	loopMatch(ax, v, 1, 1);
417	loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx)
418	loopMatch(ax, v, 2, 1000);
419	loopMatch(ax, v, 2, 100);
420	loopMatch(ax, v, 2, 10);
421	loopMatch(ax, v, 2, 1);
422	loopMatch(ax, v, 3, 1);
423
424	return v;
425});
426// add adobe acrobat
427/*
428PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) {
429	var version = [],
430		d = ax.GetVersions().split(',')[0].split('=')[1].split('.');
431
432	if (d) {
433		version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
434	}
435	return version;
436});
437*/
438// necessary detection (fixes for <IE9)
439mejs.MediaFeatures = {
440	init: function() {
441		var
442			t = this,
443			d = document,
444			nav = mejs.PluginDetector.nav,
445			ua = mejs.PluginDetector.ua.toLowerCase(),
446			i,
447			v,
448			html5Elements = ['source','track','audio','video'];
449
450		// detect browsers (only the ones that have some kind of quirk we need to work around)
451		t.isiPad = (ua.match(/ipad/i) !== null);
452		t.isiPhone = (ua.match(/iphone/i) !== null);
453		t.isiOS = t.isiPhone || t.isiPad;
454		t.isAndroid = (ua.match(/android/i) !== null);
455		t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null);
456		t.isBustedNativeHTTPS = (location.protocol === 'https:' && (ua.match(/android [12]\./) !== null || ua.match(/macintosh.* version.* safari/) !== null));
457		t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1 || nav.appName.toLowerCase().match(/trident/gi) !== null);
458		t.isChrome = (ua.match(/chrome/gi) !== null);
459		t.isChromium = (ua.match(/chromium/gi) !== null);
460		t.isFirefox = (ua.match(/firefox/gi) !== null);
461		t.isWebkit = (ua.match(/webkit/gi) !== null);
462		t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit && !t.isIE;
463		t.isOpera = (ua.match(/opera/gi) !== null);
464		t.hasTouch = ('ontouchstart' in window); //  && window.ontouchstart != null); // this breaks iOS 7
465
466		// Borrowed from `Modernizr.svgasimg`, sources:
467		// - https://github.com/Modernizr/Modernizr/issues/687
468		// - https://github.com/Modernizr/Modernizr/pull/1209/files
469		t.svgAsImg = !!document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
470
471		// create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection
472		for (i=0; i<html5Elements.length; i++) {
473			v = document.createElement(html5Elements[i]);
474		}
475
476		t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid);
477
478		// Fix for IE9 on Windows 7N / Windows 7KN (Media Player not installer)
479		try{
480			v.canPlayType("video/mp4");
481		}catch(e){
482			t.supportsMediaTag = false;
483		}
484
485		t.supportsPointerEvents = (function() {
486			// TAKEN FROM MODERNIZR
487			var element = document.createElement('x'),
488				documentElement = document.documentElement,
489				getComputedStyle = window.getComputedStyle,
490				supports;
491			if(!('pointerEvents' in element.style)){
492				return false;
493			}
494			element.style.pointerEvents = 'auto';
495			element.style.pointerEvents = 'x';
496			documentElement.appendChild(element);
497			supports = getComputedStyle &&
498				getComputedStyle(element, '').pointerEvents === 'auto';
499			documentElement.removeChild(element);
500			return !!supports;
501		})();
502
503
504		 // Older versions of Firefox can't move plugins around without it resetting,
505		t.hasFirefoxPluginMovingProblem = false;
506
507		// detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails)
508
509		// iOS
510		t.hasiOSFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined');
511
512		// W3C
513		t.hasNativeFullscreen = (typeof v.requestFullscreen !== 'undefined');
514
515		// webkit/firefox/IE11+
516		t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined');
517		t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined');
518		t.hasMsNativeFullScreen = (typeof v.msRequestFullscreen !== 'undefined');
519
520		t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen || t.hasMsNativeFullScreen);
521		t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen;
522
523		// Enabled?
524		if (t.hasMozNativeFullScreen) {
525			t.nativeFullScreenEnabled = document.mozFullScreenEnabled;
526		} else if (t.hasMsNativeFullScreen) {
527			t.nativeFullScreenEnabled = document.msFullscreenEnabled;
528		}
529
530		if (t.isChrome) {
531			t.hasiOSFullScreen = false;
532		}
533
534		if (t.hasTrueNativeFullScreen) {
535
536			t.fullScreenEventName = '';
537			if (t.hasWebkitNativeFullScreen) {
538				t.fullScreenEventName = 'webkitfullscreenchange';
539
540			} else if (t.hasMozNativeFullScreen) {
541				t.fullScreenEventName = 'mozfullscreenchange';
542
543			} else if (t.hasMsNativeFullScreen) {
544				t.fullScreenEventName = 'MSFullscreenChange';
545			}
546
547			t.isFullScreen = function() {
548				if (t.hasMozNativeFullScreen) {
549					return d.mozFullScreen;
550
551				} else if (t.hasWebkitNativeFullScreen) {
552					return d.webkitIsFullScreen;
553
554				} else if (t.hasMsNativeFullScreen) {
555					return d.msFullscreenElement !== null;
556				}
557			}
558
559			t.requestFullScreen = function(el) {
560
561				if (t.hasWebkitNativeFullScreen) {
562					el.webkitRequestFullScreen();
563
564				} else if (t.hasMozNativeFullScreen) {
565					el.mozRequestFullScreen();
566
567				} else if (t.hasMsNativeFullScreen) {
568					el.msRequestFullscreen();
569
570				}
571			}
572
573			t.cancelFullScreen = function() {
574				if (t.hasWebkitNativeFullScreen) {
575					document.webkitCancelFullScreen();
576
577				} else if (t.hasMozNativeFullScreen) {
578					document.mozCancelFullScreen();
579
580				} else if (t.hasMsNativeFullScreen) {
581					document.msExitFullscreen();
582
583				}
584			}
585
586		}
587
588
589		// OS X 10.5 can't do this even if it says it can :(
590		if (t.hasiOSFullScreen && ua.match(/mac os x 10_5/i)) {
591			t.hasNativeFullScreen = false;
592			t.hasiOSFullScreen = false;
593		}
594
595	}
596};
597mejs.MediaFeatures.init();
598
599/*
600extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below)
601*/
602mejs.HtmlMediaElement = {
603	pluginType: 'native',
604	isFullScreen: false,
605
606	setCurrentTime: function (time) {
607		this.currentTime = time;
608	},
609
610	setMuted: function (muted) {
611		this.muted = muted;
612	},
613
614	setVolume: function (volume) {
615		this.volume = volume;
616	},
617
618	// for parity with the plugin versions
619	stop: function () {
620		this.pause();
621	},
622
623	// This can be a url string
624	// or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
625	setSrc: function (url) {
626		
627		// Fix for IE9 which can't set .src when there are <source> elements. Awesome, right?
628		var 
629			existingSources = this.getElementsByTagName('source');
630		while (existingSources.length > 0){
631			this.removeChild(existingSources[0]);
632		}
633	
634		if (typeof url == 'string') {
635			this.src = url;
636		} else {
637			var i, media;
638
639			for (i=0; i<url.length; i++) {
640				media = url[i];
641				if (this.canPlayType(media.type)) {
642					this.src = media.src;
643					break;
644				}
645			}
646		}
647	},
648
649	setVideoSize: function (width, height) {
650		this.width = width;
651		this.height = height;
652	}
653};
654
655/*
656Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember]
657*/
658mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) {
659	this.id = pluginid;
660	this.pluginType = pluginType;
661	this.src = mediaUrl;
662	this.events = {};
663	this.attributes = {};
664};
665
666// JavaScript values and ExternalInterface methods that match HTML5 video properties methods
667// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html
668// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
669mejs.PluginMediaElement.prototype = {
670
671	// special
672	pluginElement: null,
673	pluginType: '',
674	isFullScreen: false,
675
676	// not implemented :(
677	playbackRate: -1,
678	defaultPlaybackRate: -1,
679	seekable: [],
680	played: [],
681
682	// HTML5 read-only properties
683	paused: true,
684	ended: false,
685	seeking: false,
686	duration: 0,
687	error: null,
688	tagName: '',
689
690	// HTML5 get/set properties, but only set (updated by event handlers)
691	muted: false,
692	volume: 1,
693	currentTime: 0,
694
695	// HTML5 methods
696	play: function () {
697		if (this.pluginApi != null) {
698			if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') {
699				this.pluginApi.playVideo();
700			} else {
701				this.pluginApi.playMedia();
702			}
703			this.paused = false;
704		}
705	},
706	load: function () {
707		if (this.pluginApi != null) {
708			if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') {
709			} else {
710				this.pluginApi.loadMedia();
711			}
712			
713			this.paused = false;
714		}
715	},
716	pause: function () {
717		if (this.pluginApi != null) {
718			if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') {
719		        if( this.pluginApi.getPlayerState() == 1 ) {
720				    this.pluginApi.pauseVideo();
721                }
722			} else {
723				this.pluginApi.pauseMedia();
724			}			
725			
726			
727			this.paused = true;
728		}
729	},
730	stop: function () {
731		if (this.pluginApi != null) {
732			if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') {
733				this.pluginApi.stopVideo();
734			} else {
735				this.pluginApi.stopMedia();
736			}	
737			this.paused = true;
738		}
739	},
740	canPlayType: function(type) {
741		var i,
742			j,
743			pluginInfo,
744			pluginVersions = mejs.plugins[this.pluginType];
745
746		for (i=0; i<pluginVersions.length; i++) {
747			pluginInfo = pluginVersions[i];
748
749			// test if user has the correct plugin version
750			if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) {
751
752				// test for plugin playback types
753				for (j=0; j<pluginInfo.types.length; j++) {
754					// find plugin that can play the type
755					if (type == pluginInfo.types[j]) {
756						return 'probably';
757					}
758				}
759			}
760		}
761
762		return '';
763	},
764	
765	positionFullscreenButton: function(x,y,visibleAndAbove) {
766		if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) {
767			this.pluginApi.positionFullscreenButton(Math.floor(x),Math.floor(y),visibleAndAbove);
768		}
769	},
770	
771	hideFullscreenButton: function() {
772		if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) {
773			this.pluginApi.hideFullscreenButton();
774		}		
775	},	
776	
777
778	// custom methods since not all JavaScript implementations supp
778ort get/set
779
780	// This can be a url string
781	// or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
782	setSrc: function (url) {
783		if (typeof url == 'string') {
784			this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url));
785			this.src = mejs.Utility.absolutizeUrl(url);
786		} else {
787			var i, media;
788
789			for (i=0; i<url.length; i++) {
790				media = url[i];
791				if (this.canPlayType(media.type)) {
792					this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src));
793					this.src = mejs.Utility.absolutizeUrl(media.src);
794					break;
795				}
796			}
797		}
798
799	},
800	setCurrentTime: function (time) {
801		if (this.pluginApi != null) {
802			if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') {
803				this.pluginApi.seekTo(time);
804			} else {
805				this.pluginApi.setCurrentTime(time);
806			}				
807			
808			
809			
810			this.currentTime = time;
811		}
812	},
813	setVolume: function (volume) {
814		if (this.pluginApi != null) {
815			// same on YouTube and MEjs
816			if (this.pluginType == 'youtube') {
817				this.pluginApi.setVolume(volume * 100);
818			} else {
819				this.pluginApi.setVolume(volume);
820			}
821			this.volume = volume;
822		}
823	},
824	setMuted: function (muted) {
825		if (this.pluginApi != null) {
826			if (this.pluginType == 'youtube') {
827				if (muted) {
828					this.pluginApi.mute();
829				} else {
830					this.pluginApi.unMute();
831				}
832				this.muted = muted;
833				this.dispatchEvent({type:'volumechange'});
834			} else {
835				this.pluginApi.setMuted(muted);
836			}
837			this.muted = muted;
838		}
839	},
840
841	// additional non-HTML5 methods
842	setVideoSize: function (width, height) {
843		
844		//if (this.pluginType == 'flash' || this.pluginType == 'silverlight') {
845			if (this.pluginElement && this.pluginElement.style) {
846				this.pluginElement.style.width = width + 'px';
847				this.pluginElement.style.height = height + 'px';
848			}
849			if (this.pluginApi != null && this.pluginApi.setVideoSize) {
850				this.pluginApi.setVideoSize(width, height);
851			}
852		//}
853	},
854
855	setFullscreen: function (fullscreen) {
856		if (this.pluginApi != null && this.pluginApi.setFullscreen) {
857			this.pluginApi.setFullscreen(fullscreen);
858		}
859	},
860	
861	enterFullScreen: function() {
862		if (this.pluginApi != null && this.pluginApi.setFullscreen) {
863			this.setFullscreen(true);
864		}		
865		
866	},
867	
868	exitFullScreen: function() {
869		if (this.pluginApi != null && this.pluginApi.setFullscreen) {
870			this.setFullscreen(false);
871		}
872	},	
873
874	// start: fake events
875	addEventListener: function (eventName, callback, bubble) {
876		this.events[eventName] = this.events[eventName] || [];
877		this.events[eventName].push(callback);
878	},
879	removeEventListener: function (eventName, callback) {
880		if (!eventName) { this.events = {}; return true; }
881		var callbacks = this.events[eventName];
882		if (!callbacks) return true;
883		if (!callback) { this.events[eventName] = []; return true; }
884		for (var i = 0; i < callbacks.length; i++) {
885			if (callbacks[i] === callback) {
886				this.events[eventName].splice(i, 1);
887				return true;
888			}
889		}
890		return false;
891	},	
892	dispatchEvent: function (event) {
893		var i,
894			args,
895			callbacks = this.events[event.type];
896
897		if (callbacks) {
898			for (i = 0; i < callbacks.length; i++) {
899				callbacks[i].apply(this, [event]);
900			}
901		}
902	},
903	// end: fake events
904	
905	// fake DOM attribute methods
906	hasAttribute: function(name){
907		return (name in this.attributes);  
908	},
909	removeAttribute: function(name){
910		delete this.attributes[name];
911	},
912	getAttribute: function(name){
913		if (this.hasAttribute(name)) {
914			return this.attributes[name];
915		}
916		return null;
917	},
918	setAttribute: function(name, value){
919		this.attributes[name] = value;
920	},
921
922	remove: function() {
923		mejs.Utility.removeSwf(this.pluginElement.id);
924	}
925};
926
927/*
928Default options
929*/
930mejs.MediaElementDefaults = {
931	// allows testing on HTML5, flash, silverlight
932	// auto: attempts to detect what the browser can do
933	// auto_plugin: prefer plugins and then attempt native HTML5
934	// native: forces HTML5 playback
935	// shim: disallows HTML5, will attempt either Flash or Silverlight
936	// none: forces fallback view
937	mode: 'auto',
938	// remove or reorder to change plugin priority and availability
939	plugins: ['flash','silverlight','youtube','vimeo'],
940	// shows debug errors on screen
941	enablePluginDebug: false,
942	// use plugin for browsers that have trouble with Basic Authentication on HTTPS sites
943	httpsBasicAuthSite: false,
944	// overrides the type specified, useful for dynamic instantiation
945	type: '',
946	// path to Flash and Silverlight plugins
947	pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
948	// name of flash file
949	flashName: 'flashmediaelement.swf',
950	// streamer for RTMP streaming
951	flashStreamer: '',
952	// set to 'always' for CDN version
953	flashScriptAccess: 'sameDomain',	
954	// turns on the smoothing filter in Flash
955	enablePluginSmoothing: false,
956	// enabled pseudo-streaming (seek) on .mp4 files
957	enablePseudoStreaming: false,
958	// start query parameter sent to server for pseudo-streaming
959	pseudoStreamingStartQueryParam: 'start',
960	// name of silverlight file
961	silverlightName: 'silverlightmediaelement.xap',
962	// default if the <video width> is not specified
963	defaultVideoWidth: 480,
964	// default if the <video height> is not specified
965	defaultVideoHeight: 270,
966	// overrides <video width>
967	pluginWidth: -1,
968	// overrides <video height>
969	pluginHeight: -1,
970	// additional plugin variables in 'key=value' form
971	pluginVars: [],	
972	// rate in milliseconds for Flash and Silverlight to fire the timeupdate event
973	// larger number is less accurate, but less strain on plugin->JavaScript bridge
974	timerRate: 250,
975	// initial volume for player
976	startVolume: 0.8,
977	// custom error message in case media cannot be played; otherwise, Download File
978	// link will be displayed
979	customError: "",
980	success: function () { },
981	error: function () { }
982};
983
984/*
985Determines if a browser supports the <video> or <audio> element
986and returns either the native element or a Flash/Silverlight version that
987mimics HTML5 MediaElement
988*/
989mejs.MediaElement = function (el, o) {
990	return mejs.HtmlMediaElementShim.create(el,o);
991};
992
993mejs.HtmlMediaElementShim = {
994
995	create: function(el, o) {
996		var
997			options = {},
998			htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
999			tagName = htmlMediaElement.tagName.toLowerCase(),
1000			isMediaTag = (tagName === 'audio' || tagName === 'video'),
1001			src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'),
1002			poster = htmlMediaElement.getAttribute('poster'),
1003			autoplay =  htmlMediaElement.getAttribute('autoplay'),
1004			preload =  htmlMediaElement.getAttribute('preload'),
1005			controls =  htmlMediaElement.getAttribute('controls'),
1006			playback,
1007			prop;
1008
1009		// extend options
1010		for (prop in mejs.MediaElementDefaults) {
1011			options[prop] = mejs.MediaElementDefaults[prop];
1012		}
1013		for (prop in o) {
1014			options[prop] = o[prop];
1015		}		
1016		
1017
1018		// clean up attributes
1019		src = 		(typeof src == 'undefined' 	|| src === null || src == '') ? null : src;		
1020		poster =	(typeof poster == 'undefined' 	|| poster === null) ? '' : poster;
1021		preload = 	(typeof preload == 'undefined' 	|| preload === null || preload === 'false') ? 'none' : preload;
1022		autoplay = 	!(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false');
1023		controls = 	!(typeof controls == 'undefined' || controls === null || controls === 'false');
1024
1025		// test for HTML5 and plugin capabilities
1026		playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src);
1027		playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '';
1028        	playback.scheme = mejs.Utility.determineScheme(playback.url);
1029
1030		if (playback.method == 'native') {
1031			// second fix for android
1032			if (mejs.MediaFeatures.isBustedAndroid) {
1033				htmlMediaElement.src = playback.url;
1034				htmlMediaElement.addEventListener('click', function() {
1035					htmlMediaElement.play();
1036				}, false);
1037			}
1038		
1039			// add methods to native HTMLMediaElement
1040			return this.updateNative(playback, options, autoplay, preload);
1041		} else if (playback.method !== '') {
1042			// create plugin to mimic HTMLMediaElement
1043			
1044			return this.createPlugin( playback,  options, poster, autoplay, preload, controls);
1045		} else {
1046			// boo, no HTML5, no Flash, no Silverlight.
1047			this.createErrorMessage( playback, options, poster );
1048			
1049			return this;
1050		}
1051	},
1052	
1053	determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) {
1054		var
1055			mediaFiles = [],
1056			i,
1057			j,
1058			k,
1059			l,
1060			n,
1061			type,
1062			result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() !== 'audio'), scheme: ''},
1063			pluginName,
1064			pluginVersions,
1065			pluginInfo,
1066			dummy,
1067			media;
1068			
1069		// STEP 1: Get URL and type from <video src> or <source src>
1070
1071		// supplied type overrides <video type> and <source type>
1072		if (typeof options.type != 'undefined' && options.type !== '') {
1073			
1074			// accept either string or array of types
1075			if (typeof options.type == 'string') {
1076				mediaFiles.push({type:options.type, url:src});
1077			} else {
1078				
1079				for (i=0; i<options.type.length; i++) {
1080					mediaFiles.push({type:options.type[i], url:src});
1081				}
1082			}
1083
1084		// test for src attribute first
1085		} else if (src !== null) {
1086			type = this.formatType(src, htmlMediaElement.getAttribute('type'));
1087			mediaFiles.push({type:type, url:src});
1088
1089		// then test for <source> elements
1090		} else {
1091			// test <source> types to see if they are usable
1092			for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
1093				n = htmlMediaElement.childNodes[i];
1094				if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
1095					src = n.getAttribute('src');
1096					type = this.formatType(src, n.getAttribute('type'));
1097					media = n.getAttribute('media');
1098
1099					if (!media || !window.matchMedia || (window.matchMedia && window.matchMedia(media).matches)) {
1100						mediaFiles.push({type:type, url:src});
1101					}
1102				}
1103			}
1104		}
1105		
1106		// in the case of dynamicly created players
1107		// check for audio types
1108		if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) {
1109			result.isVideo = false;
1110		}
1111		
1112
1113		// STEP 2: Test for playback method
1114		
1115		// special case for Android which sadly doesn't implement the canPlayType function (always returns '')
1116		if (result.isVideo && mejs.MediaFeatures.isBustedAndroid) {
1117			htmlMediaElement.canPlayType = function(type) {
1118				return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : '';
1119			};
1120		}		
1121		
1122		// special case for Chromium to specify natively supported video codecs (i.e. WebM and Theora) 
1123		if (result.isVideo && mejs.MediaFeatures.isChromium) {
1124			htmlMediaElement.canPlayType = function(type) { 
1125				return (type.match(/video\/(webm|ogv|ogg)/gi) !== null) ? 'maybe' : '';
1126			}; 
1127		}
1128
1129		// test for native playback first
1130		if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native')  && !(mejs.MediaFeatures.isBustedNativeHTTPS && options.httpsBasicAuthSite === true)) {
1131						
1132			if (!isMediaTag) {
1133
1134				// create a real HTML5 Media Element 
1135				dummy = document.createElement( result.isVideo ? 'video' : 'audio');			
1136				htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement);
1137				htmlMediaElement.style.display = 'none';
1138				
1139				// use this one from now on
1140				result.htmlMediaElement = htmlMediaElement = dummy;
1141			}
1142				
1143			for (i=0; i<mediaFiles.length; i++) {
1144				// normal check
1145				if (mediaFiles[i].type == "video/m3u8" || htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== ''
1146					// special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg')
1147					|| htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== ''
1148					// special case for m4a supported by detecting mp4 support
1149					|| htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/m4a/,'mp4')).replace(/no/, '') !== '') {
1150					result.method = 'native';
1151					result.url = mediaFiles[i].url;
1152					break;
1153				}
1154			}			
1155			
1156			if (result.method === 'native') {
1157				if (result.url !== null) {
1158					htmlMediaElement.src = result.url;
1159				}
1160			
1161				// if `auto_plugin` mode, then cache the native result but try plugins.
1162				if (options.mode !== 'auto_plugin') {
1163					return result;
1164				}
1165			}
1166		}
1167
1168		// if native playback didn't work, then test plugins
1169		if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') {
1170			for (i=0; i<mediaFiles.length; i++) {
1171				type = mediaFiles[i].type;
1172
1173				// test all plugins in order of preference [silverlight, flash]
1174				for (j=0; j<options.plugins.length; j++) {
1175
1176					pluginName = options.plugins[j];
1177			
1178					// test version of plugin (for future features)
1179					pluginVersions = mejs.plugins[pluginName];				
1180					
1181					for (k=0; k<pluginVersions.length; k++) {
1182						pluginInfo = pluginVersions[k];
1183					
1184						// test if user has the correct plugin version
1185						
1186						// for youtube/vimeo
1187						if (pluginInfo.version == null || 
1188							
1189							mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {
1190
1191							// test for plugin playback types
1192							for (l=0; l<pluginInfo.types.length; l++) {
1193								// find plugin that can play the type
1194								if (type.toLowerCase() == pluginInfo.types[l].toLowerCase()) {
1195									result.method = pluginName;
1196									result.url = mediaFiles[i].url;
1197									return result;
1198								}
1199							}
1200						}
1201					}
1202				}
1203			}
1204		}
1205		
1206		// at this point, being in 'auto_plugin' mode implies that we tried plugins but failed.
1207		// if we have native support then return that.
1208		if (options.mode === 'auto_plugin' && result.method === 'native') {
1209			return result;
1210		}
1211
1212		// what if there's nothing to play? just grab the first available
1213		if (result.method === '' && mediaFiles.length > 0) {
1214			result.url = mediaFiles[0].url;
1215		}
1216
1217		return result;
1218	},
1219
1220	formatType: function(url, type) {
1221		// if no type is supplied, fake it with the extension
1222		if (url && !type) {		
1223			return this.getTypeFromFile(url);
1224		} else {
1225			// only return the mime part of the type in case the attribute contains the codec
1226			// see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element
1227			// `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4`
1228			
1229			if (type && ~type.indexOf(';')) {
1230				return type.substr(0, type.indexOf(';')); 
1231			} else {
1232				return type;
1233			}
1234		}
1235	},
1236	
1237	getTypeFromFile: function(url) {
1238		url = url.split('?')[0];
1239		var
1240			ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(),
1241			av = /(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video/' : 'audio/';
1242		return this.getTypeFromExtension(ext, av);
1243	},
1244	
1245	getTypeFromExtension: function(ext, av) {
1246		av = av || '';
1247		
1248		switch (ext) {
1249			case 'mp4':
1250			case 'm4v':
1251			case 'm4a':
1252			case 'f4v':
1253			case 'f4a':
1254				return av + 'mp4';
1255			case 'flv':
1256				return av + 'x-flv';
1257			case 'webm':
1258			case 'webma':
1259			case 'webmv':	
1260				return av + 'webm';
1261			case 'ogg':
1262			case 'oga':
1263			case 'ogv':	
1264				return av + 'ogg';
1265			case 'm3u8':
1266				return 'application/x-mpegurl';
1267			case 'ts':
1268				return av + 'mp2t';
1269			default:
1270				return av + ext;
1271		}
1272	},
1273
1274	createErrorMessage: function(playback, options, poster) {
1275		var 
1276			htmlMediaElement = playback.htmlMediaElement,
1277			errorContainer = document.createElement('div'),
1278			errorContent = options.customError;
1279			
1280		errorContainer.className = 'me-cannotplay';
1281
1282		try {
1283			errorContainer.style.width = htmlMediaElement.width + 'px';
1284			errorContainer.style.height = htmlMediaElement.height + 'px';
1285		} catch (e) {}
1286
1287		if (!errorContent) {
1288			errorContent = '<a href="' + playback.url + '">';
1289
1290			if (poster !== '') {
1291				errorContent += '<img src="' + poster + '" width="100%" height="100%" alt="" />';
1292			}
1293
1294			errorContent += '<span>' + mejs.i18n.t('mejs.download-file') + '</span></a>';
1295		}
1296
1297		errorContainer.innerHTML = errorContent;
1298
1299		htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
1300		htmlMediaElement.style.display = 'none';
1301
1302		options.error(htmlMediaElement);
1303	},
1304
1305	createPlugin:function(playback, options, poster, autoplay, preload, controls) {
1306		var 
1307			htmlMediaElement = playback.htmlMediaElement,
1308			width = 1,
1309			height = 1,
1310			pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++),
1311			pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url),
1312			container = document.createElement('div'),
1313			specialIEContainer,
1314			node,
1315			initVars;
1316
1317		// copy tagName from html media element
1318		pluginMediaElement.tagName = htmlMediaElement.tagName;
1319
1320		// copy attributes from html media element to plugin media element
1321		for (var i = 0; i < htmlMediaElement.attributes.length; i++) {
1322			var attribute = htmlMediaElement.attributes[i];
1323			if (attribute.specified) {
1324				pluginMediaElement.setAttribute(attribute.name, attribute.value);
1325			}
1326		}
1327
1328		// check for placement inside a <p> tag (sometimes WYSIWYG editors do this)
1329		node = htmlMediaElement.parentNode;
1330
1331		while (node !== null && node.tagName != null && node.tagName.toLowerCase() !== 'body' && 
1332				node.parentNode != null && node.parentNode.tagName != null && node.parentNode.constructor != null && node.parentNode.constructor.name === "ShadowRoot") {
1333			if (node.parentNode.tagName.toLowerCase() === 'p') {
1334				node.parentNode.parentNode.insertBefore(node, node.parentNode);
1335				break;
1336			}
1337			node = node.parentNode;
1338		}
1339
1340		if (playback.isVideo) {
1341			width = (options.pluginWidth > 0) ? options.pluginWidth : (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
1342			height = (options.pluginHeight > 0) ? options.pluginHeight : (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
1343		
1344			// in case of '%' make sure it's encoded
1345			width = mejs.Utility.encodeUrl(width);
1346			height = mejs.Utility.encodeUrl(height);
1347		
1348		} else {
1349			if (options.enablePluginDebug) {
1350				width = 320;
1351				height = 240;
1352			}
1353		}
1354
1355		// register plugin
1356		pluginMediaElement.success = options.success;
1357		
1358		// add container (must be added to DOM before inserting HTML for IE)
1359		container.className = 'me-plugin';
1360		container.id = pluginid + '_container';
1361		
1362		if (playback.isVideo) {
1363				htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);
1364		} else {
1365				document.body.insertBefore(container, document.body.childNodes[0]);
1366		}
1367		
1368		if (playback.method === 'flash' || playback.method === 'silverlight') {
1369
1370			var canPlayVideo = htmlMediaElement.getAttribute('type') === 'audio/mp4',
1371				childrenSources = htmlMediaElement.getElementsByTagName('source');
1372
1373			if (childrenSources && !canPlayVideo) {
1374				for (var i = 0, total = childrenSources.length; i < total; i++) {
1375					if (childrenSources[i].getAttribute('type') === 'audio/mp4') {
1376						canPlayVideo = true;
1377					}
1378				}
1379			}
1380
1381			// flash/silverlight vars
1382			initVars = [
1383				'id=' + pluginid,
1384				'isvideo=' + ((playback.isVideo || canPlayVideo) ? "true" : "false"),
1385				'autoplay=' + ((autoplay) ? "true" : "false"),
1386				'preload=' + preload,
1387				'width=' + width,
1388				'startvolume=' + options.startVolume,
1389				'timerrate=' + options.timerRate,
1390				'flashstreamer=' + options.flashStreamer,
1391				'height=' + height,
1392				'pseudostreamstart=' + options.pseudoStreamingStartQueryParam];
1393	
1394			if (playback.url !== null) {
1395				if (playback.method == 'flash') {
1396					initVars.push('file=' + mejs.Utility.encodeUrl(playback.url));
1397				} else {
1398					initVars.push('file=' + playback.url);
1399				}
1400			}
1401			if (options.enablePluginDebug) {
1402				initVars.push('debug=true');
1403			}
1404			if (options.enablePluginSmoothing) {
1405				initVars.push('smoothing=true');
1406			}
1407			if (options.enablePseudoStreaming) {
1408				initVars.push('pseudostreaming=true');
1409			}
1410			if (controls) {
1411				initVars.push('controls=true'); // shows controls in the plugin if desired
1412			}
1413			if (options.pluginVars) {
1414				initVars = initVars.concat(options.pluginVars);
1415			}		
1416			
1417			// call from plugin
1418			window[pluginid + '_init'] = function() {
1419				switch (pluginMediaElement.pluginType) {
1420					case 'flash':
1421						pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(pluginid);
1422						break;
1423					case 'silverlight':
1424						pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
1425						pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS;
1426						break;
1427				}
1428	
1429				if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) {
1430					pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
1431				}
1432			};
1433			
1434			// event call from plugin
1435			window[pluginid + '_event'] = function(eventName, values) {
1436		
1437				var
1438					e,
1439					i,
1440					bufferedTime;
1441		        
1442				// fake event object to mimic real HTML media event.
1443				e = {
1444					type: eventName,
1445					target: pluginMediaElement
1446				};
1447		
1448				// attach all values to element and event object
1449				for (i in values) {
1450					pluginMediaElement[i] = values[i];
1451					e[i] = values[i];
1452				}
1453		
1454				// fake the newer W3C buffered TimeRange (loaded and total have been removed)
1455				bufferedTime = values.bufferedTime || 0;
1456		
1457				e.target.buffered = e.buffered = {
1458					start: function(index) {
1459						return 0;
1460					},
1461					end: function (index) {
1462						return bufferedTime;
1463					},
1464					length: 1
1465				};
1466		
1467				pluginMediaElement.dispatchEvent(e);
1468			}			
1469			
1470			
1471		}
1472
1473		switch (playback.method) {
1474			case 'silverlight':
1475				container.innerHTML =
1476'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' +
1477'<param name="initParams" value="' + initVars.join(',') + '" />' +
1478'<param name="windowless" value="true" />' +
1479'<param name="background" value="black" />' +
1480'<param name="minRuntimeVersion" value="3.0.0.0" />' +
1481'<param name="autoUpgrade" value="true" />' +
1482'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
1483'</object>';
1484					break;
1485
1486			case 'flash':
1487
1488				if (mejs.MediaFeatures.isIE) {
1489					specialIEContainer = document.createElement('div');
1490					container.appendChild(specialIEContainer);
1491					specialIEContainer.outerHTML =
1492'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
1493'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' +
1494'<param name="movie" value="' + options.pluginPath + options.flashName + '?' + (new Date().getTime()) + '" />' +
1495'<param name="flashvars" value="' + initVars.join('&amp;') + '" />' +
1496'<param name="quality" value="high" />' +
1497'<param name="bgcolor" value="#000000" />' +
1498'<param name="wmode" value="transparent" />' +
1499'<param name="allowScriptAccess" value="' + options.flashScriptAccess + '" />' +
1500'<param name="allowFullScreen" value="true" />' +
1501'<param name="scale" value="default" />' + 
1502'</object>';
1503
1504				} else {
1505
1506					container.innerHTML =
1507'<embed id="' + pluginid + '" name="' + pluginid + '" ' +
1508'play="true" ' +
1509'loop="false" ' +
1510'quality="high" ' +
1511'bgcolor="#000000" ' +
1512'wmode="transparent" ' +
1513'allowScriptAccess="' + options.flashScriptAccess + '" ' +
1514'allowFullScreen="true" ' +
1515'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' +
1516'src="' + options.pluginPath + options.flashName + '" ' +
1517'flashvars="' + initVars.join('&') + '" ' +
1518'width="' + width + '" ' +
1519'height="' + height + '" ' +
1520'scale="default"' + 
1521'class="mejs-shim"></embed>';
1522				}
1523				break;
1524			
1525			case 'youtube':
1526			
1527				
1528				var videoId;
1529				// youtu.be url from share button
1530				if (playback.url.lastIndexOf("youtu.be") != -1) {
1531					videoId = playback.url.substr(playback.url.lastIndexOf('/')+1);
1532					if (videoId.indexOf('?') != -1) {
1533						videoId = videoId.substr(0, videoId.indexOf('?'));
1534					}
1535				}
1536				else {
1537					// https://www.youtube.com/watch?v=
1538					var videoIdMatch = playback.url.match( /[?&]v=([^&#]+)|&|#|$/ );
1539					if ( videoIdMatch ) {
1540						videoId = videoIdMatch[1];
1541					}
1542				}
1543				youtubeSettings = {
1544						container: container,
1545						containerId: container.id,
1546						pluginMediaElement: pluginMediaElement,
1547						pluginId: pluginid,
1548						videoId: videoId,
1549						height: height,
1550						width: width,
1551                        scheme: playback.scheme,
1552						variables: options.youtubeIframeVars
1553					};				
1554				
1555				// favor iframe version of YouTube
1556				if (window.postMessage) {
1557					mejs.YouTubeApi.enqueueIframe(youtubeSettings);		
1558				} else if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) {
1559					mejs.YouTubeApi.createFlash(youtubeSettings, options);
1560				}
1561				break;
1562			
1563			// DEMO Code. Does NOT work.
1564			case 'vimeo':
1565				var player_id = pluginid + "_player";
1566				pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1);
1567				
1568				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>';
1569				if (typeof($f) == 'function') { // froogaloop available
1570					var player = $f(container.childNodes[0]),
1571						playerState = -1;
1572					
1573					player.addEvent('ready', function() {
1574		
1575						player.playVideo = function() {
1576							player.api( 'play' );
1577						};
1578						player.stopVideo = function() {
1579							player.api( 'unload' );
1580						};
1581						player.pauseVideo = function() {
1582							player.api( 'pause' );
1583						};
1584						player.seekTo = function( seconds ) {
1585							player.api( 'seekTo', seconds );
1586						};
1587						player.setVolume = function( volume ) {
1588							player.api( 'setVolume', volume );
1589						};
1590						player.setMuted = function( muted ) {
1591							if( muted ) {
1592								player.lastVolume = player.api( 'getVolume' );
1593								player.api( 'setVolume', 0 );
1594							} else {
1595								player.api( 'setVolume', player.lastVolume );
1596								delete player.lastVolume;
1597							}
1598						};
1599						// parity with YT player
1600						player.getPlayerState = function() {
1601							return playerState;
1602						};
1603
1604						function createEvent(player, pluginMediaElement, eventName, e) {
1605							var event = {
1606								type: eventName,
1607								target: pluginMediaElement
1608							};
1609							if (eventName == 'timeupdate') {
1610								pluginMediaElement.currentTime = event.currentTime = e.seconds;
1611								pluginMediaElement.duration = event.duration = e.duration;
1612							}
1613							pluginMediaElement.dispatchEvent(event);
1614						}
1615
1616						player.addEvent('play', function() {
1617							playerState = 1;
1618							createEvent(player, pluginMediaElement, 'play');
1619							createEvent(player, pluginMediaElement, 'playing');
1620						});
1621
1622						player.addEvent('pause', function() {
1623							playerState = 2;							
1624							createEvent(player, pluginMediaElement, 'pause');
1625						});
1626
1627						player.addEvent('finish', function() {
1628							playerState = 0;							
1629							createEvent(player, pluginMediaElement, 'ended');
1630						});
1631
1632						player.addEvent('playProgress', function(e) {
1633							createEvent(player, pluginMediaElement, 'timeupdate', e);
1634						});
1635						
1636						player.addEvent('seek', function(e) {
1637							playerState = 3;
1638							createEvent(player, pluginMediaElement, 'seeked', e);
1639						});	
1640						
1641						player.addEvent('loadProgress', function(e) {
1642							playerState = 3;
1643							createEvent(player, pluginMediaElement, 'progress', e);
1644						});												
1645
1646						pluginMediaElement.pluginElement = container;
1647						pluginMediaElement.pluginApi = player;
1648
1649						pluginMediaElement.success(pluginMediaElement, pluginMediaElement.pluginElement);						
1650					});
1651				}
1652				else {
1653					console.warn("You need to include froogaloop for vimeo to work");
1654				}
1655				break;			
1656		}
1657		// hide original element
1658		htmlMediaElement.style.display = 'none';
1659		// prevent browser from autoplaying when using a plugin
1660		htmlMediaElement.removeAttribute('autoplay');
1661		
1662		return pluginMediaElement;
1663	},
1664
1665	updateNative: function(playback, options, autoplay, preload) {
1666		
1667		var htmlMediaElement = playback.htmlMediaElement,
1668			m;
1669		
1670		
1671		// add methods to video object to bring it into parity with Flash Object
1672		for (m in mejs.HtmlMediaElement) {
1673			htmlMediaElement[m] = mejs.HtmlMediaElement[m];
1674		}
1675
1676		/*
1677		Chrome now supports preload="none"
1678		if (mejs.MediaFeatures.isChrome) {
1679		
1680			// special case to enforce preload attribute (Chrome doesn't respect this)
1681			if (preload === 'none' && !autoplay) {
1682			
1683				// forces the browser to stop loading (note: fails in IE9)
1684				htmlMediaElement.src = '';
1685				htmlMediaElement.load();
1686				htmlMediaElement.canceledPreload = true;
1687
1688				htmlMediaElement.addEventListener('play',function() {
1689					if (htmlMediaElement.canceledPreload) {
1690						htmlMediaElement.src = playback.url;
1691						htmlMediaElement.load();
1692						htmlMediaElement.play();
1693						htmlMediaElement.canceledPreload = false;
1694					}
1695				}, false);
1696			// for some reason Chrome forgets how to autoplay sometimes.
1697			} else if (autoplay) {
1698				htmlMediaElement.load();
1699				htmlMediaElement.play();
1700			}
1701		}
1702		*/
1703
1704		// fire success code
1705		options.success(htmlMediaElement, htmlMediaElement);
1706		
1707		return htmlMediaElement;
1708	}
1709};
1710
1711/*
1712 - test on IE (object vs. embed)
1713 - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE)
1714 - fullscreen?
1715*/
1716
1717// YouTube Flash and Iframe API
1718mejs.YouTubeApi = {
1719	isIframeStarted: false,
1720	isIframeLoaded: false,
1721	loadIframeApi: function(yt) {
1722		if (!this.isIframeStarted) {
1723			var tag = document.createElement('script');
1724			tag.src = yt.scheme + "www.youtube.com/player_api";
1725			var firstScriptTag = document.getElementsByTagName('script')[0];
1726			firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
1727			this.isIframeStarted = true;
1728		}
1729	},
1730	iframeQueue: [],
1731	enqueueIframe: function(yt) {
1732		
1733		if (this.isLoaded) {
1734			this.createIframe(yt);
1735		} else {
1736			this.loadIframeApi(yt);
1737			this.iframeQueue.push(yt);
1738		}
1739	},
1740	createIframe: function(settings) {
1741
1742		var
1743		pluginMediaElement = settings.pluginMediaElement,
1744		defaultVars = {controls:0, wmode:'transparent'},
1745		player = new YT.Player(settings.containerId, {
1746			height: settings.height,
1747			width: settings.width,
1748			videoId: settings.videoId,
1749			playerVars: mejs.$.extend({}, defaultVars, settings.variables),
1750			events: {
1751				'onReady': function(e) {
1752					
1753					// wrapper to match
1754					player.setVideoSize = function(width, height) {
1755						player.setSize(width, height);
1756					};
1757					
1758					// hook up iframe object to MEjs
1759					settings.pluginMediaElement.pluginApi = player;
1760					settings.pluginMediaElement.pluginElement = document.getElementById(settings.containerId);
1761					
1762					// init mejs
1763					pluginMediaElement.success(pluginMediaElement, pluginMediaElement.pluginElement);
1764
1765					mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay');
1766					
1767					// create timer
1768					setInterval(function() {
1769						mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate');
1770					}, 250);
1771
1772					if (typeof pluginMediaElement.attributes.autoplay !== 'undefined') {
1773						player.playVideo();
1774					}
1775				},
1776				'onStateChange': function(e) {
1777
1778					mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement);
1779					
1780				}
1781			}
1782		});
1783	},
1784	
1785	createEvent: function (player, pluginMediaElement, eventName) {
1786		var event = {
1787			type: eventName,
1788			target: pluginMediaElement
1789		};
1790
1791		if (player && player.getDuration) {
1792			
1793			// time 
1794			pluginMediaElement.currentTime = event.currentTime = player.getCurrentTime();
1795			pluginMediaElement.duration = event.duration = player.getDuration();
1796			
1797			// state
1798			event.paused = pluginMediaElement.paused;
1799			event.ended = pluginMediaElement.ended;			
1800			
1801			// sound
1802			event.muted = player.isMuted();
1803			event.volume = player.getVolume() / 100;
1804			
1805			// progress
1806			event.bytesTotal = player.getVideoBytesTotal();
1807			event.bufferedBytes = player.getVideoBytesLoaded();
1808			
1809			// fake the W3C buffered TimeRange
1810			var bufferedTime = event.bufferedBytes / event.bytesTotal * event.duration;
1811			
1812			event.target.buffered = event.buffered = {
1813				start: function(index) {
1814					return 0;
1815				},
1816				end: function (index) {
1817					return bufferedTime;
1818				},
1819				length: 1
1820			};
1821
1822		}
1823		
1824		// send event up the chain
1825		pluginMediaElement.dispatchEvent(event);
1826	},	
1827	
1828	iFrameReady: function() {
1829		
1830		this.isLoaded = true;
1831		this.isIframeLoaded = true;
1832		
1833		while (this.iframeQueue.length > 0) {
1834			var settings = this.iframeQueue.pop();
1835			this.createIframe(settings);
1836		}	
1837	},
1838	
1839	// FLASH!
1840	flashPlayers: {},
1841	createFlash: function(settings) {
1842		
1843		this.flashPlayers[settings.pluginId] = settings;
1844		
1845		/*
1846		settings.container.innerHTML =
1847			'<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + settings.scheme + 'www.youtube.com/apiplayer?enablejsapi=1&amp;playerapiid=' + settings.pluginId  + '&amp;version=3&amp;autoplay=0&amp;controls=0&amp;modestbranding=1&loop=0" ' +
1848				'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' +
1849				'<param name="allowScriptAccess" value="sameDomain">' +
1850				'<param name="wmode" value="transparent">' +
1851			'</object>';
1852		*/
1853
1854		var specialIEContainer,
1855			youtubeUrl = settings.scheme + 'www.youtube.com/apiplayer?enablejsapi=1&amp;playerapiid=' + settings.pluginId  + '&amp;version=3&amp;autoplay=0&amp;controls=0&amp;modestbranding=1&loop=0';
1856			
1857		if (mejs.MediaFeatures.isIE) {
1858			
1859			specialIEContainer = document.createElement('div');
1860			settings.container.appendChild(specialIEContainer);
1861			specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + settings.scheme + 'download.macromedia.com/pub/s
1861hockwave/cabs/flash/swflash.cab" ' +
1862'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' +
1863	'<param name="movie" value="' + youtubeUrl + '" />' +
1864	'<param name="wmode" value="transparent" />' +
1865	'<param name="allowScriptAccess" value="' + options.flashScriptAccess + '" />' +
1866	'<param name="allowFullScreen" value="true" />' +
1867'</object>';
1868		} else {
1869		settings.container.innerHTML =
1870			'<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' +
1871				'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' +
1872				'<param name="allowScriptAccess" value="' + options.flashScriptAccess + '">' +
1873				'<param name="wmode" value="transparent">' +
1874			'</object>';
1875		}		
1876		
1877	},
1878	
1879	flashReady: function(id) {
1880		var
1881			settings = this.flashPlayers[id],
1882			player = document.getElementById(id),
1883			pluginMediaElement = settings.pluginMediaElement;
1884		
1885		// hook up and return to MediaELementPlayer.success	
1886		pluginMediaElement.pluginApi = 
1887		pluginMediaElement.pluginElement = player;
1888		
1889		settings.success(pluginMediaElement, pluginMediaElement.pluginElement);
1890		
1891		// load the youtube video
1892		player.cueVideoById(settings.videoId);
1893		
1894		var callbackName = settings.containerId + '_callback';
1895		
1896		window[callbackName] = function(e) {
1897			mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement);
1898		};
1899		
1900		player.addEventListener('onStateChange', callbackName);
1901		
1902		setInterval(function() {
1903			mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate');
1904		}, 250);
1905		
1906		mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay');
1907	},
1908	
1909	handleStateChange: function(youTubeState, player, pluginMediaElement) {
1910		switch (youTubeState) {
1911			case -1: // not started
1912				pluginMediaElement.paused = true;
1913				pluginMediaElement.ended = true;
1914				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata');
1915				//createYouTubeEvent(player, pluginMediaElement, 'loadeddata');
1916				break;
1917			case 0:
1918				pluginMediaElement.paused = false;
1919				pluginMediaElement.ended = true;
1920				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended');
1921				break;
1922			case 1:
1923				pluginMediaElement.paused = false;
1924				pluginMediaElement.ended = false;				
1925				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play');
1926				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing');
1927				break;
1928			case 2:
1929				pluginMediaElement.paused = true;
1930				pluginMediaElement.ended = false;				
1931				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause');
1932				break;
1933			case 3: // buffering
1934				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress');
1935				break;
1936			case 5:
1937				// cued?
1938				break;						
1939			
1940		}			
1941		
1942	}
1943}
1944// IFRAME
1945window.onYouTubePlayerAPIReady = function() {
1946	mejs.YouTubeApi.iFrameReady();
1947};
1948// FLASH
1949window.onYouTubePlayerReady = function(id) {
1950	mejs.YouTubeApi.flashReady(id);
1951};
1952
1953window.mejs = mejs;
1954window.MediaElement = mejs.MediaElement;
1955
1956/**
1957 * Localize strings
1958 *
1959 * Include translations from JS files and method to pluralize properly strings.
1960 *
1961 */
1962(function (doc, win, mejs, undefined) {
1963
1964	var i18n = {
1965		/**
1966		 * @type {String}
1967		 */
1968		'default': 'en',
1969
1970		/**
1971		 * @type {String[]}
1972		 */
1973		locale: {
1974			language: (mejs.i18n && mejs.i18n.locale.language) || '',
1975			strings: (mejs.i18n && mejs.i18n.locale.strings) || {}
1976		},
1977
1978		/**
1979		 * Filters for available languages.
1980		 *
1981		 * This plural forms are grouped in family groups based on
1982		 * https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals#List_of_Plural_Rules
1983		 * with some additions and corrections according to the Localization Guide list
1984		 * (http://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html)
1985		 *
1986		 * Arguments are dynamic following the structure:
1987		 * - argument1 : Number to determine form
1988		 * - argument2...argumentN: Possible matches
1989		 *
1990		 * @type {Function[]}
1991		 */
1992		pluralForms: [
1993			// 0: Chinese, Japanese, Korean, Persian, Turkish, Thai, Lao, Aymará,
1994			// Tibetan, Chiga, Dzongkha, Indonesian, Lojban, Georgian, Kazakh, Khmer, Kyrgyz, Malay,
1995			// Burmese, Yakut, Sundanese, Tatar, Uyghur, Vietnamese, Wolof
1996			function () {
1997				return arguments[1];
1998			},
1999			// 1: Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish, Estonian, Finnish,
2000			// Hungarian, Basque, Greek, Hebrew, Italian, Portuguese, Spanish, Catalan, Afrikaans,
2001			// Angika, Assamese, Asturian, Azerbaijani, Bulgarian, Bengali, Bodo, Aragonese, Dogri,
2002			// Esperanto, Argentinean Spanish, Fulah, Friulian, Galician, Gujarati, Hausa,
2003			// Hindi, Chhattisgarhi, Armenian, Interlingua, Greenlandic, Kannada, Kurdish, Letzeburgesch,
2004			// Maithili, Malayalam, Mongolian, Manipuri, Marathi, Nahuatl, Neapolitan, Norwegian Bokmal,
2005			// Nepali, Norwegian Nynorsk, Norwegian (old code), Northern Sotho, Oriya, Punjabi, Papiamento,
2006			// Piemontese, Pashto, Romansh, Kinyarwanda, Santali, Scots, Sindhi, Northern Sami, Sinhala,
2007			// Somali, Songhay, Albanian, Swahili, Tamil, Telugu, Turkmen, Urdu, Yoruba
2008			function () {
2009				var args = arguments;
2010				if (args[0] === 1) {
2011					return args[1];
2012				} else {
2013					return args[2];
2014				}
2015			},
2016			// 2: French, Brazilian Portuguese, Acholi, Akan, Amharic, Mapudungun, Breton, Filipino,
2017			// Gun, Lingala, Mauritian Creole, Malagasy, Maori, Occitan, Tajik, Tigrinya, Uzbek, Walloon
2018			function () {
2019				var args = arguments;
2020				if ([0, 1].indexOf(args[0]) > -1) {
2021					return args[1];
2022				} else {
2023					return args[2];
2024				}
2025			},
2026			// 3: Latvian
2027			function () {
2028				var args = arguments;
2029				if (args[0] % 10 === 1 && args[0] % 100 !== 11) {
2030					return args[1];
2031				} else if (args[0] !== 0) {
2032					return args[2];
2033				} else {
2034					return args[3];
2035				}
2036			},
2037			// 4: Scottish Gaelic
2038			function () {
2039				var args = arguments;
2040				if (args[0] === 1 || args[0] === 11) {
2041					return args[1];
2042				} else if (args[0] === 2 || args[0] === 12) {
2043					return args[2];
2044				} else if (args[0] > 2 && args[0] < 20) {
2045					return args[3];
2046				} else {
2047					return args[4];
2048				}
2049			},
2050			// 5:  Romanian
2051			function () {
2052				if (args[0] === 1) {
2053					return args[1];
2054				} else if (args[0] === 0 || (args[0] % 100 > 0 && args[0] % 100 < 20)) {
2055					return args[2];
2056				} else {
2057					return args[3];
2058				}
2059			},
2060			// 6: Lithuanian
2061			function () {
2062				var args = arguments;
2063				if (args[0] % 10 === 1 && args[0] % 100 !== 11) {
2064					return args[1];
2065				} else if (args[0] % 10 >= 2 && (args[0] % 100 < 10 || args[0] % 100 >= 20)) {
2066					return args[2];
2067				} else {
2068					return [3];
2069				}
2070			},
2071			// 7: Belarusian, Bosnian, Croatian, Serbian, Russian, Ukrainian
2072			function () {
2073				var args = arguments;
2074				if (args[0] % 10 === 1 && args[0] % 100 !== 11) {
2075					return args[1];
2076				} else if (args[0] % 10 >= 2 && args[0] % 10 <= 4 && (args[0] % 100 < 10 || args[0] % 100 >= 20)) {
2077					return args[2];
2078				} else {
2079					return args[3];
2080				}
2081			},
2082			// 8:  Slovak, Czech
2083			function () {
2084				var args = arguments;
2085				if (args[0] === 1) {
2086					return args[1];
2087				} else if (args[0] >= 2 && args[0] <= 4) {
2088					return args[2];
2089				} else {
2090					return args[3];
2091				}
2092			},
2093			// 9: Polish
2094			function () {
2095				var args = arguments;
2096				if (args[0] === 1) {
2097					return args[1];
2098				} else if (args[0] % 10 >= 2 && args[0] % 10 <= 4 && (args[0] % 100 < 10 || args[0] % 100 >= 20)) {
2099					return args[2];
2100				} else {
2101					return args[3];
2102				}
2103			},
2104			// 10: Slovenian
2105			function () {
2106				var args = arguments;
2107				if (args[0] % 100 === 1) {
2108					return args[2];
2109				} else if (args[0] % 100 === 2) {
2110					return args[3];
2111				} else if (args[0] % 100 === 3 || args[0] % 100 === 4) {
2112					return args[4];
2113				} else {
2114					return args[1];
2115				}
2116			},
2117			// 11: Irish Gaelic
2118			function () {
2119				var args = arguments;
2120				if (args[0] === 1) {
2121					return args[1];
2122				} else if (args[0] === 2) {
2123					return args[2];
2124				} else if (args[0] > 2 && args[0] < 7) {
2125					return args[3];
2126				} else if (args[0] > 6 && args[0] < 11) {
2127					return args[4];
2128				} else {
2129					return args[5];
2130				}
2131			},
2132			// 12: Arabic
2133			function () {
2134				var args = arguments;
2135				if (args[0] === 0) {
2136					return args[1];
2137				} else if (args[0] === 1) {
2138					return args[2];
2139				} else if (args[0] === 2) {
2140					return args[3];
2141				} else if (args[0] % 100 >= 3 && args[0] % 100 <= 10) {
2142					return args[4];
2143				} else if (args[0] % 100 >= 11) {
2144					return args[5];
2145				} else {
2146					return args[6];
2147				}
2148			},
2149			// 13: Maltese
2150			function () {
2151				var args = arguments;
2152				if (args[0] === 1) {
2153					return args[1];
2154				} else if (args[0] === 0 || (args[0] % 100 > 1 && args[0] % 100 < 11)) {
2155					return args[2];
2156				} else if (args[0] % 100 > 10 && args[0] % 100 < 20) {
2157					return args[3];
2158				} else {
2159					return args[4];
2160				}
2161
2162			},
2163			// 14: Macedonian
2164			function () {
2165				var args = arguments;
2166				if (args[0] % 10 === 1) {
2167					return args[1];
2168				} else if (args[0] % 10 === 2) {
2169					return args[2];
2170				} else {
2171					return args[3];
2172				}
2173			},
2174			// 15:  Icelandic
2175			function () {
2176				var args = arguments;
2177				if (args[0] !== 11 && args[0] % 10 === 1) {
2178					return args[1];
2179				} else {
2180					return args[2];
2181				}
2182			},
2183			// New additions
2184
2185			// 16:  Kashubian
2186			// Note: in https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals#List_of_Plural_Rules
2187			// Breton is listed as #16 but in the Localization Guide it belongs to the group 2
2188			function () {
2189				var args = arguments;
2190				if (args[0] === 1) {
2191					return args[1];
2192				} else if (args[0] % 10 >= 2 && args[0] % 10 <= 4 && (args[0] % 100 < 10 || args[0] % 100 >= 20)) {
2193					return args[2];
2194				} else {
2195					return args[3];
2196				}
2197			},
2198			// 17:  Welsh
2199			function () {
2200				var args = arguments;
2201				if (args[0] === 1) {
2202					return args[1];
2203				} else if (args[0] === 2) {
2204					return args[2];
2205				} else if (args[0] !== 8 && args[0] !== 11) {
2206					return args[3];
2207				} else {
2208					return args[4];
2209				}
2210			},
2211			// 18:  Javanese
2212			function () {
2213				var args = arguments;
2214				if (args[0] === 0) {
2215					return args[1];
2216				} else {
2217					return args[2];
2218				}
2219			},
2220			// 19:  Cornish
2221			function () {
2222				var args = arguments;
2223				if (args[0] === 1) {
2224					return args[1];
2225				} else if (args[0] === 2) {
2226					return args[2];
2227				} else if (args[0] === 3) {
2228					return args[3];
2229				} else {
2230					return args[4];
2231				}
2232			},
2233			// 20:  Mandinka
2234			function () {
2235				var args = arguments;
2236				if (args[0] === 0) {
2237					return args[1];
2238				} else if (args[0] === 1) {
2239					return args[2];
2240				} else {
2241					return args[3];
2242				}
2243			}
2244		],
2245		/**
2246		 * Get specified language
2247		 *
2248		 */
2249		getLanguage: function () {
2250			var language = i18n.locale.language || i18n['default'];
2251			return /^(x\-)?[a-z]{2,}(\-\w{2,})?(\-\w{2,})?$/.exec(language) ? language : i18n['default'];
2252		},
2253
2254		/**
2255		 * Translate a string to a specified language, including optionally a number to pluralize translation
2256		 *
2257		 * @param {String} message
2258		 * @param {Number} pluralParam
2259		 * @return {String}
2260		 */
2261		t: function (message, pluralParam) {
2262
2263			if (typeof message === 'string' && message.length) {
2264
2265				var
2266					language = i18n.getLanguage(),
2267					str,
2268					pluralForm,
2269					/**
2270					 * Modify string using algorithm to detect plural forms.
2271					 *
2272					 * @private
2273					 * @see http://stackoverflow.com/questions/1353408/messageformat-in-javascript-parameters-in-localized-ui-strings
2274					 * @param {String|String[]}
2274 input   - String or array of strings to pick the plural form
2275					 * @param {Number} number           - Number to determine the proper plural form
2276					 * @param {Number} form             - Number of language family to apply plural form
2277					 * @return {String}
2278					 */
2279					plural = function (input, number, form) {
2280
2281						if (typeof input !== 'object' || typeof number !== 'number' || typeof form !== 'number') {
2282							return input;
2283						}
2284
2285						if (typeof input === 'string') {
2286							return input;
2287						}
2288
2289						// Perform plural form or return original text
2290						return i18n.pluralForms[form].apply(null, [number].concat(input));
2291					},
2292					/**
2293					 *
2294					 * @param {String} input
2295					 * @return {String}
2296					 */
2297					escapeHTML = function (input) {
2298						var map = {
2299							'&': '&amp;',
2300							'<': '&lt;',
2301							'>': '&gt;',
2302							'"': '&quot;'
2303						};
2304
2305						return input.replace(/[&<>"]/g, function(c) {
2306							return map[c];
2307						});
2308					}
2309				;
2310
2311				// Fetch the localized version of the string
2312				if (i18n.locale.strings && i18n.locale.strings[language]) {
2313					str = i18n.locale.strings[language][message];
2314					if (typeof pluralParam === 'number') {
2315						pluralForm = i18n.locale.strings[language]['mejs.plural-form'];
2316						str = plural.apply(null, [str, pluralParam, pluralForm]);
2317					}
2318				}
2319
2320				// Fallback to default language if requested uid is not translated
2321				if (!str && i18n.locale.strings && i18n.locale.strings[i18n['default']]) {
2322					str = i18n.locale.strings[i18n['default']][message];
2323					if (typeof pluralParam === 'number') {
2324						pluralForm = i18n.locale.strings[i18n['default']]['mejs.plural-form'];
2325						str = plural.apply(null, [str, pluralParam, pluralForm]);
2326
2327					}
2328				}
2329
2330				// As a last resort, use the requested uid, to mimic original behavior of i18n utils (in which uid was the english text)
2331				str = str || message;
2332
2333				// Replace token
2334				if (typeof pluralParam === 'number') {
2335					str = str.replace('%1', pluralParam);
2336				}
2337
2338				return escapeHTML(str);
2339
2340			}
2341
2342			return message;
2343		}
2344
2345	};
2346
2347	// i18n fixes for compatibility with WordPress
2348	if (typeof mejsL10n !== 'undefined') {
2349		i18n.locale.language = mejsL10n.language;
2350	}
2351
2352	// Register variable
2353	mejs.i18n = i18n;
2354
2355
2356}(document, window, mejs));
2357
2358// i18n fixes for compatibility with WordPress
2359;(function (mejs, undefined) {
2360
2361	"use strict";
2362
2363	if (typeof mejsL10n !== 'undefined') {
2364		mejs[mejsL10n.lang] = mejsL10n.strings;
2365	}
2366
2367}(mejs.i18n.locale.strings));
2368/*!
2369 * This is a i18n.locale language object.
2370 *
2371 * English; This can serve as a template for other languages to translate
2372 *
2373 * @author
2374 *   TBD
2375 *   Sascha Greuel (Twitter: @SoftCreatR)
2376 *
2377 * @see
2378 *   me-i18n.js
2379 *
2380 * @params
2381 *  - exports - CommonJS, window ..
2382 */
2383(function (exports) {
2384    "use strict";
2385
2386    if (exports.en === undefined) {
2387        exports.en = {
2388            "mejs.plural-form": 1,
2389
2390            // me-shim
2391            "mejs.download-file": "Download File",
2392
2393            // mep-feature-contextmenu
2394            "mejs.fullscreen-off": "Turn off Fullscreen",
2395            "mejs.fullscreen-on": "Go Fullscreen",
2396            "mejs.download-video": "Download Video",
2397
2398            // mep-feature-fullscreen
2399            "mejs.fullscreen": "Fullscreen",
2400
2401            // mep-feature-jumpforward
2402            "mejs.time-jump-forward": ["Jump forward 1 second", "Jump forward %1 seconds"],
2403
2404            // mep-feature-playpause
2405            "mejs.play": "Play",
2406            "mejs.pause": "Pause",
2407
2408            // mep-feature-postroll
2409            "mejs.close": "Close",
2410
2411            // mep-feature-progress
2412            "mejs.time-slider": "Time Slider",
2413            "mejs.time-help-text": "Use Left/Right Arrow keys to advance one second, Up/Down arrows to advance ten seconds.",
2414
2415            // mep-feature-skipback
2416            "mejs.time-skip-back": ["Skip back 1 second", "Skip back %1 seconds"],
2417
2418            // mep-feature-tracks
2419            "mejs.captions-subtitles": "Captions/Subtitles",
2420            "mejs.none": "None",
2421
2422            // mep-feature-volume
2423            "mejs.mute-toggle": "Mute Toggle",
2424            "mejs.volume-help-text": "Use Up/Down Arrow keys to increase or decrease volume.",
2425            "mejs.unmute": "Unmute",
2426            "mejs.mute": "Mute",
2427            "mejs.volume-slider": "Volume Slider",
2428
2429            // mep-player
2430            "mejs.video-player": "Video Player",
2431            "mejs.audio-player": "Audio Player",
2432
2433            // mep-feature-ads
2434            "mejs.ad-skip": "Skip ad",
2435            "mejs.ad-skip-info": ["Skip in 1 second", "Skip in %1 seconds"],
2436
2437            // mep-feature-sourcechooser
2438            "mejs.source-chooser": "Source Chooser"
2439        };
2440    }
2441}(mejs.i18n.locale.strings));
2442
2443/*!
2444 *
2445 * MediaElementPlayer
2446 * http://mediaelementjs.com/
2447 *
2448 * Creates a controller bar for HTML5 <video> add <audio> tags
2449 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper)
2450 *
2451 * Copyright 2010-2013, John Dyer (http://j.hn/)
2452 * License: MIT
2453 *
2454 */
2455if (typeof jQuery != 'undefined') {
2456	mejs.$ = jQuery;
2457} else if (typeof Zepto != 'undefined') {
2458	mejs.$ = Zepto;
2459
2460	// define `outerWidth` method which has not been realized in Zepto
2461	Zepto.fn.outerWidth = function(includeMargin) {
2462		var width = $(this).width();
2463		if (includeMargin) {
2464			width += parseInt($(this).css('margin-right'), 10);
2465			width += parseInt($(this).css('margin-left'), 10);
2466		}
2467		return width
2468	}
2469
2470} else if (typeof ender != 'undefined') {
2471	mejs.$ = ender;
2472}
2473(function ($) {
2474
2475	// default player values
2476	mejs.MepDefaults = {
2477		// url to poster (to fix iOS 3.x)
2478		poster: '',
2479		// When the video is ended, we can show the poster.
2480		showPosterWhenEnded: false,
2481		// default if the <video width> is not specified
2482		defaultVideoWidth: 480,
2483		// default if the <video height> is not specified
2484		defaultVideoHeight: 270,
2485		// if set, overrides <video width>
2486		videoWidth: -1,
2487		// if set, overrides <video height>
2488		videoHeight: -1,
2489		// default if the user doesn't specify
2490		defaultAudioWidth: 400,
2491		// default if the user doesn't specify
2492		defaultAudioHeight: 30,
2493		// default amount to move back when back key is pressed
2494		defaultSeekBackwardInterval: function(media) {
2495			return (media.duration * 0.05);
2496		},
2497		// default amount to move forward when forward key is pressed
2498		defaultSeekForwardInterval: function(media) {
2499			return (media.duration * 0.05);
2500		},
2501		// set dimensions via JS instead of CSS
2502		setDimensions: true,
2503		// width of audio player
2504		audioWidth: -1,
2505		// height of audio player
2506		audioHeight: -1,
2507		// initial volume when the player starts (overrided by user cookie)
2508		startVolume: 0.8,
2509		// useful for <audio> player loops
2510		loop: false,
2511		// rewind to beginning when media ends
2512		autoRewind: true,
2513		// resize to media dimensions
2514		enableAutosize: true,
2515		/*
2516		 * Time format to use. Default: 'mm:ss'
2517		 * Supported units:
2518		 *   h: hour
2519		 *   m: minute
2520		 *   s: second
2521		 *   f: frame count
2522		 * When using 'hh', 'mm', 'ss' or 'ff' we always display 2 digits.
2523		 * If you use 'h', 'm', 's' or 'f' we display 1 digit if possible.
2524		 *
2525		 * Example to display 75 seconds:
2526		 * Format 'mm:ss': 01:15
2527		 * Format 'm:ss': 1:15
2528		 * Format 'm:s': 1:15
2529		 */
2530		timeFormat: '',
2531		// forces the hour marker (##:00:00)
2532		alwaysShowHours: false,
2533		// show framecount in timecode (##:00:00:00)
2534		showTimecodeFrameCount: false,
2535		// used when showTimecodeFrameCount is set to true
2536		framesPerSecond: 25,
2537		// automatically calculate the width of the progress bar based on the sizes of other elements
2538		autosizeProgress : true,
2539		// Hide controls when playing and mouse is not over the video
2540		alwaysShowControls: false,
2541		// Display the video control
2542		hideVideoControlsOnLoad: false,
2543		// Enable click video element to toggle play/pause
2544		clickToPlayPause: true,
2545		// Time in ms to hide controls
2546		controlsTimeoutDefault: 1500,
2547		// Time in ms to trigger the timer when mouse moves
2548		controlsTimeoutMouseEnter: 2500,
2549		// Time in ms to trigger the timer when mouse leaves
2550		controlsTimeoutMouseLeave: 1000,
2551		// force iPad's native controls
2552		iPadUseNativeControls: false,
2553		// force iPhone's native controls
2554		iPhoneUseNativeControls: false,
2555		// force Android's native controls
2556		AndroidUseNativeControls: false,
2557		// features to show
2558		features: ['playpause','current','progress','duration','tracks','volume','fullscreen'],
2559		// only for dynamic
2560		isVideo: true,
2561		// stretching modes (auto, fill, responsive, none)
2562		stretching: 'auto',
2563		// turns keyboard support on and off for this instance
2564		enableKeyboard: true,
2565		// when this player starts, it will pause other players
2566		pauseOtherPlayers: true,
2567		// array of keyboard actions such as play pause
2568		keyActions: [
2569				{
2570						keys: [
2571								32, // SPACE
2572								179 // GOOGLE play/pause button
2573								 ],
2574						action: function(player, media, key, event) {
2575
2576							if (!mejs.MediaFeatures.isFirefox) {
2577								if (media.paused || media.ended) {
2578									media.play();
2579								} else {
2580									media.pause();
2581								}
2582							}
2583						}
2584				},
2585				{
2586						keys: [38], // UP
2587						action: function(player, media, key, event) {
2588								player.container.find('.mejs-volume-slider').css('display','block');
2589								if (player.isVideo) {
2590										player.showControls();
2591										player.startControlsTimer();
2592								}
2593
2594								var newVolume = Math.min(media.volume + 0.1, 1);
2595								media.setVolume(newVolume);
2596						}
2597				},
2598				{
2599						keys: [40], // DOWN
2600						action: function(player, media, key, event) {
2601								player.container.find('.mejs-volume-slider').css('display','block');
2602								if (player.isVideo) {
2603										player.showControls();
2604										player.startControlsTimer();
2605								}
2606
2607								var newVolume = Math.max(media.volume - 0.1, 0);
2608								media.setVolume(newVolume);
2609						}
2610				},
2611				{
2612						keys: [
2613								37, // LEFT
2614								227 // Google TV rewind
2615						],
2616						action: function(player, media, key, event) {
2617								if (!isNaN(media.duration) && media.duration > 0) {
2618										if (player.isVideo) {
2619												player.showControls();
2620												player.startControlsTimer();
2621										}
2622
2623										// 5%
2624										var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0);
2625										media.setCurrentTime(newTime);
2626								}
2627						}
2628				},
2629				{
2630						keys: [
2631								39, // RIGHT
2632								228 // Google TV forward
2633						],
2634						action: function(player, media, key, event) {
2635								if (!isNaN(media.duration) && media.duration > 0) {
2636										if (player.isVideo) {
2637												player.showControls();
2638												player.startControlsTimer();
2639										}
2640
2641										// 5%
2642										var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration);
2643										media.setCurrentTime(newTime);
2644								}
2645						}
2646				},
2647				{
2648						keys: [70], // F
2649						action: function(player, media, key, event) {
2650								if (typeof player.enterFullScreen != 'undefined') {
2651										if (player.isFullScreen) {
2652												player.exitFullScreen();
2653										} else {
2654												player.enterFullScreen();
2655										}
2656								}
2657						}
2658				},
2659				{
2660						keys: [77], // M
2661						action: function(player, media, key, event) {
2662								player.container.find('.mejs-volume-slider').css('display','block');
2663								if (player.isVideo) {
2664										player.showControls();
2665										player.startControlsTimer();
2666								}
2667								if (player.media.muted) {
2668										player.setMuted(false);
2669								} else {
2670										player.setMuted(true);
2671								}
2672						}
2673				}
2674		]
2675	};
2676
2677	mejs.mepIndex = 0;
2678
2679	mejs.players = {};
2680
2681	// wraps a MediaElement object in player controls
2682	mejs.MediaElementPlayer = function(node, o) {
2683		// enforce object, even without "new" (via John Resig)
2684		if ( !(this instanceof mejs.MediaElementPlayer) ) {
2685			return new mejs.MediaElementPlayer(node, o);
2686		}
2687
2688		var t = this;
2689
2690		// these will be reset after the MediaElement.success fires
2691		t.$media = t.$node = $(node);
2692		t.node = t.media = t.$media[0];
2693
2694		if(!t.node) {
2695			return;
2696		}
2697
2698		// check for existing player
2699		if (typeof t.node.player != 'undefined') {
2700			return t.node.player;
2701		}
2702
2703
2704		// try to get options from data-mejsoptions
2705		if (typeof o == 'undefined') {
2706			o = t.$node.data('mejsoptions');
2707		}
2708
2709		// extend default options
2710		t.options = $.extend({},mejs.MepDefaults,o);
2711
2712		if (!t.options.timeFormat) {
2713			// Generate the time format according to options
2714			t.options.timeFormat = 'mm:ss';
2715			if (t.options.alwaysShowHours) {
2716				t.options.timeFormat = 'hh:mm:ss';
2717			}
2718			if (t.options.showTimecodeFrameCount) {
2719				t.options.timeFormat += ':ff';
2720			}
2721		}
2722
2723		mejs.Utility.calculateTimeFormat(0, t.options, t.options.framesPerSecond || 25);
2724
2725		// unique ID
2726		t.id = 'mep_' + mejs.mepIndex++;
2727
2728		// add to player array (for focus events)
2729		mejs.players[t.id] = t;
2730
2731		// start up
2732		t.init();
2733
2734		return t;
2735	};
2736
2737	// actual player
2738	mejs.MediaElementPlayer.prototype = {
2739
2740		hasFocus: false,
2741
2742		controlsAreVisible: true,
2743
2744		init: function() {
2745
2746			var
2747				t = this,
2748				mf = mejs.MediaFeatures,
2749				// options for MediaElement (shim)
2750				meOptions = $.extend(true, {}, t.options, {
2751					success: function(media, domNode) { t.meReady(media, domNode); },
2752					error: function(e) { t.handleError(e);}
2753				}),
2754				tagName = t.media.tagName.toLowerCase();
2755
2756			t.isDynamic = (tagName !== 'audio' && tagName !== 'video');
2757
2758			if (t.isDynamic) {
2759				// get video from src or href?
2760				t.isVideo = t.options.isVideo;
2761			} else {
2762				t.isVideo = (tagName !== 'audio' && t.options.isVideo);
2763			}
2764
2765			// use native controls in iPad, iPhone, and Android
2766			if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) {
2767
2768				// add controls and stop
2769				t.$media.attr('controls', 'controls');
2770
2771				// attempt to fix iOS 3 bug
2772				//t.$media.removeAttr('poster');
2773								// no Issue found on iOS3 -ttroxell
2774
2775				// override Apple's autoplay override for iPads
2776				if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
2777					t.play();
2778				}
2779
2780			} else if (mf.isAndroid && t.options.AndroidUseNativeControls) {
2781
2782				// leave default player
2783
2784			} else if (t.isVideo || (!t.isVideo && t.options.features.length)) {
2785
2786				// DESKTOP: use MediaElementPlayer controls
2787
2788				// remove native controls
2789				t.$media.removeAttr('controls');
2790				var videoPlayerTitle = t.isVideo ?
2791					mejs.i18n.t('mejs.video-player') : mejs.i18n.t('mejs.audio-player');
2792				// insert description for screen readers
2793				$('<span class="mejs-offscreen">' + videoPlayerTitle + '</span>').insertBefore(t.$media);
2794				// build container
2795				t.container =
2796					$('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svgAsImg ? 'svg' : 'no-svg') +
2797					  '" tabindex="0" role="application" aria-label="' + videoPlayerTitle + '">
2797'+
2798						'<div class="mejs-inner">'+
2799							'<div class="mejs-mediaelement"></div>'+
2800							'<div class="mejs-layers"></div>'+
2801							'<div class="mejs-controls"></div>'+
2802							'<div class="mejs-clear"></div>'+
2803						'</div>' +
2804					'</div>')
2805					.addClass(t.$media[0].className)
2806					.insertBefore(t.$media)
2807					.focus(function ( e ) {
2808						if( !t.controlsAreVisible && !t.hasFocus && t.controlsEnabled) {
2809							t.showControls(true);
2810							// In versions older than IE11, the focus causes the playbar to be displayed
2811							// if user clicks on the Play/Pause button in the control bar once it attempts
2812							// to hide it
2813							if (!t.hasMsNativeFullScreen) {
2814								// If e.relatedTarget appears before container, send focus to play button,
2815								// else send focus to last control button.
2816								var btnSelector = '.mejs-playpause-button > button';
2817
2818								if (mejs.Utility.isNodeAfter(e.relatedTarget, t.container[0])) {
2819									btnSelector = '.mejs-controls .mejs-button:last-child > button';
2820								}
2821
2822								var button = t.container.find(btnSelector);
2823								button.focus();
2824							}
2825						}
2826					});
2827
2828				// When no elements in controls, hide bar completely
2829				if (!t.options.features.length) {
2830					t.container.css('background', 'transparent').find('.mejs-controls').hide();
2831				}
2832 
2833				if (t.isVideo && t.options.stretching === 'fill' && !t.container.parent('mejs-fill-container').length) {
2834					// outer container
2835					t.outerContainer = t.$media.parent();
2836					t.container.wrap('<div class="mejs-fill-container"/>');
2837				}
2838
2839				// add classes for user and content
2840				t.container.addClass(
2841					(mf.isAndroid ? 'mejs-android ' : '') +
2842					(mf.isiOS ? 'mejs-ios ' : '') +
2843					(mf.isiPad ? 'mejs-ipad ' : '') +
2844					(mf.isiPhone ? 'mejs-iphone ' : '') +
2845					(t.isVideo ? 'mejs-video ' : 'mejs-audio ')
2846				);
2847
2848
2849				// move the <video/video> tag into the right spot
2850				t.container.find('.mejs-mediaelement').append(t.$media);
2851
2852				// needs to be assigned here, after iOS remap
2853				t.node.player = t;
2854
2855				// find parts
2856				t.controls = t.container.find('.mejs-controls');
2857				t.layers = t.container.find('.mejs-layers');
2858
2859				// determine the size
2860
2861				/* size priority:
2862					(1) videoWidth (forced),
2863					(2) style="width;height;"
2864					(3) width attribute,
2865					(4) defaultVideoWidth (for unspecified cases)
2866				*/
2867
2868				var tagType = (t.isVideo ? 'video' : 'audio'),
2869					capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1);
2870
2871
2872
2873				if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) {
2874					t.width = t.options[tagType + 'Width'];
2875				} else if (t.media.style.width !== '' && t.media.style.width !== null) {
2876					t.width = t.media.style.width;
2877				} else if (t.media.getAttribute('width') !== null) {
2878					t.width = t.$media.attr('width');
2879				} else {
2880					t.width = t.options['default' + capsTagName + 'Width'];
2881				}
2882
2883				if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) {
2884					t.height = t.options[tagType + 'Height'];
2885				} else if (t.media.style.height !== '' && t.media.style.height !== null) {
2886					t.height = t.media.style.height;
2887				} else if (t.$media[0].getAttribute('height') !== null) {
2888					t.height = t.$media.attr('height');
2889				} else {
2890					t.height = t.options['default' + capsTagName + 'Height'];
2891				}
2892
2893				// set the size, while we wait for the plugins to load below
2894				t.setPlayerSize(t.width, t.height);
2895
2896				// create MediaElementShim
2897				meOptions.pluginWidth = t.width;
2898				meOptions.pluginHeight = t.height;
2899			}
2900			// Hide media completely for audio that doesn't have any features
2901			else if (!t.isVideo && !t.options.features.length) {
2902				t.$media.hide();
2903			}
2904
2905			// create MediaElement shim
2906			mejs.MediaElement(t.$media[0], meOptions);
2907
2908			if (typeof(t.container) !== 'undefined' && t.options.features.length && t.controlsAreVisible) {
2909				// controls are shown when loaded
2910				t.container.trigger('controlsshown');
2911			}
2912		},
2913
2914		showControls: function(doAnimation) {
2915			var t = this;
2916
2917			doAnimation = typeof doAnimation == 'undefined' || doAnimation;
2918
2919			if (t.controlsAreVisible)
2920				return;
2921
2922			if (doAnimation) {
2923				t.controls
2924					.removeClass('mejs-offscreen')
2925					.stop(true, true).fadeIn(200, function() {
2926						t.controlsAreVisible = true;
2927						t.container.trigger('controlsshown');
2928					});
2929
2930				// any additional controls people might add and want to hide
2931				t.container.find('.mejs-control')
2932					.removeClass('mejs-offscreen')
2933					.stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;});
2934
2935			} else {
2936				t.controls
2937					.removeClass('mejs-offscreen')
2938					.css('display','block');
2939
2940				// any additional controls people might add and want to hide
2941				t.container.find('.mejs-control')
2942					.removeClass('mejs-offscreen')
2943					.css('display','block');
2944
2945				t.controlsAreVisible = true;
2946				t.container.trigger('controlsshown');
2947			}
2948
2949			t.setControlsSize();
2950
2951		},
2952
2953		hideControls: function(doAnimation) {
2954			var t = this;
2955
2956			doAnimation = typeof doAnimation == 'undefined' || doAnimation;
2957
2958			if (!t.controlsAreVisible || t.options.alwaysShowControls || t.keyboardAction || t.media.paused || t.media.ended)
2959				return;
2960
2961			if (doAnimation) {
2962				// fade out main controls
2963				t.controls.stop(true, true).fadeOut(200, function() {
2964					$(this)
2965						.addClass('mejs-offscreen')
2966						.css('display','block');
2967
2968					t.controlsAreVisible = false;
2969					t.container.trigger('controlshidden');
2970				});
2971
2972				// any additional controls people might add and want to hide
2973				t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() {
2974					$(this)
2975						.addClass('mejs-offscreen')
2976						.css('display','block');
2977				});
2978			} else {
2979
2980				// hide main controls
2981				t.controls
2982					.addClass('mejs-offscreen')
2983					.css('display','block');
2984
2985				// hide others
2986				t.container.find('.mejs-control')
2987					.addClass('mejs-offscreen')
2988					.css('display','block');
2989
2990				t.controlsAreVisible = false;
2991				t.container.trigger('controlshidden');
2992			}
2993		},
2994
2995		controlsTimer: null,
2996
2997		startControlsTimer: function(timeout) {
2998
2999			var t = this;
3000
3001			timeout = typeof timeout != 'undefined' ? timeout : t.options.controlsTimeoutDefault;
3002
3003			t.killControlsTimer('start');
3004
3005			t.controlsTimer = setTimeout(function() {
3006				//
3007				t.hideControls();
3008				t.killControlsTimer('hide');
3009			}, timeout);
3010		},
3011
3012		killControlsTimer: function(src) {
3013
3014			var t = this;
3015
3016			if (t.controlsTimer !== null) {
3017				clearTimeout(t.controlsTimer);
3018				delete t.controlsTimer;
3019				t.controlsTimer = null;
3020			}
3021		},
3022
3023		controlsEnabled: true,
3024
3025		disableControls: function() {
3026			var t= this;
3027
3028			t.killControlsTimer();
3029			t.hideControls(false);
3030			this.controlsEnabled = false;
3031		},
3032
3033		enableControls: function() {
3034			var t= this;
3035
3036			t.showControls(false);
3037
3038			t.controlsEnabled = true;
3039		},
3040
3041		// Sets up all controls and events
3042		meReady: function(media, domNode) {
3043			
3044			var
3045				t = this,
3046				mf = mejs.MediaFeatures,
3047				autoplayAttr = domNode.getAttribute('autoplay'),
3048				autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'),
3049				featureIndex,
3050				feature;
3051
3052			// make sure it can't create itself again if a plugin reloads
3053			if (t.created) {
3054				return;
3055			} else {
3056				t.created = true;
3057			}
3058
3059			t.media = media;
3060			t.domNode = domNode;
3061
3062			if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) {
3063
3064				// In the event that no features are specified for audio,
3065				// create only MediaElement instance rather than
3066				// doing all the work to create a full player
3067				if (!t.isVideo && !t.options.features.length) {
3068
3069					// force autoplay for HTML5
3070					if (autoplay && media.pluginType == 'native') {
3071						t.play();
3072					}
3073
3074
3075					if (t.options.success) {
3076
3077						if (typeof t.options.success == 'string') {
3078							window[t.options.success](t.media, t.domNode, t);
3079						} else {
3080							t.options.success(t.media, t.domNode, t);
3081						}
3082					}
3083
3084					return;
3085				}
3086
3087				// two built in features
3088				t.buildposter(t, t.controls, t.layers, t.media);
3089				t.buildkeyboard(t, t.controls, t.layers, t.media);
3090				t.buildoverlays(t, t.controls, t.layers, t.media);
3091
3092				// grab for use by features
3093				t.findTracks();
3094
3095				// add user-defined features/controls
3096				for (featureIndex in t.options.features) {
3097					feature = t.options.features[featureIndex];
3098					if (t['build' + feature]) {
3099						try {
3100							t['build' + feature](t, t.controls, t.layers, t.media);
3101						} catch (e) {
3102							// TODO: report control error
3103							//throw e;
3104							
3105							
3106						}
3107					}
3108				}
3109
3110				t.container.trigger('controlsready');
3111
3112				// reset all layers and controls
3113				t.setPlayerSize(t.width, t.height);
3114				t.setControlsSize();
3115
3116
3117				// controls fade
3118				if (t.isVideo) {
3119
3120					if (mejs.MediaFeatures.hasTouch && !t.options.alwaysShowControls) {
3121
3122						// for touch devices (iOS, Android)
3123						// show/hide without animation on touch
3124
3125						t.$media.bind('touchstart', function() {
3126
3127							// toggle controls
3128							if (t.controlsAreVisible) {
3129								t.hideControls(false);
3130							} else {
3131								if (t.controlsEnabled) {
3132									t.showControls(false);
3133								}
3134							}
3135						});
3136
3137					} else {
3138
3139						// create callback here since it needs access to current
3140						// MediaElement object
3141						t.clickToPlayPauseCallback = function() {
3142							//
3143
3144							if (t.options.clickToPlayPause) {
3145								if (t.media.paused) {
3146									t.play();
3147								} else {
3148									t.pause();
3149								}
3150
3151								var button = t.$media.closest('.mejs-container').find('.mejs-overlay-button'),
3152									pressed = button.attr('aria-pressed');
3153								button.attr('aria-pressed', !pressed);
3154							}
3155						};
3156
3157						// click to play/pause
3158						t.media.addEventListener('click', t.clickToPlayPauseCallback, false);
3159
3160						// show/hide controls
3161						t.container
3162							.bind('mouseenter', function () {
3163								if (t.controlsEnabled) {
3164									if (!t.options.alwaysShowControls ) {
3165										t.killControlsTimer('enter');
3166										t.showControls();
3167										t.startControlsTimer(t.options.controlsTimeoutMouseEnter);
3168									}
3169								}
3170							})
3171							.bind('mousemove', function() {
3172								if (t.controlsEnabled) {
3173									if (!t.controlsAreVisible) {
3174										t.showControls();
3175									}
3176									if (!t.options.alwaysShowControls) {
3177										t.startControlsTimer(t.options.controlsTimeoutMouseEnter);
3178									}
3179								}
3180							})
3181							.bind('mouseleave', function () {
3182								if (t.controlsEnabled) {
3183									if (!t.media.paused && !t.options.alwaysShowControls) {
3184										t.startControlsTimer(t.options.controlsTimeoutMouseLeave);
3185									}
3186								}
3187							});
3188					}
3189
3190					if(t.options.hideVideoControlsOnLoad) {
3191						t.hideControls(false);
3192					}
3193
3194					// check for autoplay
3195					if (autoplay && !t.options.alwaysShowControls) {
3196						t.hideControls();
3197					}
3198
3199					// resizer
3200					if (t.options.enableAutosize) {
3201						t.media.addEventListener('loadedmetadata', function(e) {
3202							// if the <video height> was not set and the options.videoHeight was not set
3203							// then resize to the real dimensions
3204							if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
3205								t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
3206								t.setControlsSize();
3207								t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
3208							}
3209						}, false);
3210					}
3211				}
3212
3213				// EVENTS
3214
3215				// FOCUS: when a video starts playing, it takes focus from other players (possibly pausing them)
3216				t.media.addEventListener('play', function() {
3217					var playerIndex;
3218
3219					// go through all other players
3220					for (playerIndex in mejs.players) {
3221						var p = mejs.players[playerIndex];
3222						if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) {
3223							p.pause();
3224						}
3225						p.hasFocus = false;
3226					}
3227
3228					t.hasFocus = true;
3229				},false);
3230
3231
3232				// ended for all
3233				t.media.addEventListener('ended', function (e) {
3234					if(t.options.autoRewind) {
3235						try{
3236							t.media.setCurrentTime(0);
3237							// Fixing an Android stock browser bug, where "seeked" isn't fired correctly after ending the video and jumping to the beginning
3238							window.setTimeout(function(){
3239								$(t.container).find('.mejs-overlay-loading').parent().hide();
3240							}, 20);
3241						} catch (exp) {
3242
3243						}
3244					}
3245					if (t.media.pluginType === 'youtube') {
3246						t.media.stop();
3247					} else {
3248						t.media.pause();
3249					}
3250
3251					if (t.setProgressRail) {
3252						t.setProgressRail();
3253					}
3254					if (t.setCurrentRail) {
3255						t.setCurrentRail();
3256					}
3257
3258					if (t.options.loop) {
3259						t.play();
3260					} else if (!t.options.alwaysShowControls && t.controlsEnabled) {
3261						t.showControls();
3262					}
3263				}, false);
3264
3265				// resize on the first play
3266				t.media.addEventListener('loadedmetadata', function() {
3267
3268					mejs.Utility.calculateTimeFormat(t.duration, t.options, t.options.framesPerSecond || 25);
3269
3270					if (t.updateDuration) {
3271						t.updateDuration();
3272					}
3273					if (t.updateCurrent) {
3274						t.updateCurrent();
3275					}
3276
3277					if (!t.isFullScreen) {
3278						t.setPlayerSize(t.width, t.height);
3279						t.setControlsSize();
3280					}
3281				}, false);
3282
3283				// Only change the time format when necessary
3284				var duration = null;
3285				t.media.addEventListener('timeupdate',function() {
3286					if (duration !== this.duration) {
3287						duration = this.duration;
3288						mejs.Utility.calculateTimeFormat(duration, t.options, t.options.framesPerSecond || 25);
3289						
3290						// make sure to fill in and resize the controls (e.g., 00:00 => 01:13:15
3291						if (t.updateDuration) {
3292							t.updateDuration();
3293						}
3294						if (t.updateCurrent) {
3295							t.updateCurrent();
3296						}
3297						t.setControlsSize();
3298						
3299					}
3300				}, false);
3301
3302				t.container.focusout(function (e) {
3303					if( e.relatedTarget ) { //FF is working on supporting focusout https://bugzilla.mozilla.org/show_bug.cgi?id=687787
3304						var $target = $(e.relatedTarget);
3305						if (t.keyboardAction && $target.parents('.mejs-container').length === 0) {
3306							t.keyboardAction = false;
3307							if (t.isVideo && !t.options.alwaysShowControls) {
3308								t.hideControls(true);
3309							}
3310
3311						}
3312					}
3313				});
3314
3315				// webkit has trouble doing this without a delay
3316				setTimeout(function () {
3317					t.setPlayerSize(t.width, t.height);
3318					t.setControlsSize();
3319				}, 50);
3320
3321				// adjust controls whenever window sizes (used to be in fullscreen only)
3322				t.globalBind('resize', function() {
3323
3324					// don't resize for fullscreen mode
3325					if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) {
3326						t.setPlayerSize(t.width, t.height);
3327					}
3328
3329					// always adjust controls
3330					t.setControlsSize();
3331				});
3332
3333				// This is a work-around for a bug in the YouTube iFrame player, which means
3334				//	we can't use the play() API for the initial playback on iOS or Android;
3335				//	user has to start playback directly by tapping on the iFrame.
3336				if (t.media.pluginType == 'youtube' && ( mf.isiOS || mf.isAndroid ) ) {
3337					t.container.find('.mejs-overlay-play').hide();
3338					t.container.find('.mejs-poster').hide();
3339				}
3340			}
3341
3342			// force autoplay for HTML5
3343			if (autoplay && media.pluginType == 'native') {
3344				t.play();
3345			}
3346
3347
3348			if (t.options.success) {
3349
3350				if (typeof t.options.success == 'string') {
3351					window[t.options.success](t.media, t.domNode, t);
3352				} else {
3353					t.options.success(t.media, t.domNode, t);
3354				}
3355			}
3356		},
3357
3358		handleError: function(e) {
3359			var t = this;
3360
3361			if (t.controls) {
3362				t.controls.hide();
3363			}
3364
3365			// Tell user that the file cannot be played
3366			if (t.options.error) {
3367				t.options.error(e);
3368			}
3369		},
3370
3371		setPlayerSize: function(width,height) {
3372			var t = this;
3373
3374			if( !t.options.setDimensions ) {
3375				return false;
3376			}
3377
3378			if (typeof width != 'undefined') {
3379				t.width = width;
3380			}
3381
3382			if (typeof height != 'undefined') {
3383				t.height = height;
3384			}
3385 
3386			// check stretching modes
3387			switch (t.options.stretching) {
3388				case 'fill':
3389					// The 'fill' effect only makes sense on video; for audio we will set the dimensions
3390					if (t.isVideo) {
3391						this.setFillMode();
3392					} else {
3393						this.setDimensions(t.width, t.height);
3394					}
3395					break;
3396				case 'responsive':
3397					this.setResponsiveMode();
3398					break;
3399				case 'none':
3400					this.setDimensions(t.width, t.height);
3401					break;
3402				// This is the 'auto' mode
3403				default:
3404					if (this.hasFluidMode() === true) {
3405						this.setResponsiveMode();
3406					} else {
3407						this.setDimensions(t.width, t.height);
3408					}
3409					break;
3410			}
3411		},
3412 
3413		hasFluidMode: function() {
3414			var t = this;
3415	 
3416			// detect 100% mode - use currentStyle for IE since css() doesn't return percentages
3417			return (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%'));
3418		},
3419 
3420		setResponsiveMode: function() {
3421			var t = this;
3422		
3423			// do we have the native dimensions yet?
3424			var nativeWidth = (function() {
3425				if (t.isVideo) {
3426					if (t.media.videoWidth && t.media.videoWidth > 0) {
3427						return t.media.videoWidth;
3428					} else if (t.media.getAttribute('width') !== null) {
3429						return t.media.getAttribute('width');
3430					} else {
3431						return t.options.defaultVideoWidth;
3432					}
3433				} else {
3434					return t.options.defaultAudioWidth;
3435				}
3436			})();
3437		
3438			var nativeHeight = (function() {
3439				if (t.isVideo) {
3440					if (t.media.videoHeight && t.media.videoHeight > 0) {
3441						return t.media.videoHeight;
3442					} else if (t.media.getAttribute('height') !== null) {
3443						return t.media.getAttribute('height');
3444					} else {
3445						return t.options.defaultVideoHeight;
3446					}
3447				} else {
3448					return t.options.defaultAudioHeight;
3449				}
3450			})();
3451		
3452			var parentWidth = t.container.parent().closest(':visible').width(),
3453			parentHeight = t.container.parent().closest(':visible').height(),
3454			newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight;
3455			
3456			// When we use percent, the newHeight can't be calculated so we get the container height
3457			if (isNaN(newHeight) || ( parentHeight !== 0 && newHeight > parentHeight && parentHeight > nativeHeight)) {
3458				newHeight = parentHeight;
3459			}
3460		
3461			if (t.container.parent().length > 0 && t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
3462				parentWidth = $(window).width();
3463				newHeight = $(window).height();
3464			}
3465		
3466			if ( newHeight && parentWidth ) {
3467			
3468				// set outer container size
3469				t.container
3470					.width(parentWidth)
3471					.height(newHeight);
3472				
3473				// set native <video> or <audio> and shims
3474				t.$media.add(t.container.find('.mejs-shim'))
3475					.width('100%')
3476					.height('100%');
3477				
3478				// if shim is ready, send the size to the embeded plugin
3479				if (t.isVideo) {
3480					if (t.media.setVideoSize) {
3481						t.media.setVideoSize(parentWidth, newHeight);
3482					}
3483				}
3484		
3485				// set the layers
3486				t.layers.children('.mejs-layer')
3487					.width('100%')
3488					.height('100%');
3489			}
3490		},
3491 
3492		setFillMode: function() {
3493			var t = this,
3494				parent = t.outerContainer;
3495 
3496			if (!parent.width()) {
3497				parent.height(t.$media.width());
3498			}
3499 
3500			if (!parent.height()) {
3501				parent.height(t.$media.height());
3502			}
3503 
3504			var parentWidth = parent.width(),
3505				parentHeight = parent.height();
3506			
3507			t.setDimensions('100%', '100%');
3508			
3509			// This prevents an issue when displaying poster
3510			t.container.find('.mejs-poster img').css('display', 'block');
3511			
3512			targetElement = t.container.find('object, embed, iframe, video');
3513			
3514			// calculate new width and height
3515			var initHeight = t.height,
3516				initWidth = t.width,
3517				// scale to the target width
3518				scaleX1 = parentWidth,
3519				scaleY1 = (initHeight * parentWidth) / initWidth,
3520				// scale to the target height
3521				scaleX2 = (initWidth * parentHeight) / initHeight,
3522				scaleY2 = parentHeight,
3523				// now figure out which one we should use
3524				bScaleOnWidth = !(scaleX2 > parentWidth),
3525				finalWidth = bScaleOnWidth ? Math.floor(scaleX1) : Math.floor(scaleX2),
3526				finalHeight = bScaleOnWidth ? Math.floor(scaleY1) : Math.floor(scaleY2);
3527			
3528			if (bScaleOnWidth) {
3529				targetElement.height(finalHeight).width(parentWidth);
3530				if (t.media.setVideoSize) {
3531					t.media.setVideoSize(parentWidth, finalHeight);
3532				}
3533			} else {
3534				targetElement.height(parentHeight).width(finalWidth);
3535				if (t.media.setVideoSize) {
3536					t.media.setVideoSize(finalWidth, parentHeight);
3537				}
3538			}
3539			
3540			targetElement.css({
3541				'margin-left': Math.floor((parentWidth - finalWidth) / 2),
3542				'margin-top': 0
3543			});
3544		},
3545	 
3546		setDimensions: function(width, height) {
3547			var t = this;
3548			
3549			t.container
3550				.width(width)
3551				.height(height);
3552			
3553			t.layers.children('.mejs-layer')
3554				.width(width)
3555				.height(height);
3556		},
3557
3558		setControlsSize: function() {
3559			var t = this,
3560				usedWidth = 0,
3561				railWidth = 0,
3562				rail = t.controls.find('.mejs-time-rail'),
3563				total = t.controls.find('.mejs-time-total'),
3564				others = rail.siblings(),
3565				lastControl = others.last(),
3566				lastControlPosition = null,
3567				avoidAutosizeProgress = t.options && !t.options.autosizeProgress;
3568
3569			// skip calculation if hidden
3570			if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) {
3571				return;
3572			}
3573
3574			// allow the size to come from custom CSS
3575			if (avoidAutosizeProgress) {
3576				// Also, frontends devs can be more flexible
3577				// due the opportunity of absolute positioning.
3578				railWidth = parseInt(rail.css('width'), 10);
3579			}
3580
3581			// attempt to autosize
3582			if (railWidth === 0 || !railWidth) {
3583
3584				// find the size of all the other controls besides the rail
3585				others.each(function() {
3586					var $this = $(this);
3587					if ($this.css('position') != 'absolute' && $this.is(':visible')) {
3588						usedWidth += $(this).outerWidth(true);
3589					}
3590				});
3591
3592				// fit the rail into the remaining space
3593				railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width());
3594			}
3595
3596			// resize the rail,
3597			// but then check if the last control (say, the fullscreen button) got pushed down
3598			// this often happens when zoomed
3599			do {
3600				// outer area
3601				// we only want to set an inline style with the width of the rail
3602				// if we're trying to autosize.
3603				if (!avoidAutosizeProgress) {
3604					rail.width(railWidth);
3605				}
3606
3607				// dark space
3608				total.width(railWidth - (total.outerWidth(true) - total.width()));
3609
3610				if (lastControl.css('position') != 'absolute') {
3611					lastControlPosition = lastControl.length ? lastControl.position() : null;
3612					railWidth--;
3613				}
3614			} while (lastControlPosition !== null && lastControlPosition.top.toFixed(2) > 0 && railWidth > 0);
3615
3616			t.container.trigger('controlsresize');
3617		},
3618
3619
3620		buildposter: function(player, controls, layers, media) {
3621			var t = this,
3622				poster =
3623				$('<div class="mejs-poster mejs-layer">' +
3624				'</div>')
3625					.appendTo(layers),
3626				posterUrl = player.$media.attr('poster');
3627
3628			// prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
3629			if (player.options.poster !== '') {
3630				posterUrl = player.options.poster;
3631			}
3632
3633			// second, try the real poster
3634			if ( posterUrl ) {
3635				t.setPoster(posterUrl);
3636			} else {
3637				poster.hide();
3638			}
3639
3640			media.addEventListener('play',function() {
3641				poster.hide();
3642			}, false);
3643
3644			if(player.options.showPosterWhenEnded && player.options.autoRewind){
3645				media.addEventListener('ended',function() {
3646					poster.show();
3647				}, false);
3648			}
3649		},
3650
3651		setPoster: function(url) {
3652			var t = this,
3653				posterDiv = t.container.find('.mejs-poster'),
3654				posterImg = posterDiv.find('img');
3655
3656			if (posterImg.length === 0) {
3657				posterImg = $('<img width="100%" height="100%" alt="" />').appendTo(posterDiv);
3658			}
3659
3660			posterImg.attr('src', url);
3661			posterDiv.css({'background-image' : 'url(' + url + ')'});
3662		},
3663
3664		buildoverlays: function(player, controls, layers, media) {
3665			var t = this;
3666			if (!player.isVideo)
3667				return;
3668
3669			var
3670			loading =
3671				$('<div class="mejs-overlay mejs-layer">'+
3672					'<div class="mejs-overlay-loading"><span></span></div>'+
3673				'</div>')
3674				.hide() // start out hidden
3675				.appendTo(layers),
3676			error =
3677				$('<div class="mejs-overlay mejs-layer">'+
3678					'<div class="mejs-overlay-error"></div>'+
3679				'</div>')
3680				.hide() // start out hidden
3681				.appendTo(layers),
3682			// this needs to come last so it's on top
3683			bigPlay =
3684				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
3685					'<div class="mejs-overlay-button" role="button" aria-label="' + mejs.i18n.t('mejs.play') + '" aria-pressed="false"></div>'+
3686				'</div>')
3687				.appendTo(layers)
3688				.bind('click', function() {	 // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video
3689					if (t.options.clickToPlayPause) {
3690						if (media.paused) {
3691							media.play();
3692						}
3693
3694						var button = $(this).find('.mejs-overlay-button'),
3695							pressed = button.attr('aria-pressed');
3696						button.attr('aria-pressed', !!pressed);
3697					}
3698				});
3699
3700			/*
3701			if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) {
3702				bigPlay.remove();
3703				loading.remove();
3704			}
3705			*/
3706
3707
3708			// show/hide big play button
3709			media.addEventListener('play',function() {
3710				bigPlay.hide();
3711				loading.hide();
3712				controls.find('.mejs-time-buffering').hide();
3713				error.hide();
3714			}, false);
3715
3716			media.addEventListener('playing', function() {
3717				bigPlay.hide();
3718				loading.hide();
3719				controls.find('.mejs-time-buffering').hide();
3720				error.hide();
3721			}, false);
3722
3723			media.addEventListener('seeking', function() {
3724				loading.show();
3725				controls.find('.mejs-time-buffering').show();
3726			}, false);
3727
3728			media.addEventListener('seeked', function() {
3729				loading.hide();
3730				controls.find('.mejs-time-buffering').hide();
3731			}, false);
3732
3733			media.addEventListener('pause',function() {
3734				if (!mejs.MediaFeatures.isiPhone) {
3735					bigPlay.show();
3736				}
3737			}, false);
3738
3739			media.addEventListener('waiting', function() {
3740				loading.show();
3741				controls.find('.mejs-time-buffering').show();
3742			}, false);
3743
3744
3745			// show/hide loading
3746			media.addEventListener('loadeddata',function() {
3747				// for some reason Chrome is firing this event
3748				//if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
3749				//	return;
3750
3751				loading.show();
3752				controls.find('.mejs-time-buffering').show();
3753				// 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)
3754				if (mejs.MediaFeatures.isAndroid) {
3755					media.canplayTimeout = window.setTimeout(
3756						function() {
3757							if (document.createEvent) {
3758								var evt = document.createEvent('HTMLEvents');
3759								evt.initEvent('canplay', true, true);
3760								return media.dispatchEvent(evt);
3761							}
3762						}, 300
3763					);
3764				}
3765			}, false);
3766			media.addEventListener('canplay',function() {
3767				loading.hide();
3768				controls.find('.mejs-time-buffering').hide();
3769				clearTimeout(media.canplayTimeout); // Clear timeout inside 'loadeddata' to prevent 'canplay' to fire twice
3770			}, false);
3771
3772			// error handling
3773			media.addEventListener('error',function(e) {
3774				t.handleError(e);
3775				loading.hide();
3776				bigPlay.hide();
3777				error.show();
3778				error.find('.mejs-overlay-error').html("Error loading this resource");
3779			}, false);
3780
3781			media.addEventListener('keydown', function(e) {
3782				t.onkeydown(player, media, e);
3783			}, false);
3784		},
3785
3786		buildkeyboard: function(player, controls, layers, media) {
3787
3788				var t = this;
3789
3790				t.container.keydown(function () {
3791					t.keyboardAction = true;
3792				});
3793
3794				// listen for key presses
3795				t.globalBind('keydown', function(event) {
3796					player.hasFocus = $(event.target).closest('.mejs-container').length !== 0
3797						&& $(event.target).closest('.mejs-container').attr('id') === player.$media.closest('.mejs-container').attr('id');
3798					return t.onkeydown(player, media, event);
3799				});
3800
3801
3802				// check if someone clicked outside a player region, then kill its focus
3803				t.globalBind('click', function(event) {
3804					player.hasFocus = $(event.target).closest('.mejs-container').length !== 0;
3805				});
3806
3807		},
3808		onkeydown: function(player, media, e) {
3809			if (player.hasFocus && player.options.enableKeyboard) {
3810				// find a matching key
3811				for (var i = 0, il = player.options.keyActions.length; i < il; i++) {
3812					var keyAction = player.options.keyActions[i];
3813
3814					for (var j = 0, jl = keyAction.keys.length; j < jl; j++) {
3815						if (e.keyCode == keyAction.keys[j]) {
3816							if (typeof(e.preventDefault) == "function") e.preventDefault();
3817							keyAction.action(player, media, e.keyCode, e);
3818							return false;
3819						}
3820					}
3821				}
3822			}
3823
3824			return true;
3825		},
3826
3827		findTracks: function() {
3828			var t = this,
3829				tracktags = t.$media.find('track');
3830
3831			// store for use by plugins
3832			t.tracks = [];
3833			tracktags.each(function(index, track) {
3834
3835				track = $(track);
3836
3837				t.tracks.push({
3838					srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '',
3839					src: track.attr('src'),
3840					kind: track.attr('kind'),
3841					label: track.attr('label') || '',
3842					entries: [],
3843					isLoaded: false
3844				});
3845			});
3846		},
3847		changeSkin: function(className) {
3848			this.container[0].className = 'mejs-container ' + className;
3849			this.setPlayerSize(this.width, this.height);
3850			this.setControlsSize();
3851		},
3852		play: function() {
3853			this.load();
3854			this.media.play();
3855		},
3856		pause: function() {
3857			try {
3858				this.media.pause();
3859			} catch (e) {}
3860		},
3861		load: function() {
3862			if (!this.isLoaded) {
3863				this.media.load();
3864			}
3865
3866			this.isLoaded = true;
3867		},
3868		setMuted: function(muted) {
3869			this.media.setMuted(muted);
3870		},
3871		setCurrentTime: function(time) {
3872			this.media.setCurrentTime(time);
3873		},
3874		getCurrentTime: function() {
3875			return this.media.currentTime;
3876		},
3877		setVolume: function(volume) {
3878			this.media.setVolume(volume);
3879		},
3880		getVolume: function() {
3881			return this.media.volume;
3882		},
3883		setSrc: function(src) {
3884			var
3885				t = this;
3886
3887			// If using YouTube, its API is different to load a specific source
3888			if (t.media.pluginType === 'youtube') {
3889				var videoId;
3890
3891				if (typeof src !== 'string') {
3892					var i, media;
3893
3894					for (i=0; i<src.length; i++) {
3895						media = src[i];
3896						if (this.canPlayType(media.type)) {
3897							src = media.src;
3898							break;
3899						}
3900					}
3901				}
3902
3903				// youtu.be url from share button
3904				if (src.lastIndexOf('youtu.be') !== -1) {
3905					videoId = src.substr(src.lastIndexOf('/') + 1);
3906
3907					if (videoId.indexOf('?') !== -1) {
3908						videoId = videoId.substr(0, videoId.indexOf('?'));
3909					}
3910
3911				} else {
3912					// https://www.youtube.com/watch?v=
3913					var videoIdMatch = src.match(/[?&]v=([^&#]+)|&|#|$/);
3914
3915					if (videoIdMatch) {
3916						videoId = videoIdMatch[1];
3917					}
3918				}
3919
3920				if (t.media.getAttribute('autoplay') !== null) {
3921					t.media.pluginApi.loadVideoById(videoId);
3922				} else {
3923					t.media.pluginApi.cueVideoById(videoId);
3924				}
3925
3926			}
3927			else {
3928				t.media.setSrc(src);
3929			}
3930		},
3931		remove: function() {
3932			var t = this, featureIndex, feature;
3933
3934			t.container.prev('.mejs-offscreen').remove();
3935
3936			// invoke features cleanup
3937			for (featureIndex in t.options.features) {
3938				feature = t.options.features[featureIndex];
3939				if (t['clean' + feature]) {
3940					try {
3941						t['clean' + feature](t);
3942					} catch (e) {
3943						// TODO: report control error
3944						//throw e;
3945						//
3946						//
3947					}
3948				}
3949			}
3950
3951			// grab video and put it back in place
3952			if (!t.isDynamic) {
3953				t.$media.prop('controls', true);
3954				// detach events from the video
3955				// TODO: detach event listeners better than this;
3956				//		 also detach ONLY the events attached by this plugin!
3957				t.$node.clone().insertBefore(t.container).show();
3958				t.$node.remove();
3959			} else {
3960				t.$node.insertBefore(t.container);
3961			}
3962
3963			if (t.media.pluginType !== 'native') {
3964				t.media.remove();
3965			}
3966
3967			// Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api.
3968			delete mejs.players[t.id];
3969
3970			if (typeof t.container == 'object') {
3971				t.container.remove();
3972			}
3973			t.globalUnbind();
3974			delete t.node.player;
3975		},
3976		rebuildtracks: function(){
3977			var t = this;
3978			t.findTracks();
3979			t.buildtracks(t, t.controls, t.layers, t.media);
3980		},
3981		resetSize: function(){
3982			var t = this;
3983			// webkit has trouble doing this without a delay
3984			setTimeout(function () {
3985				//
3986				t.setPlayerSize(t.width, t.height);
3987				t.setControlsSize();
3988			}, 50);
3989		}
3990	};
3991
3992	(function(){
3993		var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;
3994
3995		function splitEvents(events, id) {
3996			// add player ID as an event namespace so it's easier to unbind them all later
3997			var ret = {d: [], w: []};
3998			$.each((events || '').split(' '), function(k, v){
3999				var eventname = v + '.' + id;
4000				if (eventname.indexOf('.') === 0) {
4001					ret.d.push(eventname);
4002					ret.w.push(eventname);
4003				}
4004				else {
4005					ret[rwindow.test(v) ? 'w' : 'd'].push(eventname);
4006				}
4007			});
4008			ret.d = ret.d.join(' ');
4009			ret.w = ret.w.join(' ');
4010			return ret;
4011		}
4012
4013		mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) {
4014			var t = this;
4015			var doc = t.node ? t.node.ownerDocument : document;
4016
4017			events = splitEvents(events, t.id);
4018			if (events.d) $(doc).bind(events.d, data, callback);
4019			if (events.w) $(window).bind(events.w, data, callback);
4020		};
4021
4022		mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) {
4023			var t = this;
4024			var doc = t.node ? t.node.ownerDocument : document;
4025
4026			events = splitEvents(events, t.id);
4027			if (events.d) $(doc).unbind(events.d, callback);
4028			if (events.w) $(window).unbind(events.w, callback);
4029		};
4030	})();
4031
4032	// turn into jQuery plugin
4033	if (typeof $ != 'undefined') {
4034		$.fn.mediaelementplayer = function (options) {
4035			if (options === false) {
4036				this.each(function () {
4037					var player = $(this).data('mediaelementplayer');
4038					if (player) {
4039						player.remove();
4040					}
4041					$(this).removeData('mediaelementplayer');
4042				});
4043			}
4044			else {
4045				this.each(function () {
4046					$(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options));
4047				});
4048			}
4049			return this;
4050		};
4051
4052
4053		$(document).ready(function() {
4054			// auto enable using JSON attribute
4055			$('.mejs-player').mediaelementplayer();
4056		});
4057	}
4058
4059	// push out to window
4060	window.MediaElementPlayer = mejs.MediaElementPlayer;
4061
4062})(mejs.$);
4063
4064(function($) {
4065
4066	$.extend(mejs.MepDefaults, {
4067		playText: '',
4068		pauseText: ''
4069	});
4070
4071
4072	// PLAY/pause BUTTON
4073	$.extend(MediaElementPlayer.prototype, {
4074		buildplaypause: function(player, controls, layers, media) {
4075			var 
4076				t = this,
4077				op = t.options,
4078				playTitle = op.playText ? op.playText : mejs.i18n.t('mejs.play'),
4079				pauseTitle = op.pauseText ? op.pauseText : mejs.i18n.t('mejs.pause'),
4080				play =
4081				$('<div class="mejs-button mejs-playpause-button mejs-play" >' +
4082					'<button type="button" aria-controls="' + t.id + '" title="' + playTitle + '" aria-label="' + pauseTitle + '"></button>' +
4083				'</div>')
4084				.appendTo(controls)
4085				.click(function(e) {
4086					e.preventDefault();
4087				
4088					if (media.paused) {
4089						media.play();
4090					} else {
4091						media.pause();
4092					}
4093					
4094					return false;
4095				}),
4096				play_btn = play.find('button');
4097
4098
4099			function togglePlayPause(which) {
4100				if ('play' === which) {
4101					play.removeClass('mejs-play').addClass('mejs-pause');
4102					play_btn.attr({
4103						'title': pauseTitle,
4104						'aria-label': pauseTitle
4105					});
4106				} else {
4107					play.removeClass('mejs-pause').addClass('mejs-play');
4108					play_btn.attr({
4109						'title': playTitle,
4110						'aria-label': playTitle
4111					});
4112				}
4113			};
4114			togglePlayPause('pse');
4115
4116
4117			media.addEventListener('play',function() {
4118				togglePlayPause('play');
4119			}, false);
4120			media.addEventListener('playing',function() {
4121				togglePlayPause('play');
4122			}, false);
4123
4124
4125			media.addEventListener('pause',function() {
4126				togglePlayPause('pse');
4127			}, false);
4128			media.addEventListener('paused',function() {
4129				togglePlayPause('pse');
4130			}, false);
4131		}
4132	});
4133	
4134})(mejs.$);
4135
4136(function($) {
4137
4138	$.extend(mejs.MepDefaults, {
4139		stopText: 'Stop'
4140	});
4141
4142	// STOP BUTTON
4143	$.extend(MediaElementPlayer.prototype, {
4144		buildstop: function(player, controls, layers, media) {
4145			var t = this;
4146
4147			$('<div class="mejs-button mejs-stop-button mejs-stop">' +
4148					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' +
4149				'</div>')
4150				.appendTo(controls)
4151				.click(function() {
4152					if (!media.paused) {
4153						media.pause();
4154					}
4155					if (media.currentTime > 0) {
4156						media.setCurrentTime(0);
4157                        media.pause();
4158						controls.find('.mejs-time-current').width('0px');
4159						controls.find('.mejs-time-handle').css('left', '0px');
4160						controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0, player.options));
4161						controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0, player.options));
4162						layers.find('.mejs-poster').show();
4163					}
4164				});
4165		}
4166	});
4167	
4168})(mejs.$);
4169
4170(function($) {
4171
4172	$.extend(mejs.MepDefaults, {
4173		// Enable tooltip that shows time in progress bar
4174		enableProgressTooltip: true,
4175		progressHelpText: ''
4176	});
4177
4178	// progress/loaded bar
4179	$.extend(MediaElementPlayer.prototype, {
4180		buildprogress: function(player, controls, layers, media) {
4181
4182			var
4183				t = this,
4184				mouseIsDown = false,
4185				mouseIsOver = false,
4186				lastKeyPressTime = 0,
4187				startedPaused = false,
4188				autoRewindInitial = player.options.autoRewind,
4189				progressTitle = t.options.progressHelpText ? t.options.progressHelpText : mejs.i18n.t('mejs.time-help-text'),
4190				tooltip = player.options.enableProgressTooltip ? '<span class="mejs-time-float">' +
4191					'<span class="mejs-time-float-current">00:00</span>' +
4192					'<span class="mejs-time-float-corner"></span>' +
4193				'</span>' : "";
4194
4195			$('<div class="mejs-time-rail">' +
4196				'<span  class="mejs-time-total mejs-time-slider">' +
4197				//'<span class="mejs-offscreen">' + progressTitle + '</span>' +
4198					'<span class="mejs-time-buffering"></span>' +
4199					'<span class="mejs-time-loaded"></span>' +
4200					'<span class="mejs-time-current"></span>' +
4201					'<span class="mejs-time-handle"></span>' +
4202					 tooltip +
4203				'</span>' +
4204			'</div>')
4205				.appendTo(controls);
4206			controls.find('.mejs-time-buffering').hide();
4207
4208			t.total = controls.find('.mejs-time-total');
4209			t.loaded  = controls.find('.mejs-time-loaded');
4210			t.current  = controls.find('.mejs-time-current');
4211			t.handle  = controls.find('.mejs-time-handle');
4212			t.timefloat  = controls.find('.mejs-time-float');
4213			t.timefloatcurrent  = controls.find('.mejs-time-float-current');
4214			t.slider = controls.find('.mejs-time-slider');
4215
4216			var handleMouseMove = function (e) {
4217
4218					var offset = t.total.offset(),
4219						width = t.total.width(),
4220						percentage = 0,
4221						newTime = 0,
4222						pos = 0,
4223						x;
4224
4225					// mouse or touch position relative to the object
4226					if (e.originalEvent && e.originalEvent.changedTouches) {
4227						x = e.originalEvent.changedTouches[0].pageX;
4228					} else if (e.changedTouches) { // for Zepto
4229						x = e.changedTouches[0].pageX;
4230					} else {
4231						x = e.pageX;
4232					}
4233
4234					if (media.duration) {
4235						if (x < offset.left) {
4236							x = offset.left;
4237						} else if (x > width + offset.left) {
4238							x = width + offset.left;
4239						}
4240
4241						pos = x - offset.left;
4242						percentage = (pos / width);
4243						newTime = (percentage <= 0.02) ? 0 : percentage * media.duration;
4244
4245						// seek to where the mouse is
4246						if (mouseIsDown && newTime !== media.currentTime) {
4247							media.setCurrentTime(newTime);
4248						}
4249
4250						// position floating time box
4251						if (!mejs.MediaFeatures.hasTouch) {
4252							t.timefloat.css('left', pos);
4253							t.timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime, player.options) );
4254							t.timefloat.show();
4255						}
4256					}
4257				},
4258				// Accessibility for slider
4259				updateSlider = function (e) {
4260
4261					var seconds = media.currentTime,
4262						timeSliderText = mejs.i18n.t('mejs.time-slider'),
4263						time = mejs.Utility.secondsToTimeCode(seconds, player.options),
4264						duration = media.duration;
4265
4266					t.slider.attr({
4267						'aria-label': timeSliderText,
4268						'aria-valuemin': 0,
4269						'aria-valuemax': duration,
4270						'aria-valuenow': seconds,
4271						'aria-valuetext': time,
4272						'role': 'slider',
4273						'tabindex': 0
4274					});
4275
4276				},
4277				restartPlayer = function () {
4278					var now = new Date();
4279					if (now - lastKeyPressTime >= 1000) {
4280						media.play();
4281					}
4282				};
4283
4284			t.slider.bind('focus', function (e) {
4285				player.options.autoRewind = false;
4286			});
4287
4288			t.slider.bind('blur', function (e) {
4289				player.options.autoRewind = autoRewindInitial;
4290			});
4291
4292			t.slider.bind('keydown', function (e) {
4293
4294				if ((new Date() - lastKeyPressTime) >= 1000) {
4295					startedPaused = media.paused;
4296				}
4297
4298				var keyCode = e.keyCode,
4299					duration = media.duration,
4300					seekTime = media.currentTime,
4301					seekForward  = player.options.defaultSeekForwardInterval(media),
4302					seekBackward = player.options.defaultSeekBackwardInterval(media);
4303
4304				switch (keyCode) {
4305					case 37: // left
4306					case 40: // Down
4307						seekTime -= seekBackward;
4308						break;
4309					case 39: // Right
4310					case 38: // Up
4311						seekTime += seekForward;
4312						break;
4313					case 36: // Home
4314						seekTime = 0;
4315						break;
4316					case 35: // end
4317						seekTime = duration;
4318						break;
4319					case 32: // space
4320					case 13: // enter
4321						media.paused ? media.play() : media.pause();
4322						return;
4323					default:
4324						return;
4325				}
4326
4327				seekTime = seekTime < 0 ? 0 : (seekTime >= duration ? duration : Math.floor(seekTime));
4328				lastKeyPressTime = new Date();
4329				if (!startedPaused) {
4330					media.pause();
4331				}
4332
4333				if (seekTime < media.duration && !startedPaused) {
4334					setTimeout(restartPlayer, 1100);
4335				}
4336
4337				media.setCurrentTime(seekTime);
4338
4339				e.preventDefault();
4340				e.stopPropagation();
4341				return false;
4342			});
4343
4344
4345			// handle clicks
4346			//controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove);
4347			t.total
4348				.bind('mousedown touchstart', function (e) {
4349					// only handle left clicks or touch
4350					if (e.which === 1 || e.which === 0) {
4351						mouseIsDown = true;
4352						handleMouseMove(e);
4353						t.globalBind('mousemove.dur touchmove.dur', function(e) {
4354							handleMouseMove(e);
4355						});
4356						t.globalBind('mouseup.dur touchend.dur', function (e) {
4357							mouseIsDown = false;
4358							if (typeof t.timefloat !== 'undefined') {
4359								t.timefloat.hide();
4360							}
4361							t.globalUnbind('.dur');
4362						});
4363					}
4364				})
4365				.bind('mouseenter', function(e) {
4366					mouseIsOver = true;
4367					t.globalBind('mousemove.dur', function(e) {
4368						handleMouseMove(e);
4369					});
4370					if (typeof t.timefloat !== 'undefined' && !mejs.MediaFeatures.hasTouch) {
4371						t.timefloat.show();
4372					}
4373				})
4374				.bind('mouseleave',function(e) {
4375					mouseIsOver = false;
4376					if (!mouseIsDown) {
4377						t.globalUnbind('.dur');
4378						if (typeof t.timefloat !== 'undefined') {
4379							t.timefloat.hide();
4380						}
4381					}
4382				});
4383
4384			// loading
4385			media.addEventListener('progress', function (e) {
4386				player.setProgressRail(e);
4387				player.setCurrentRail(e);
4388			}, false);
4389
4390			// current time
4391			media.addEventListener('timeupdate', function(e) {
4392				player.setProgressRail(e);
4393				player.setCurrentRail(e);
4394				updateSlider(e);
4395			}, false);
4396
4397			t.container.on('controlsresize', function(e) {
4398				player.setProgressRail(e);
4399				player.setCurrentRail(e);
4400			});
4401		},
4402		setProgressRail: function(e) {
4403
4404			var
4405				t = this,
4406				target = (e !== undefined) ? e.target : t.media,
4407				percent = null;
4408
4409			// newest HTML5 spec has buffered array (FF4, Webkit)
4410			if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) {
4411				// account for a real array with multiple values - always read the end of the last buffer
4412				percent = target.buffered.end(target.buffered.length - 1) / target.duration;
4413			} 
4414			// Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
4415			// to be anything other than 0. If the byte count is available we use this instead.
4416			// Browsers that support the else if do not seem to have the bufferedBytes value and
4417			// should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8.
4418			else if (target && target.bytesTotal !== undefined && target.bytesTotal > 0 && target.bufferedBytes !== undefined) {
4419				percent = target.bufferedBytes / target.bytesTotal;
4420			}
4421			// Firefox 3 with an Ogg file seems to go this way
4422			else if (e && e.lengthComputable && e.total !== 0) {
4423				percent = e.loaded / e.total;
4424			}
4425
4426			// finally update the progress bar
4427			if (percent !== null) {
4428				percent = Math.min(1, Math.max(0, percent));
4429				// update loaded bar
4430				if (t.loaded && t.total) {
4431					t.loaded.width(t.total.width() * percent);
4432				}
4433			}
4434		},
4435		setCurrentRail: function() {
4436
4437			var t = this;
4438		
4439			if (t.media.currentTime !== undefined && t.media.duration) {
4440
4441				// update bar and handle
4442				if (t.total && t.handle) {
4443					var 
4444						newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration),
4445						handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2);
4446
4447					t.current.width(newWidth);
4448					t.handle.css('left', handlePos);
4449				}
4450			}
4451
4452		}
4453	});
4454})(mejs.$);
4455
4456(function($) {
4457	
4458	// options
4459	$.extend(mejs.MepDefaults, {
4460		duration: -1,
4461		timeAndDurationSeparator: '<span> | </span>'
4462	});
4463
4464
4465	// current and duration 00:00 / 00:00
4466	$.extend(MediaElementPlayer.prototype, {
4467		buildcurrent: function(player, controls, layers, media) {
4468			var t = this;
4469			
4470			$('<div class="mejs-time" role="timer" aria-live="off">' +
4471					'<span class="mejs-currenttime">' + 
4472						mejs.Utility.secondsToTimeCode(0, player.options) +
4473                    '</span>'+
4474				'</div>')
4475			.appendTo(controls);
4476			
4477			t.currenttime = t.controls.find('.mejs-currenttime');
4478
4479			media.addEventListener('timeupdate',function() {
4480				if (t.controlsAreVisible) {
4481					player.updateCurrent();
4482				}
4483
4484			}, false);
4485		},
4486
4487
4488		buildduration: function(player, controls, layers, media) {
4489			var t = this;
4490			
4491			if (controls.children().last().find('.mejs-currenttime').length > 0) {
4492				$(t.options.timeAndDurationSeparator +
4493					'<span class="mejs-duration">' + 
4494						mejs.Utility.secondsToTimeCode(t.options.duration, t.options) +
4495					'</span>')
4496					.appendTo(controls.find('.mejs-time'));
4497			} else {
4498
4499				// add class to current time
4500				controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container');
4501				
4502				$('<div class="mejs-time mejs-duration-container">'+
4503					'<span class="mejs-duration">' + 
4504						mejs.Utility.secondsToTimeCode(t.options.duration, t.options) +
4505					'</span>' +
4506				'</div>')
4507				.appendTo(controls);
4508			}
4509			
4510			t.durationD = t.controls.find('.mejs-duration');
4511
4512			media.addEventListener('timeupdate',function() {
4513				if (t.controlsAreVisible) {
4514					player.updateDuration();
4515				}
4516			}, false);
4517		},
4518		
4519		updateCurrent:  function() {
4520			var t = this;
4521			
4522			var currentTime = t.media.currentTime;
4523			
4524			if (isNaN(currentTime)) {
4525				currentTime = 0;
4526			}
4527
4528			if (t.currenttime) {
4529				t.currenttime.html(mejs.Utility.secondsToTimeCode(currentTime, t.options));
4530			}
4531		},
4532		
4533		updateDuration: function() {
4534			var t = this;
4535			
4536			var duration = t.media.duration;
4537			if (t.options.duration > 0) {
4538				duration = t.options.duration;
4539			}
4540			
4541			if (isNaN(duration)) {
4542				duration = 0;
4543			}
4544
4545			//Toggle the long video class if the video is longer than an hour.
4546			t.container.toggleClass("mejs-long-video", duration > 3600);
4547			
4548			if (t.durationD && duration > 0) {
4549				t.durationD.html(mejs.Utility.secondsToTimeCode(duration, t.options));
4550			}		
4551		}
4552	});
4553
4554})(mejs.$);
4555
4556(function ($) {
4557
4558	$.extend(mejs.MepDefaults, {
4559		muteText: mejs.i18n.t('mejs.mute-toggle'),
4560		allyVolumeControlText: mejs.i18n.t('mejs.volume-help-text'),
4561		hideVolumeOnTouchDevices: true,
4562
4563		audioVolume: 'horizontal',
4564		videoVolume: 'vertical'
4565	});
4566
4567	$.extend(MediaElementPlayer.prototype, {
4568		buildvolume: function (player, controls, layers, media) {
4569
4570			// Android and iOS don't support volume controls
4571			if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices)
4572				return;
4573
4574			var t = this,
4575				mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume,
4576				mute = (mode == 'horizontal') ?
4577
4578					// horizontal version
4579					$('<div class="mejs-button mejs-volume-button mejs-mute">' +
4580						'<button type="button" aria-controls="' + t.id +
4581						'" title="' + t.options.muteText +
4582						'" aria-label="' + t.options.muteText +
4583						'"></button>' +
4584						'</div>' +
4585						'<a href="javascript:void(0);" class="mejs-horizontal-volume-slider">' + // outer background
4586						'<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' +
4587						'<div class="mejs-horizontal-volume-total"></div>' + // line background
4588						'<div class="mejs-horizontal-volume-current"></div>' + // current volume
4589						'<div class="mejs-horizontal-volume-handle"></div>' + // handle
4590						'</a>'
4591					)
4592					.appendTo(controls) :
4593
4594					// vertical version
4595					$('<div class="mejs-button mejs-volume-button mejs-mute">' +
4596						'<button type="button" aria-controls="' + t.id +
4597						'" title="' + t.options.muteText +
4598						'" aria-label="' + t.options.muteText +
4599						'"></button>' +
4600						'<a href="javascript:void(0);" class="mejs-volume-slider">' + // outer background
4601						'<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' +
4602						'<div class="mejs-volume-total"></div>' + // line background
4603						'<div class="mejs-volume-current"></div>' + // current volume
4604						'<div class="mejs-volume-handle"></div>' + // handle
4605						'</a>' +
4606						'</div>')
4607					.appendTo(controls),
4608				volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'),
4609				volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'),
4610				volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'),
4611				volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'),
4612
4613				positionVolumeHandle = function (volume, secondTry) {
4614
4615					if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') {
4616						volumeSlider.show();
4617						positionVolumeHandle(volume, true);
4618						volumeSlider.hide();
4619						return;
4620					}
4621
4622					// correct to 0-1
4623					volume = Math.max(0, volume);
4624					volume = Math.min(volume, 1);
4625
4626					// adjust mute button style
4627					if (volume === 0) {
4628						mute.removeClass('mejs-mute').addClass('mejs-unmute');
4629						mute.children('button').attr('title', mejs.i18n.t('mejs.unmute')).attr('aria-label', mejs.i18n.t('mejs.unmute'));
4630					} else {
4631						mute.removeClass('mejs-unmute').addClass('mejs-mute');
4632						mute.children('button').attr('title', mejs.i18n.t('mejs.mute')).attr('aria-label', mejs.i18n.t('mejs.mute'));
4633					}
4634
4635					// top/left of full size volume slider background
4636					var totalPosition = volumeTotal.position();
4637					// position slider
4638					if (mode == 'vertical') {
4639						var
4640							// height of the full size volume slider background
4641							totalHeight = volumeTotal.height(),
4642
4643							// the new top position based on the current volume
4644							// 70% volume on 100px height == top:30px
4645							newTop = totalHeight - (totalHeight * volume);
4646
4647						// handle
4648						volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2)));
4649
4650						// show the current visibility
4651						volumeCurrent.height(totalHeight - newTop);
4652						volumeCurrent.css('top', totalPosition.top + newTop);
4653					} else {
4654						var
4655							// height of the full size volume slider background
4656							totalWidth = volumeTotal.width(),
4657
4658							// the new left position based on the current volume
4659							newLeft = totalWidth * volume;
4660
4661						// handle
4662						volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2)));
4663
4664						// rezize the current part of the volume bar
4665						volumeCurrent.width(Math.round(newLeft));
4666					}
4667				},
4668				handleVolumeMove = function (e) {
4669
4670					var volume = null,
4671						totalOffset = volumeTotal.offset();
4672
4673					// calculate the new volume based on the moust position
4674					if (mode === 'vertical') {
4675
4676						var
4677							railHeight = volumeTotal.height(),
4678							newY = e.pageY - totalOffset.top;
4679
4680						volume = (railHeight - newY) / railHeight;
4681
4682						// the controls just hide themselves (usually when mouse moves too far up)
4683						if (totalOffset.top === 0 || totalOffset.left === 0) {
4684							return;
4685						}
4686
4687					} else {
4688						var
4689							railWidth = volumeTotal.width(),
4690							newX = e.pageX - totalOffset.left;
4691
4692						volume = newX / railWidth;
4693					}
4694
4695					// ensure the volume isn't outside 0-1
4696					volume = Math.max(0, volume);
4697					volume = Math.min(volume, 1);
4698
4699					// position the slider and handle
4700					positionVolumeHandle(volume);
4701
4702					// set the media object (this will trigger the volumechanged event)
4703					if (volume === 0) {
4704						media.setMuted(true);
4705					} else {
4706						media.setMuted(false);
4707					}
4708					media.setVolume(volume);
4709				},
4710				mouseIsDown = false,
4711				mouseIsOver = false;
4712
4713			// SLIDER
4714
4715			mute
4716			.hover(function () {
4717				volumeSlider.show();
4718				mouseIsOver = true;
4719			}, function () {
4720				mouseIsOver = false;
4721
4722				if (!mouseIsDown && mode == 'vertical') {
4723					volumeSlider.hide();
4724				}
4725			});
4726
4727			var updateVolumeSlider = function (e) {
4728
4729				var volume = Math.floor(media.volume * 100);
4730
4731				volumeSlider.attr({
4732					'aria-label': mejs.i18n.t('mejs.volume-slider'),
4733					'aria-valuemin': 0,
4734					'aria-valuemax': 100,
4735					'aria-valuenow': volume,
4736					'aria-valuetext': volume + '%',
4737					'role': 'slider',
4738					'tabindex': 0
4739				});
4740
4741			};
4742
4743			volumeSlider
4744			.bind('mouseover', function () {
4745				mouseIsOver = true;
4746			})
4747			.bind('mousedown', function (e) {
4748				handleVolumeMove(e);
4749				t.globalBind('mousemove.vol', function (e) {
4750					handleVolumeMove(e);
4751				});
4752				t.globalBind('mouseup.vol', function () {
4753					mouseIsDown = false;
4754					t.globalUnbind('.vol');
4755
4756					if (!mouseIsOver && mode == 'vertical') {
4757						volumeSlider.hide();
4758					}
4759				});
4760				mouseIsDown = true;
4761
4762				return false;
4763			})
4764			.bind('keydown', function (e) {
4765				var keyCode = e.keyCode;
4766				var volume = media.volume;
4767				switch (keyCode) {
4768					case 38: // Up
4769						volume = Math.min(volume + 0.1, 1);
4770						break;
4771					case 40: // Down
4772						volume = Math.max(0, volume - 0.1);
4773						break;
4774					default:
4775						return true;
4776				}
4777
4778				mouseIsDown = false;
4779				positionVolumeHandle(volume);
4780				media.setVolume(volume);
4781				return false;
4782			});
4783
4784			// MUTE button
4785			mute.find('button').click(function () {
4786				media.setMuted(!media.muted);
4787			});
4788
4789			//Keyboard input
4790			mute.find('button').bind('focus', function () {
4791				volumeSlider.show();
4792			});
4793
4794			// listen for volume change events from other sources
4795			media.addEventListener('volumechange', function (e) {
4796				if (!mouseIsDown) {
4797					if (media.muted) {
4798						positionVolumeHandle(0);
4799						mute.removeClass('mejs-mute').addClass('mejs-unmute');
4800					} else {
4801						positionVolumeHandle(media.volume);
4802						mute.removeClass('mejs-unmute').addClass('mejs-mute');
4803					}
4804				}
4805				updateVolumeSlider(e);
4806			}, false);
4807
4808			// mutes the media and sets the volume icon muted if the initial volume is set to 0
4809			if (player.options.startVolume === 0) {
4810				media.setMuted(true);
4811			}
4812
4813			// shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
4814			if (media.pluginType === 'native') {
4815				media.setVolume(player.options.startVolume);
4816			}
4817
4818			t.container.on('controlsresize', function () {
4819				if (media.muted) {
4820					positionVolumeHandle(0);
4821					mute.removeClass('mejs-mute').addClass('mejs-unmute');
4822				} else {
4823					positionVolumeHandle(media.volume);
4824					mute.removeClass('mejs-unmute').addClass('mejs-mute');
4825				}
4826			});
4827		}
4828	});
4829
4830})(mejs.$);
4831
4832(function($) {
4833
4834	$.extend(mejs.MepDefaults, {
4835		usePluginFullScreen: true,
4836		newWindowCallback: function() { return '';},
4837		fullscreenText: ''
4838	});
4839
4840	$.extend(MediaElementPlayer.prototype, {
4841
4842		isFullScreen: false,
4843
4844		isNativeFullScreen: false,
4845
4846		isInIframe: false,
4847							
4848		// Possible modes
4849		// (1) 'native-native' 	HTML5 video  + browser fullscreen (IE10+, etc.)
4850		// (2) 'plugin-native' 	plugin video + browser fullscreen (fails in some versions of Firefox)
4851		// (3) 'fullwindow' 	Full window (retains all UI)
4852		// usePluginFullScreen = true
4853		// (4) 'plugin-click' 	Flash 1 - click through with pointer events
4854		// (5) 'plugin-hover' 	Flash 2 - hover popup in flash (IE6-8)		
4855		fullscreenMode: '',
4856
4857		buildfullscreen: function(player, controls, layers, media) {
4858
4859			if (!player.isVideo)
4860				return;
4861				
4862			player.isInIframe = (window.location != window.parent.location);	
4863		
4864			// detect on start
4865			media.addEventListener('loadstart', function() { player.detectFullscreenMode(); });
4866				
4867			// build button
4868			var t = this,
4869				hideTimeout = null,
4870				fullscreenTitle = t.options.fullscreenText ? t.options.fullscreenText : mejs.i18n.t('mejs.fullscreen'),
4871				fullscreenBtn =
4872					$('<div class="mejs-button mejs-fullscreen-button">' +
4873						'<button type="button" aria-controls="' + t.id + '" title="' + fullscreenTitle + '" aria-label="' + fullscreenTitle + '"></button>' +
4874					'</div>')
4875					.appendTo(controls)
4876					.on('click', function() {
4877						
4878						// toggle fullscreen
4879						var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen;
4880	
4881						if (isFullScreen) {
4882							player.exitFullScreen();
4883						} else {
4884							player.enterFullScreen();
4885						}
4886					})										
4887					.on('mouseover', function() {
4888						
4889						// very old browsers with a plugin
4890						if (t.fullscreenMode == 'plugin-hover') {						
4891							if (hideTimeout !== null) {
4892								clearTimeout(hideTimeout);
4893								delete hideTimeout;
4894							}
4895	
4896							var buttonPos = fullscreenBtn.offset(),
4897								containerPos = player.container.offset();
4898	
4899							media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true);
4900						}
4901
4902					})
4903					.on('mouseout', function() {
4904
4905						if (t.fullscreenMode == 'plugin-hover') {						
4906							if (hideTimeout !== null) {
4907								clearTimeout(hideTimeout);
4908								delete hideTimeout;
4909							}
4910	
4911							hideTimeout = setTimeout(function() {
4912								media.hideFullscreenButton();
4913							}, 1500);
4914						}
4915
4916					});
4917
4918					
4919
4920			player.fullscreenBtn = fullscreenBtn;
4921
4922			t.globalBind('keydown',function (e) {
4923				if (e.keyCode == 27 && ((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen)) {
4924					player.exitFullScreen();
4925				}
4926			});
4927			
4928			t.normalHeight = 0;
4929			t.normalWidth = 0;					
4930					
4931			// setup native fullscreen event
4932			if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
4933
4934				// chrome doesn't alays fire this in an iframe
4935				var fullscreenChanged = function(e) {
4936					if (player.isFullScreen) {
4937						if (mejs.MediaFeatures.isFullScreen()) {
4938							player.isNativeFullScreen = true;
4939							// reset the controls once we are fully in full screen
4940							player.setControlsSize();
4941						} else {
4942							player.isNativeFullScreen = false;
4943							// when a user presses ESC
4944							// make sure to put the player back into place
4945							player.exitFullScreen();
4946						}
4947					}
4948				};
4949
4950				player.globalBind(mejs.MediaFeatures.fullScreenEventName, fullscreenChanged);
4951			}
4952
4953		},
4954		
4955		detectFullscreenMode: function() {
4956			
4957			var t = this,
4958				mode = '',
4959				features = mejs.MediaFeatures;
4960			
4961			if (features.hasTrueNativeFullScreen && t.media.pluginType === 'native') {
4962				mode = 'native-native';
4963			} else if (features.hasTrueNativeFullScreen && t.media.pluginType !== 'native' && !features.hasFirefoxPluginMovingProblem) {
4964				mode = 'plugin-native';					
4965			} else if (t.usePluginFullScreen) { 
4966				if (mejs.MediaFeatures.supportsPointerEvents) {
4967					mode = 'plugin-click';
4968					// this needs some special setup
4969					t.createPluginClickThrough();				
4970				} else { 
4971					mode = 'plugin-hover';
4972				}
4973				
4974			} else {
4975				mode = 'fullwindow';
4976			}
4977			
4978			
4979			t.fullscreenMode = mode;		
4980			return mode;
4981		},
4982		
4983		isPluginClickThroughCreated: false,
4984		
4985		createPluginClickThrough: function() {
4986				
4987			var t = this;
4988			
4989			// don't build twice
4990			if (t.isPluginClickThroughCreated) {
4991				return;
4992			}	
4993
4994			// allows clicking through the fullscreen button and controls down directly to Flash
4995
4996			/*
4997			 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)
4998			 We then put a divs over the video and on either side of the fullscreen button
4999			 to capture mouse movement and restore the controls once the mouse moves outside of the fullscreen button
5000			*/
5001
5002			var fullscreenIsDisabled = false,
5003				restoreControls = function() {
5004					if (fullscreenIsDisabled) {
5005						// hide the hovers
5006						for (var i in hoverDivs) {
5007							hoverDivs[i].hide();
5008						}
5009
5010						// restore the control bar
5011						t.fullscreenBtn.css('pointer-events', '');
5012						t.controls.css('pointer-events', '');
5013
5014						// prevent clicks from pausing video
5015						t.media.removeEventListener('click', t.clickToPlayPauseCallback);
5016
5017						// store for later
5018						fullscreenIsDisabled = false;
5019					}
5020				},
5021				hoverDivs = {},
5022				hoverDivNames = ['top', 'left', 'right', 'bottom'],
5023				i, len,
5024				positionHoverDivs = function() {
5025					var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left,
5026						fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top,
5027						fullScreenBtnWidth = fullscreenBtn.outerWidth(true),
5028						fullScreenBtnHeight = fullscreenBtn.outerHeight(true),
5029						containerWidth = t.container.width(),
5030						containerHeight = t.container.height();
5031
5032					for (i in hoverDivs) {
5033						hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'});
5034					}
5035
5036					// over video, but not controls
5037					hoverDivs['top']
5038						.width( containerWidth )
5039						.height( fullScreenBtnOffsetTop );
5040
5041					// over controls, but not the fullscreen button
5042					hoverDivs['left']
5043						.width( fullScreenBtnOffsetLeft )
5044						.height( fullScreenBtnHeight )
5045						.css({top: fullScreenBtnOffsetTop});
5046
5047					// after the fullscreen button
5048					hoverDivs['right']
5049						.width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth )
5050						.height( fullScreenBtnHeight )
5051						.css({top: fullScreenBtnOffsetTop,
5052							 left: fullScreenBtnOffsetLeft + fullScreenBtnWidth});
5053
5054					// under the fullscreen button
5055					hoverDivs['bottom']
5056						.width( containerWidth )
5057						.height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop )
5058						.css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight});
5059				};
5060
5061			t.globalBind('resize', function() {
5062				positionHoverDivs();
5063			});
5064
5065			for (i = 0, len = hoverDivNames.length; i < len; i++) {
5066				hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide();
5067			}
5068
5069			// on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash
5070			fullscreenBtn.on('mouseover',function() {
5071
5072				if (!t.isFullScreen) {
5073
5074					var buttonPos = fullscreenBtn.offset(),
5075						containerPos = player.container.offset();
5076
5077					// move the button in Flash into place
5078					media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false);
5079
5080					// allows click through
5081					t.fullscreenBtn.css('pointer-events', 'none');
5082					t.controls.css('pointer-events', 'none');
5083
5084					// restore click-to-play
5085					t.media.addEventListener('click', t.clickToPlayPauseCallback);
5086
5087					// show the divs that will restore things
5088					for (i in hoverDivs) {
5089						hoverDivs[i].show();
5090					}
5091
5092					positionHoverDivs();
5093
5094					fullscreenIsDisabled = true;
5095				}
5096
5097			});
5098
5099			// restore controls anytime the user enters or leaves fullscreen
5100			media.addEventListener('fullscreenchange', function(e) {
5101				t.isFullScreen = !t.isFullScreen;
5102				// don't allow plugin click to pause video - messes with
5103				// plugin's controls
5104				if (t.isFullScreen) {
5105					t.media.removeEventListener('click', t.clickToPlayPauseCallback);
5106				} else {
5107					t.media.addEventListener('click', t.clickToPlayPauseCallback);
5108				}
5109				restoreControls();
5110			});
5111
5112
5113			// the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events
5114			// so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button
5115
5116			t.globalBind('mousemove', function(e) {
5117
5118				// if the mouse is anywhere but the fullsceen button, then restore it all
5119				if (fullscreenIsDisabled) {
5120
5121					var fullscreenBtnPos = fullscreenBtn.offset();
5122
5123
5124					if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) ||
5125						e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true)
5126						) {
5127
5128						fullscreenBtn.css('pointer-events', '');
5129						t.controls.css('pointer-events', '');
5130
5131						fullscreenIsDisabled = false;
5132					}
5133				}
5134			});
5135
5136
5137			t.isPluginClickThroughCreated = true;
5138		},		
5139
5140		cleanfullscreen: function(player) {
5141			player.exitFullScreen();
5142		},
5143
5144        containerSizeTimeout: null,
5145
5146		enterFullScreen: function() {
5147
5148			var t = this;
5149
5150			if (mejs.MediaFeatures.isiOS && mejs.MediaFeatures.hasiOSFullScreen && typeof t.media.webkitEnterFullscreen === 'function') {
5151			    t.media.webkitEnterFullscreen();
5152				return;
5153			}
5154
5155			// set it to not show scroll bars so 100% will work
5156            $(document.documentElement).addClass('mejs-fullscreen');
5157
5158			// store sizing
5159			t.normalHeight = t.container.height();
5160			t.normalWidth = t.container.width();
5161
5162
5163
5164			// attempt to do true fullscreen
5165			if (t.fullscreenMode === 'native-native' || t.fullscreenMode === 'plugin-native') {
5166
5167				mejs.MediaFeatures.requestFullScreen(t.container[0]);
5168				//return;
5169
5170				if (t.isInIframe) {
5171					// sometimes exiting from fullscreen doesn't work
5172					// notably in Chrome <iframe>. Fixed in version 17
5173					setTimeout(function checkFullscreen() {
5174
5175						if (t.isNativeFullScreen) {
5176							var percentErrorMargin = 0.002, // 0.2%
5177								windowWidth = $(window).width(),
5178								screenWidth = screen.width,
5179								absDiff = Math.abs(screenWidth - windowWidth),
5180								marginError = screenWidth * percentErrorMargin;
5181
5182							// check if the video is suddenly not really fullscreen
5183							if (absDiff > marginError) {
5184								// manually exit
5185								t.exitFullScreen();
5186							} else {
5187								// test again
5188								setTimeout(checkFullscreen, 500);
5189							}
5190						}
5191						
5192					}, 1000);
5193				}
5194				
5195			} else if (t.fullscreeMode == 'fullwindow') {				
5196				// move into position
5197				
5198			}			
5199			
5200			// make full size
5201			t.container
5202				.addClass('mejs-container-fullscreen')
5203				.width('100%')
5204				.height('100%');
5205				//.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000});
5206
5207			// Only needed for safari 5.1 native full screen, can cause display issues elsewhere
5208			// Actually, it seems to be needed for IE8, too
5209			//if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
5210				t.containerSizeTimeout = setTimeout(function() {
5211					t.container.css({width: '100%', height: '100%'});
5212					t.setControlsSize();
5213				}, 500);
5214			//}
5215
5216			if (t.media.pluginType === 'native') {
5217				t.$media
5218					.width('100%')
5219					.height('100%');
5220			} else {
5221				t.container.find('.mejs-shim')
5222					.width('100%')
5223					.height('100%');	
5224				
5225				setTimeout(function() {
5226					var win = $(window),
5227						winW = win.width(),
5228						winH = win.height();
5229							
5230					t.media.setVideoSize(winW,winH);			
5231				}, 500);
5232			}
5233
5234			t.layers.children('div')
5235				.width('100%')
5236				.height('100%');
5237
5238			if (t.fullscreenBtn) {
5239				t.fullscreenBtn
5240					.removeClass('mejs-fullscreen')
5241					.addClass('mejs-unfullscreen');
5242			}
5243
5244			t.setControlsSize();
5245			t.isFullScreen = true;
5246
5247			var zoomFactor = Math.min(screen.width / t.width, screen.height / t.height);
5248			t.container.find('.mejs-captions-text').css('font-size', zoomFactor * 100 + '%');
5249			t.container.find('.mejs-captions-text').css('line-height', 'normal');
5250			t.container.find('.mejs-captions-position').css('bottom', '45px');
5251
5252			t.container.trigger('enteredfullscreen');
5253		},
5254
5255		exitFullScreen: function() {
5256
5257			var t = this;
5258
5259            // Prevent container from attempting to stretch a second time
5260            clearTimeout(t.containerSizeTimeout);
5261
5262			// firefox can't adjust plugins
5263			/*
5264			if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) {
5265				t.media.setFullscreen(false);
5266				//player.isFullScreen = false;
5267				return;
5268			}
5269			*/
5270
5271			// come out of native fullscreen
5272			if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) {
5273				mejs.MediaFeatures.cancelFullScreen();
5274			}
5275
5276			// restore scroll bars to document
5277            $(document.documentElement).removeClass('mejs-fullscreen');
5278
5279			t.container
5280				.removeClass('mejs-container-fullscreen')
5281				.width(t.normalWidth)
5282				.height(t.normalHeight);
5283
5284			if (t.media.pluginType === 'native') {
5285				t.$media
5286					.width(t.normalWidth)
5287					.height(t.normalHeight);
5288			} else {
5289				t.container.find('.mejs-shim')
5290					.width(t.normalWidth)
5291					.height(t.normalHeight);
5292
5293				t.media.setVideoSize(t.normalWidth, t.normalHeight);
5294			}
5295
5296			t.layers.children('div')
5297				.width(t.normalWidth)
5298				.height(t.normalHeight);
5299
5300			t.fullscreenBtn
5301				.removeClass('mejs-unfullscreen')
5302				.addClass('mejs-fullscreen');
5303
5304			t.setControlsSize();
5305			t.isFullScreen = false;
5306
5307			t.container.find('.mejs-captions-text').css('font-size','');
5308			t.container.find('.mejs-captions-text').css('line-height', '');
5309			t.container.find('.mejs-captions-position').css('bottom', '');
5310
5311			t.container.trigger('exitedfullscreen');
5312		}
5313	});
5314
5315})(mejs.$);
5316
5317(function($) {
5318
5319	// Speed
5320	$.extend(mejs.MepDefaults, {
5321
5322		// We also support to pass object like this:
5323		// [{name: 'Slow', value: '0.75'}, {name: 'Normal', value: '1.00'}, ...]
5324		speeds: ['2.00', '1.50', '1.25', '1.00', '0.75'],
5325
5326		defaultSpeed: '1.00',
5327		
5328		speedChar: 'x'
5329
5330	});
5331
5332	$.extend(MediaElementPlayer.prototype, {
5333
5334		buildspeed: function(player, controls, layers, media) {
5335			var t = this;
5336
5337			if (t.media.pluginType == 'native') {
5338				var 
5339					speedButton = null,
5340					speedSelector = null,
5341					playbackSpeed = null,
5342					inputId = null;
5343
5344				var speeds = [];
5345				var defaultInArray = false;
5346				for (var i=0, len=t.options.speeds.length; i < len; i++) {
5347					var s = t.options.speeds[i];
5348					if (typeof(s) === 'string'){
5349						speeds.push({
5350							name: s + t.options.speedChar,
5351							value: s
5352						});
5353						if(s === t.options.defaultSpeed) {
5354							defaultInArray = true;
5355						}
5356					}
5357					else {
5358						speeds.push(s);
5359						if(s.value === t.options.defaultSpeed) {
5360							defaultInArray = true;
5361						}
5362					}
5363				}
5364
5365				if (!defaultInArray) {
5366					speeds.push({
5367						name: t.options.defaultSpeed + t.options.speedChar,
5368						value: t.options.defaultSpeed
5369					});
5370				}
5371
5372				speeds.sort(function(a, b) {
5373					return parseFloat(b.value) - parseFloat(a.value);
5374				});
5375
5376				var getSpeedNameFromValue = function(value) {
5377					for(i=0,len=speeds.length; i <len; i++) {
5378						if (speeds[i].value === value) {
5379							return speeds[i].name;
5380						}
5381					}
5382				};
5383
5384				var html = '<div class="mejs-button mejs-speed-button">' +
5385							'<button type="button">' + getSpeedNameFromValue(t.options.defaultSpeed) + '</button>' +
5386							'<div class="mejs-speed-selector">' +
5387							'<ul>';
5388
5389				for (i = 0, il = speeds.length; i<il; i++) {
5390					inputId = t.id + '-speed-' + speeds[i].value;
5391					html += '<li>' + 
5392								'<input type="radio" name="speed" ' + 
5393											'value="' + speeds[i].value + '" ' +
5394											'id="' + inputId + '" ' +
5395											(speeds[i].value === t.options.defaultSpeed ? ' checked' : '') +
5396											' />' +
5397								'<label for="' + inputId + '" ' +
5398											(speeds[i].value === t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') +
5399											'>' + speeds[i].name + '</label>' +
5400							'</li>';
5401				}
5402				html += '</ul></div></div>';
5403
5404				speedButton = $(html).appendTo(controls);
5405				speedSelector = speedButton.find('.mejs-speed-selector');
5406
5407				playbackSpeed = t.options.defaultSpeed;
5408
5409				media.addEventListener('loadedmetadata', function(e) {
5410					if (playbackSpeed) {
5411						media.playbackRate = parseFloat(playbackSpeed);
5412					}
5413				}, true);
5414
5415				speedSelector
5416					.on('click', 'input[type="radio"]', function() {
5417						var newSpeed = $(this).attr('value');
5418						playbackSpeed = newSpeed;
5419						media.playbackRate = parseFloat(newSpeed);
5420						speedButton.find('button').html(getSpeedNameFromValue(newSpeed));
5421						speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected');
5422						speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected');
5423					});
5424				speedButton
5425					.one( 'mouseenter focusin', function() {
5426						speedSelector
5427							.height(
5428								speedButton.find('.mejs-speed-selector ul').outerHeight(true) +
5429								speedButton.find('.mejs-speed-translations').outerHeight(true))
5430							.css('top', (-1 * speedSelector.height()) + 'px');
5431					});
5432			}
5433		}
5434	});
5435
5436})(mejs.$);
5437
5438(function($) {
5439
5440	// add extra default options
5441	$.extend(mejs.MepDefaults, {
5442		// this will automatically turn on a <track>
5443		startLanguage: '',
5444
5445		tracksText: '',
5446
5447		// By default, no WAI-ARIA live region - don't make a
5448		// screen reader speak captions over an audio track.
5449		tracksAriaLive: false,
5450
5451		// option to remove the [cc] button when no <track kind="subtitles"> are present
5452		hideCaptionsButtonWhenEmpty: true,
5453
5454		// If true and we only have one track, change captions to popup
5455		toggleCaptionsButtonWhenOnlyOne: false,
5456
5457		// #id or .class
5458		slidesSelector: ''
5459	});
5460
5461	$.extend(MediaElementPlayer.prototype, {
5462
5463		hasChapters: false,
5464
5465		cleartracks: function(player, controls, layers, media){
5466			if(player) {
5467				if(player.captions) player.captions.remove();
5468				if(player.chapters) player.chapters.remove();
5469				if(player.captionsText) player.captionsText.remove();
5470				if(player.captionsButton) player.captionsButton.remove();
5471			}
5472		},
5473		buildtracks: function(player, controls, layers, media) {
5474			if (player.tracks.length === 0)
5475				return;
5476
5477			var t = this,
5478				attr = t.options.tracksAriaLive ?
5479					'role="log" aria-live="assertive" aria-atomic="false"' : '',
5480				tracksTitle = t.options.tracksText ? t.options.tracksText : mejs.i18n.t('mejs.captions-subtitles'),
5481				i,
5482				kind;
5483
5484			if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide
5485				for (i = t.domNode.textTracks.length - 1; i >= 0; i--) {
5486					t.domNode.textTracks[i].mode = "hidden";
5487				}
5488			}
5489			t.cleartracks(player, controls, layers, media);
5490			player.chapters =
5491					$('<div class="mejs-chapters mejs-layer"></div>')
5492						.prependTo(layers).hide();
5493			player.captions =
5494					$('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover" ' +
5495					attr + '><span class="mejs-captions-text"></span></div></div>')
5496						.prependTo(layers).hide();
5497			player.captionsText = player.captions.find('.mejs-captions-text');
5498			player.captionsButton =
5499					$('<div class="mejs-button mejs-captions-button">'+
5500						'<button type="button" aria-controls="' + t.id + '" title="' + tracksTitle + '" aria-label="' + tracksTitle + '"></button>'+
5501						'<div class="mejs-captions-selector">'+
5502							'<ul>'+
5503								'<li>'+
5504									'<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' +
5505									'<label for="' + player.id + '_captions_none">' + mejs.i18n.t('mejs.none') +'</label>'+
5506								'</li>'	+
5507							'</ul>'+
5508						'</div>'+
5509					'</div>')
5510						.appendTo(controls);
5511
5512
5513			var subtitleCount = 0;
5514			for (i=0; i<player.tracks.length; i++) {
5515				kind = player.tracks[i].kind;
5516				if (kind === 'subtitles' || kind === 'captions') {
5517					subtitleCount++;
5518				}
5519			}
5520
5521			// if only one language then just make the button a toggle
5522			if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){
5523				// click
5524				player.captionsButton.on('click',function() {
5525					if (player.selectedTrack === null) {
5526						lang = player.tracks[0].srclang;
5527					} else {
5528						lang = 'none';
5529					}
5530					player.setTrack(lang);
5531				});
5532			} else {
5533				// hover or keyboard focus
5534				player.captionsButton.on( 'mouseenter focusin', function() {
5535					$(this).find('.mejs-captions-selector').removeClass('mejs-offscreen');
5536				})
5537
5538				// handle clicks to the language radio buttons
5539				.on('click','input[type=radio]',function() {
5540					lang = this.value;
5541					player.setTrack(lang);
5542				});
5543
5544				player.captionsButton.on( 'mouseleave focusout', function() {
5545					$(this).find(".mejs-captions-selector").addClass("mejs-offscreen");
5546				});
5547
5548			}
5549
5550			if (!player.options.alwaysShowControls) {
5551				// move with controls
5552				player.container
5553					.bind('controlsshown', function () {
5554						// push captions above controls
5555						player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
5556
5557					})
5558					.bind('controlshidden', function () {
5559						if (!media.paused) {
5560							// move back to normal place
5561							player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover');
5562						}
5563					});
5564			} else {
5565				player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
5566			}
5567
5568			player.trackToLoad = -1;
5569			player.selectedTrack = null;
5570			player.isLoadingTrack = false;
5571
5572			// add to list
5573			for (i=0; i<player.tracks.length; i++) {
5574				kind = player.tracks[i].kind;
5575				if (kind === 'subtitles' || kind === 'captions') {
5576					player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label);
5577				}
5578			}
5579
5580			// start loading tracks
5581			player.loadNextTrack();
5582
5583			media.addEventListener('timeupdate',function() {
5584				player.displayCaptions();
5585			}, false);
5586
5587			if (player.options.slidesSelector !== '') {
5588				player.slidesContainer = $(player.options.slidesSelector);
5589
5590				media.addEventListener('timeupdate',function() {
5591					player.displaySlides();
5592				}, false);
5593
5594			}
5595
5596			media.addEventListener('loadedmetadata', function() {
5597				player.displayChapters();
5598			}, false);
5599
5600			player.container.hover(
5601				function () {
5602					// chapters
5603					if (player.hasChapters) {
5604						player.chapters.removeClass('mejs-offscreen');
5605						player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight());
5606					}
5607				},
5608				function () {
5609					if (player.hasChapters && !media.paused) {
5610						player.chapters.fadeOut(200, function() {
5611							$(this).addClass('mejs-offscreen');
5612							$(this).css('display','block');
5613						});
5614					}
5615				});
5616
5617			t.container.on('controlsresize', function() {
5618				t.adjustLanguageBox();
5619			});
5620
5621			// check for autoplay
5622			if (player.node.getAttribute('autoplay') !== null) {
5623				player.chapters.addClass('mejs-offscreen');
5624			}
5625		},
5626
5627		setTrack: function(lang){
5628
5629			var t = this,
5630				i;
5631
5632			if (lang == 'none') {
5633				t.selectedTrack = null;
5634				t.captionsButton.removeClass('mejs-captions-enabled');
5635			} else {
5636				for (i=0; i<t.tracks.length; i++) {
5637					if (t.tracks[i].srclang == lang) {
5638						if (t.selectedTrack === null)
5639							t.captionsButton.addClass('mejs-captions-enabled');
5640						t.selectedTrack = t.tracks[i];
5641						t.captions.attr('lang', t.selectedTrack.srclang);
5642						t.displayCaptions();
5643						break;
5644					}
5645				}
5646			}
5647		},
5648
5649		loadNextTrack: function() {
5650			var t = this;
5651
5652			t.trackToLoad++;
5653			if (t.trackToLoad < t.tracks.length) {
5654				t.isLoadingTrack = true;
5655				t.loadTrack(t.trackToLoad);
5656			} else {
5657				// add done?
5658				t.isLoadingTrack = false;
5659
5660				t.checkForTracks();
5661			}
5662		},
5663
5664		loadTrack: function(index){
5665			var
5666				t = this,
5667				track = t.tracks[index],
5668				after = function() {
5669
5670					track.isLoaded = true;
5671
5672					t.enableTrackButton(track.srclang, track.label);
5673
5674					t.loadNextTrack();
5675
5676				};
5677
5678
5679			if (track.src !== undefined || track.src !== "") {
5680				$.ajax({
5681					url: track.src,
5682					dataType: "text",
5683					success: function(d) {
5684
5685						// parse the loaded file
5686						if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) {
5687							track.entries = mejs.TrackFormatParser.dfxp.parse(d);
5688						} else {
5689							track.entries = mejs.TrackFormatParser.webvtt.parse(d);
5690						}
5691
5692						after();
5693
5694						if (track.kind == 'chapters') {
5695							t.media.addEventListener('play', function() {
5696								if (t.media.duration > 0) {
5697									t.displayChapters(track);
5698								}
5699							}, false);
5700						}
5701
5702						if (track.kind == 'slides') {
5703							t.setupSlides(track);
5704						}
5705					},
5706					error: function() {
5707						t.removeTrackButton(track.srclang);
5708						t.loadNextTrack();
5709					}
5710				});
5711			}
5712		},
5713
5714		enableTrackButton: function(lang, label) {
5715			var t = this;
5716
5717			if (label === '') {
5718				label = mejs.language.codes[lang] || lang;
5719			}
5720
5721			t.captionsButton
5722				.find('input[value=' + lang + ']')
5723					.prop('disabled',false)
5724				.siblings('label')
5725					.html( label );
5726
5727			// auto select
5728			if (t.options.startLanguage == lang) {
5729				$('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click');
5730			}
5731
5732			t.adjustLanguageBox();
5733		},
5734
5735		removeTrackButton: function(lang) {
5736			var t = this;
5737
5738			t.captionsButton.find('input[value=' + lang + ']').closest('li').remove();
5739
5740			t.adjustLanguageBox();
5741		},
5742
5743		addTrackButton: function(lang, label) {
5744			var t = this;
5745			if (label === '') {
5746				label = mejs.language.codes[lang] || lang;
5747			}
5748
5749			t.captionsButton.find('ul').append(
5750				$('<li>'+
5751					'<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' +
5752					'<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+
5753				'</li>')
5754			);
5755
5756			t.adjustLanguageBox();
5757
5758			// remove this from the dropdownlist (if it exists)
5759			t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove();
5760		},
5761
5762		adjustLanguageBox:function() {
5763			var t = this;
5764			// adjust the size of the outer box
5765			t.captionsButton.find('.mejs-captions-selector').height(
5766				t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) +
5767				t.captionsButton.find('.mejs-captions-translations').outerHeight(true)
5768			);
5769		},
5770
5771		checkForTracks: function() {
5772			var
5773				t = this,
5774				hasSubtitles = false;
5775
5776			// check if any subtitles
5777			if (t.options.hideCaptionsButtonWhenEmpty) {
5778				for (var i=0; i<t.tracks.length; i++) {
5779					var kind = t.tracks[i].kind;
5780					if ((kind === 'subtitles' || kind === 'captions') && t.tracks[i].isLoaded) {
5781						hasSubtitles = true;
5782						break;
5783					}
5784				}
5785
5786				if (!hasSubtitles) {
5787					t.captionsButton.hide();
5788					t.setControlsSize();
5789				}
5790			}
5791		},
5792
5793		displayCaptions: function() {
5794
5795			if (typeof this.tracks == 'undefined')
5796				return;
5797
5798			var
5799				t = this,
5800				i,
5801				track = t.selectedTrack;
5802
5803			if (track !== null && track.isLoaded) {
5804				for (i=0; i<track.entries.times.length; i++) {
5805					if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) {
5806						// Set the line before the timecode as a class so the cue can be targeted if needed
5807						t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || ''));
5808						t.captions.show().height(0);
5809						return; // exit out if one is visible;
5810					}
5811				}
5812				t.captions.hide();
5813			} else {
5814				t.captions.hide();
5815			}
5816		},
5817
5818		setupSlides: function(track) {
5819			var t = this;
5820
5821			t.slides = track;
5822			t.slides.entries.imgs = [t.slides.entries.text.length];
5823			t.showSlide(0);
5824
5825		},
5826
5827		showSlide: function(index) {
5828			if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') {
5829				return;
5830			}
5831
5832			var t = this,
5833				url = t.slides.entries.text[index],
5834				img = t.slides.entries.imgs[index];
5835
5836			if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') {
5837
5838				t.slides.entries.imgs[index] = img = $('<img src="' + url + '">')
5839						.on('load', function() {
5840							img.appendTo(t.slidesContainer)
5841								.hide()
5842								.fadeIn()
5843								.siblings(':visible')
5844									.fadeOut();
5845
5846						});
5847
5848			} else {
5849
5850				if (!img.is(':visible') && !img.is(':animated')) {
5851
5852					//
5853
5854					img.fadeIn()
5855						.siblings(':visible')
5856							.fadeOut();
5857				}
5858			}
5859
5860		},
5861
5862		displaySlides: function() {
5863
5864			if (typeof this.slides == 'undefined')
5865				return;
5866
5867			var
5868				t = this,
5869				slides = t.slides,
5870				i;
5871
5872			for (i=0; i<slides.entries.times.length; i++) {
5873				if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){
5874
5875					t.showSlide(i);
5876
5877					return; // exit out if one is visible;
5878				}
5879			}
5880		},
5881
5882		displayChapters: function() {
5883			var
5884				t = this,
5885				i;
5886
5887			for (i=0; i<t.tracks.length; i++) {
5888				if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) {
5889					t.drawChapters(t.tracks[i]);
5890					t.hasChapters = true;
5891					break;
5892				}
5893			}
5894		},
5895
5896		drawChapters: function(chapters) {
5897			var
5898				t = this,
5899				i,
5900				dur,
5901				//width,
5902				//left,
5903				percent = 0,
5904				usedPercent = 0;
5905
5906			t.chapters.empty();
5907
5908			for (i=0; i<chapters.entries.times.length; i++) {
5909				dur = chapters.entries.times[i].stop - chapters.entries.times[i].start;
5910				percent = Math.floor(dur / t.media.duration * 100);
5911				if (percent + usedPercent > 100 || // too large
5912					i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in
5913					{
5914					percent = 100 - usedPercent;
5915				}
5916				//width = Math.floor(t.width * dur / t.media.duration);
5917				//left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration);
5918				//if (left + width > t.width) {
5919				//	width = t.width - left;
5920				//}
5921
5922				t.chapters.append( $(
5923					'<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' +
5924						'<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' +
5925							'<span class="ch-title">' + chapters.entries.text[i] + '</span>' +
5926							'<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start, t.options) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop, t.options) + '</span>' +
5927						'</div>' +
5928					'</div>'));
5929				usedPercent += percent;
5930			}
5931
5932			t.chapters.find('div.mejs-chapter').click(function() {
5933				t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) );
5934				if (t.media.paused) {
5935					t.media.play();
5936				}
5937			});
5938
5939			t.chapters.show();
5940		}
5941	});
5942
5943
5944
5945	mejs.language = {
5946		codes:  {
5947			af:'Afrikaans',
5948			sq:'Albanian',
5949			ar:'Arabic',
5950			be:'Belarusian',
5951			bg:'Bulgarian',
5952			ca:'Catalan',
5953			zh:'Chinese',
5954			'zh-cn':'Chinese Simplified',
5955			'zh-tw':'Chinese Traditional',
5956			hr:'Croatian',
5957			cs:'Czech',
5958			da:'Danish',
5959			nl:'Dutch',
5960			en:'English',
5961			et:'Estonian',
5962			fl:'Filipino',
5963			fi:'Finnish',
5964			fr:'French',
5965			gl:'Galician',
5966			de:'German',
5967			el:'Greek',
5968			ht:'Haitian Creole',
5969			iw:'Hebrew',
5970			hi:'Hindi',
5971			hu:'Hungarian',
5972			is:'Icelandic',
5973			id:'Indonesian',
5974			ga:'Irish',
5975			it:'Italian',
5976			ja:'Japanese',
5977			ko:'Korean',
5978			lv:'Latvian',
5979			lt:'Lithuanian',
5980			mk:'Macedonian',
5981			ms:'Malay',
5982			mt:'Maltese',
5983			no:'Norwegian',
5984			fa:'Persian',
5985			pl:'Polish',
5986			pt:'Portuguese',
5987			// 'pt-pt':'Portuguese (Portugal)',
5988			ro:'Romanian',
5989			ru:'Russian',
5990			sr:'Serbian',
5991			sk:'Slovak',
5992			sl:'Slovenian',
5993			es:'Spanish',
5994			sw:'Swahili',
5995			sv:'Swedish',
5996			tl:'Tagalog',
5997			th:'Thai',
5998			tr:'Turkish',
5999			uk:'Ukrainian',
6000			vi:'Vietnamese',
6001			cy:'Welsh',
6002			yi:'Yiddish'
6003		}
6004	};
6005
6006	/*
6007	Parses WebVTT format which should be formatted as
6008	================================
6009	WEBVTT
6010
6011	1
6012	00:00:01,1 --> 00:00:05,000
6013	A line of text
6014
6015	2
6016	00:01:15,1 --> 00:02:05,000
6017	A second line of text
6018
6019	===============================
6020
6021	Adapted from: http://www.delphiki.com/html5/playr
6022	*/
6023	mejs.TrackFormatParser = {
6024		webvtt: {
6025			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})?)(.*)$/,
6026
6027			parse: function(trackText) {
6028				var
6029					i = 0,
6030					lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/),
6031					entries = {text:[], times:[]},
6032					timecode,
6033					text,
6034					identifier;
6035				for(; i<lines.length; i++) {
6036					timecode = this.pattern_timecode.exec(lines[i]);
6037
6038					if (timecode && i<lines.length) {
6039						if ((i - 1) >= 0 && lines[i - 1] !== '') {
6040							identifier = lines[i - 1];
6041						}
6042						i++;
6043						// grab all the (possibly multi-line) text that follows
6044						text = lines[i];
6045						i++;
6046						while(lines[i] !== '' && i<lines.length){
6047							text = text + '\n' + lines[i];
6048							i++;
6049						}
6050						text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
6051						// Text is in a different array so I can use .join
6052						entries.text.push(text);
6053						entries.times.push(
6054						{
6055							identifier: identifier,
6056							start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]),
6057							stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]),
6058							settings: timecode[5]
6059						});
6060					}
6061					identifier = '';
6062				}
6063				return entries;
6064			}
6065		},
6066		// Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420
6067		dfxp: {
6068			parse: function(trackText) {
6069				trackText = $(trackText).filter("tt");
6070				var
6071					i = 0,
6072					container = trackText.children("div").eq(0),
6073					lines = container.find("p"),
6074					styleNode = trackText.find("#" + container.attr("style")),
6075					styles,
6076					text,
6077					entries = {text:[], times:[]};
6078
6079
6080				if (styleNode.length) {
6081					var attributes = styleNode.removeAttr("id").get(0).attributes;
6082					if (attributes.length) {
6083						styles = {};
6084						for (i = 0; i < attributes.length; i++) {
6085							styles[attributes[i].name.split(":")[1]] = attributes[i].value;
6086						}
6087					}
6088				}
6089
6090				for(i = 0; i<lines.length; i++) {
6091					var style;
6092					var _temp_times = {
6093						start: null,
6094						stop: null,
6095						style: null
6096					};
6097					if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin"));
6098					if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end"));
6099					if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end"));
6100					if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin"));
6101					if (styles) {
6102						style = "";
6103						for (var _style in styles) {
6104							style += _style + ":" + styles[_style] + ";";
6105						}
6106					}
6107					if (style) _temp_times.style = style;
6108					if (_temp_times.start === 0) _temp_times.start = 0.200;
6109					entries.times.push(_temp_times);
6110					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>");
6111					entries.text.push(text);
6112				}
6113				return entries;
6114			}
6115		},
6116		split2: function (text, regex) {
6117			// normal version for compliant browsers
6118			// see below for IE fix
6119			return text.split(regex);
6120		}
6121	};
6122
6123	// test for browsers with bad String.split method.
6124	if ('x\n\ny'.split(/\n/gi).length != 3) {
6125		// add super slow IE8 and below version
6126		mejs.TrackFormatParser.split2 = function(text, regex) {
6127			var
6128				parts = [],
6129				chunk = '',
6130				i;
6131
6132			for (i=0; i<text.length; i++) {
6133				chunk += text.substring(i,i+1);
6134				if (regex.test(chunk)) {
6135					parts.push(chunk.replace(regex, ''));
6136					chunk = '';
6137				}
6138			}
6139			parts.push(chunk);
6140			return parts;
6141		};
6142	}
6143
6144})(mejs.$);
6145
6146// Source Chooser Plugin
6147(function($) {
6148
6149	$.extend(mejs.MepDefaults, {
6150		sourcechooserText: ''
6151	});
6152
6153	$.extend(MediaElementPlayer.prototype, {
6154		buildsourcechooser: function(player, controls, layers, media) {
6155
6156			var
6157				t = this,
6158				sourceTitle = t.options.sourcechooserText ? t.options.sourcechooserText : mejs.i18n.t('mejs.source-chooser'),
6159				hoverTimeout
6160			;
6161
6162			player.sourcechooserButton =
6163				$('<div class="mejs-button mejs-sourcechooser-button">'+
6164						'<button type="button" role="button" aria-haspopup="true" aria-owns="' + t.id + '" title="' + sourceTitle + '" aria-label="' + sourceTitle + '"></button>'+
6165						'<div class="mejs-sourcechooser-selector mejs-offscreen" role="menu" aria-expanded="false" aria-hidden="true">'+
6166							'<ul>'+
6167							'</ul>'+
6168						'</div>'+
6169					'</div>')
6170					.appendTo(controls)
6171
6172					// hover
6173					.hover(function() {
6174						clearTimeout(hoverTimeout);
6175						player.showSourcechooserSelector();
6176					}, function() {
6177						var self = $(this);
6178						hoverTimeout = setTimeout(function () {
6179						player.hideSourcechooserSelector();
6180						}, 500);
6181					})
6182
6183					// keyboard menu activation
6184					.on('keydown', function (e) {
6185						var keyCode = e.keyCode;
6186
6187						switch (keyCode) {
6188							case 32: // space
6189								if (!mejs.MediaFeatures.isFirefox) { // space sends the click event in Firefox
6190									player.showSourcechooserSelector();
6191								}
6192								$(this).find('.mejs-sourcechooser-selector')
6193									.find('input[type=radio]:checked').first().focus();
6194								break;
6195							case 13: // enter
6196								player.showSourcechooserSelector();
6197								$(this).find('.mejs-sourcechooser-selector')
6198									.find('input[type=radio]:checked').first().focus();
6199								break;
6200							case 27: // esc
6201								player.hideSourcechooserSelector();
6202								$(this).find('button').focus();
6203								break;
6204							default:
6205								return true;
6206								}
6207							})
6208
6209					// close menu when tabbing away
6210					.on('focusout', mejs.Utility.debounce(function (e) { // Safari triggers focusout multiple times
6211						// Firefox does NOT support e.relatedTarget to see which element
6212						// just lost focus, so wait to find the next focused element
6213						setTimeout(function () {
6214							var parent = $(document.activeElement).closest('.mejs-sourcechooser-selector');
6215							if (!parent.length) {
6216								// focus is outside the control; close menu
6217								player.hideSourcechooserSelector();
6218							}
6219						}, 0);
6220					}, 100))
6221
6222					// handle clicks to the source radio buttons
6223					.delegate('input[type=radio]', 'click', function() {
6224						// set aria states
6225						$(this).attr('aria-selected', true).attr('checked', 'checked');
6226						$(this).closest('.mejs-sourcechooser-selector').find('input[type=radio]').not(this).attr('aria-selected', 'false').removeAttr('checked');
6227
6228						var src = this.value;
6229
6230						if (media.currentSrc != src) {
6231							var currentTime = media.currentTime;
6232							var paused = media.paused;
6233							media.pause();
6234							media.setSrc(src);
6235
6236							media.addEventListener('loadedmetadata', function(e) {
6237								media.currentTime = currentTime;
6238							}, true);
6239
6240							var canPlayAfterSourceSwitchHandler = function(e) {
6241								if (!paused) {
6242									media.play();
6243								}
6244								media.removeEventListener("canplay", canPlayAfterSourceSwitchHandler, true);
6245							};
6246							media.addEventListener('canplay', canPlayAfterSourceSwitchHandler, true);
6247							media.load();
6248						}
6249					})
6250
6251					// Handle click so that screen readers can toggle the menu
6252					.delegate('button', 'click', function (e) {
6253						if ($(this).siblings('.mejs-sourcechooser-selector').hasClass('mejs-offscreen')) {
6254							player.showSourcechooserSelector();
6255							$(this).siblings('.mejs-sourcechooser-selector').find('input[type=radio]:checked').first().focus();
6256						} else {
6257							player.hideSourcechooserSelector();
6258						}
6259					});
6260
6261			// add to list
6262			for (var i in this.node.children) {
6263				var src = this.node.children[i];
6264				if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) == 'probably' || media.canPlayType(src.type) == 'maybe')) {
6265					player.addSourceButton(src.src, src.title, src.type, media.src == src.src);
6266				}
6267			}
6268		},
6269
6270		addSourceButton: function(src, label, type, isCurrent) {
6271			var t = this;
6272			if (label === '' || label == undefined) {
6273				label = src;
6274			}
6275			type = type.split('/')[1];
6276
6277			t.sourcechooserButton.find('ul').append(
6278				$('<li>'+
6279						'<input type="radio" name="' + t.id + '_sourcechooser" id="' + t.id + '_sourcechooser_' + label + type + '" role="menuitemradio" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + 'aria-selected="' + isCurrent + '"' + ' />'+
6280						'<label for="' + t.id + '_sourcechooser_' + label + type + '" aria-hidden="true">' + label + ' (' + type + ')</label>'+
6281					'</li>')
6282			);
6283
6284			t.adjustSourcechooserBox();
6285
6286		},
6287
6288		adjustSourcechooserBox: function() {
6289			var t = this;
6290			// adjust the size of the outer box
6291			t.sourcechooserButton.find('.mejs-sourcechooser-selector').height(
6292				t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true)
6293			);
6294		},
6295
6296		hideSourcechooserSelector: function () {
6297			this.sourcechooserButton.find('.mejs-sourcechooser-selector')
6298				.addClass('mejs-offscreen')
6299				.attr('aria-expanded', 'false')
6300				.attr('aria-hidden', 'true')
6301				.find('input[type=radio]') // make radios not fucusable
6302				.attr('tabindex', '-1');
6303		},
6304
6305		showSourcechooserSelector: function () {
6306			this.sourcechooserButton.find('.mejs-sourcechooser-selector')
6307				.removeClass('mejs-offscreen')
6308				.attr('aria-expanded', 'true')
6309				.attr('aria-hidden', 'false')
6310				.find('input[type=radio]')
6311				.attr('tabindex', '0');
6312		}
6313	});
6314
6315})(mejs.$);
6316
6317/*
6318* ContextMenu Plugin
6319* 
6320*
6321*/
6322
6323(function($) {
6324
6325$.extend(mejs.MepDefaults,
6326	{ 'contextMenuItems': [
6327		// demo of a fullscreen option
6328		{ 
6329			render: function(player) {
6330				
6331				// check for fullscreen plugin
6332				if (typeof player.enterFullScreen == 'undefined')
6333					return null;
6334			
6335				if (player.isFullScreen) {
6336					return mejs.i18n.t('mejs.fullscreen-off');
6337				} else {
6338					return mejs.i18n.t('mejs.fullscreen-on');
6339				}
6340			},
6341			click: function(player) {
6342				if (player.isFullScreen) {
6343					player.exitFullScreen();
6344				} else {
6345					player.enterFullScreen();
6346				}
6347			}
6348		}
6349		,
6350		// demo of a mute/unmute button
6351		{ 
6352			render: function(player) {
6353				if (player.media.muted) {
6354					return mejs.i18n.t('mejs.unmute');
6355				} else {
6356					return mejs.i18n.t('mejs.mute');
6357				}
6358			},
6359			click: function(player) {
6360				if (player.media.muted) {
6361					player.setMuted(false);
6362				} else {
6363					player.setMuted(true);
6364				}
6365			}
6366		},
6367		// separator
6368		{
6369			isSeparator: true
6370		}
6371		,
6372		// demo of simple download video
6373		{ 
6374			render: function(player) {
6375				return mejs.i18n.t('mejs.download-video');
6376			},
6377			click: function(player) {
6378				window.location.href = player.media.currentSrc;
6379			}
6380		}	
6381	]}
6382);
6383
6384
6385	$.extend(MediaElementPlayer.prototype, {
6386		buildcontextmenu: function(player, controls, layers, media) {
6387			
6388			// create context menu
6389			player.contextMenu = $('<div class="mejs-contextmenu"></div>')
6390								.appendTo($('body'))
6391								.hide();
6392			
6393			// create events for showing context menu
6394			player.container.bind('contextmenu', function(e) {
6395				if (player.isContextMenuEnabled) {
6396					e.preventDefault();
6397					player.renderContextMenu(e.clientX-1, e.clientY-1);
6398					return false;
6399				}
6400			});
6401			player.container.bind('click', function() {
6402				player.contextMenu.hide();
6403			});	
6404			player.contextMenu.bind('mouseleave', function() {
6405
6406				//
6407				player.startContextMenuTimer();
6408				
6409			});		
6410		},
6411
6412		cleancontextmenu: function(player) {
6413			player.contextMenu.remove();
6414		},
6415		
6416		isContextMenuEnabled: true,
6417		enableContextMenu: function() {
6418			this.isContextMenuEnabled = true;
6419		},
6420		disableContextMenu: function() {
6421			this.isContextMenuEnabled = false;
6422		},
6423		
6424		contextMenuTimeout: null,
6425		startContextMenuTimer: function() {
6426			//
6427			
6428			var t = this;
6429			
6430			t.killContextMenuTimer();
6431			
6432			t.contextMenuTimer = setTimeout(function() {
6433				t.hideContextMenu();
6434				t.killContextMenuTimer();
6435			}, 750);
6436		},
6437		killContextMenuTimer: function() {
6438			var timer = this.contextMenuTimer;
6439			
6440			//
6441			
6442			if (timer != null) {				
6443				clearTimeout(timer);
6444				delete timer;
6445				timer = null;
6446			}
6447		},		
6448		
6449		hideContextMenu: function() {
6450			this.contextMenu.hide();
6451		},
6452		
6453		renderContextMenu: function(x,y) {
6454			
6455			// alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly
6456			var t = this,
6457				html = '',
6458				items = t.options.contextMenuItems;
6459			
6460			for (var i=0, il=items.length; i<il; i++) {
6461				
6462				if (items[i].isSeparator) {
6463					html += '<div class="mejs-contextmenu-separator"></div>';
6464				} else {
6465				
6466					var rendered = items[i].render(t);
6467				
6468					// render can return null if the item doesn't need to be used at the moment
6469					if (rendered != null) {
6470						html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>';
6471					}
6472				}
6473			}
6474			
6475			// position and show the context menu
6476			t.contextMenu
6477				.empty()
6478				.append($(html))
6479				.css({top:y, left:x})
6480				.show();
6481				
6482			// bind events
6483			t.contextMenu.find('.mejs-contextmenu-item').each(function() {
6484							
6485				// which one is this?
6486				var $dom = $(this),
6487					itemIndex = parseInt( $dom.data('itemindex'), 10 ),
6488					item = t.options.contextMenuItems[itemIndex];
6489				
6490				// bind extra functionality?
6491				if (typeof item.show != 'undefined')
6492					item.show( $dom , t);
6493				
6494				// bind click action
6495				$dom.click(function() {			
6496					// perform click action
6497					if (typeof item.click != 'undefined')
6498						item.click(t);
6499					
6500					// close
6501					t.contextMenu.hide();				
6502				});				
6503			});	
6504			
6505			// stop the controls from hiding
6506			setTimeout(function() {
6507				t.killControlsTimer('rev3');	
6508			}, 100);
6509						
6510		}
6511	});
6512	
6513})(mejs.$);
6514(function($) {
6515	// skip back button
6516
6517	$.extend(mejs.MepDefaults, {
6518		skipBackInterval: 30,
6519		// %1 will be replaced with skipBackInterval in this string
6520		skipBackText: ''
6521	});
6522
6523	$.extend(MediaElementPlayer.prototype, {
6524		buildskipback: function(player, controls, layers, media) {
6525			var
6526				t = this,
6527				defaultTitle = mejs.i18n.t('mejs.time-skip-back', t.options.skipBackInterval),
6528				skipTitle = t.options.skipBackText ? t.options.skipBackText : defaultTitle,
6529				// create the loop button
6530				loop =
6531				$('<div class="mejs-button mejs-skip-back-button">' +
6532					'<button type="button" aria-controls="' + t.id + '" title="' + skipTitle + '" aria-label="' + skipTitle + '">' + t.options.skipBackInterval + '</button>' +
6533				'</div>')
6534				// append it to the toolbar
6535				.appendTo(controls)
6536				// add a click toggle event
6537				.click(function() {
6538					media.setCurrentTime(Math.max(media.currentTime - t.options.skipBackInterval, 0));
6539					$(this).find('button').blur();
6540				});
6541		}
6542	});
6543
6544})(mejs.$);
6545
6546/**
6547 * Postroll plugin
6548 */
6549(function($) {
6550
6551	$.extend(mejs.MepDefaults, {
6552		postrollCloseText: ''
6553	});
6554
6555	// Postroll
6556	$.extend(MediaElementPlayer.prototype, {
6557		buildpostroll: function(player, controls, layers, media) {
6558			var
6559				t = this,
6560				postrollTitle = t.options.postrollCloseText ? t.options.postrollCloseText : mejs.i18n.t('mejs.close'),
6561				postrollLink = t.container.find('link[rel="postroll"]').attr('href');
6562
6563			if (typeof postrollLink !== 'undefined') {
6564				player.postroll =
6565					$('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + postrollTitle + '</a>
6565<div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide();
6566
6567				t.media.addEventListener('ended', function (e) {
6568					$.ajax({
6569						dataType: 'html',
6570						url: postrollLink,
6571						success: function (data, textStatus) {
6572							layers.find('.mejs-postroll-layer-content').html(data);
6573						}
6574					});
6575					player.postroll.show();
6576				}, false);
6577			}
6578		}
6579	});
6580
6581})(mejs.$);
6582/*
6583MediaElement-Markers is a MediaElement.js plugin that lets you add Visual Cues in the progress time rail. 
6584This plugin also lets you register a custom callback function that will be called everytime the play position reaches a marker. 
6585Marker position and a reference to the MediaElement Player object is passed to the registered callback function for any post processing. Marker color is configurable.
6586
6587*/
6588
6589(function ($) {
6590    // markers
6591
6592    $.extend(mejs.MepDefaults, {
6593        markerColor: '#E9BC3D', //default marker color
6594        markers: [],
6595        markerCallback: function () {
6596
6597        }
6598    });
6599
6600    $.extend(MediaElementPlayer.prototype, {
6601        buildmarkers: function (player, controls, layers, media) {
6602            var t = this,
6603                i = 0,
6604                currentPos = -1,
6605                currentMarker = -1,
6606                lastPlayPos = -1, //Track backward seek
6607                lastMarkerCallBack = -1; //Prevents successive firing of callbacks
6608
6609            for (i = 0; i < player.options.markers.length; ++i) {
6610                controls.find('.mejs-time-total').append('<span class="mejs-time-marker"></span>');
6611            }
6612
6613            media.addEventListener('durationchange', function (e) {
6614                player.setmarkers(controls);
6615            });
6616            media.addEventListener('timeupdate', function (e) {
6617                currentPos = Math.floor(media.currentTime);
6618                if (lastPlayPos > currentPos) {
6619                    if (lastMarkerCallBack > currentPos) {
6620                        lastMarkerCallBack = -1;
6621                    }
6622                } else {
6623                    lastPlayPos = currentPos;
6624                }
6625
6626                for (i = 0; i < player.options.markers.length; ++i) {
6627                    currentMarker = Math.floor(player.options.markers[i]); 
6628                    if (currentPos === currentMarker && currentMarker !== lastMarkerCallBack) {
6629                        player.options.markerCallback(media, media.currentTime); //Fires the callback function
6630                        lastMarkerCallBack = currentMarker;
6631                    }
6632                }
6633
6634            }, false);
6635
6636        },
6637        setmarkers: function (controls) {
6638            var t = this,
6639                i = 0,
6640                left;
6641
6642            for (i = 0; i < t.options.markers.length; ++i) {
6643                if (Math.floor(t.options.markers[i]) <= t.media.duration && Math.floor(t.options.markers[i]) >= 0) {
6644                    left = 100 * Math.floor(t.options.markers[i]) / t.media.duration;
6645                    $(controls.find('.mejs-time-marker')[i]).css({
6646                        "width": "1px",
6647                        "left": left+"%",
6648                        "background": t.options.markerColor
6649                    });
6650                }
6651            }
6652
6653        }
6654    });
6655})(mejs.$);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.