PageSourceSearch

https://taras-shevchenko.com.ua/frontend/webcontent/js/mediaelement/mediaelement-and-player.js?363

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