PageSourceSearch

https://www.steveharley.com/plugins/content/lightplayer/js/mediaelement-and-player.js

js steveharley.com collected 2026-10-02 13:22:42 UTC 149,862 bytes, 5,304 lines download raw bytes

1/*!
2* MediaElement.js
3* HTML5 <video> and <audio> shim and player
4* http://mediaelementjs.com/
5*
6* Creates a JavaScript object that mimics HTML5 MediaElement API
7* for browsers that don't understand HTML5 or can't play the provided codec
8* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3
9*
10* Copyright 2010-2014, John Dyer (http://j.hn)
11* License: MIT
12*
13*/
14// Namespace
15
16var mejs = mejs || {};
17
18// version number
19mejs.version = '2.15.1'; 
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(null, 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: 1,
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			'isvideo=' + ((playback.isVideo) ? "true" : "false"),
1272			'autoplay=' + ((autoplay) ? "true" : "false"),
1273			'preload=' + preload,
1274			'width=' + width,
1275			'startvolume=' + options.startVolume,
1276			'timerrate=' + options.timerRate,
1277			'flashstreamer=' + options.flashStreamer,
1278			'height=' + height,
1279      'pseudostreamstart=' + options.pseudoStreamingStartQueryParam];
1280
1281		if (playback.url !== null) {
1282			if (playback.method == 'flash') {
1283				initVars.push('file=' + mejs.Utility.encodeUrl(playback.url));
1284			} else {
1285				initVars.push('file=' + playback.url);
1286			}
1287		}
1288		if (options.enablePluginDebug) {
1289			initVars.push('debug=true');
1290		}
1291		if (options.enablePluginSmoothing) {
1292			initVars.push('smoothing=true');
1293		}
1294    if (options.enablePseudoStreaming) {
1295      initVars.push('pseudostreaming=true');
1296    }
1297		if (controls) {
1298			initVars.push('controls=true'); // shows controls in the plugin if desired
1299		}
1300		if (options.pluginVars) {
1301			initVars = initVars.concat(options.pluginVars);
1302		}		
1303
1304		switch (playback.method) {
1305			case 'silverlight':
1306				container.innerHTML =
1307'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' +
1308'<param name="initParams" value="' + initVars.join(',') + '" />' +
1309'<param name="windowless" value="true" />' +
1310'<param name="background" value="black" />' +
1311'<param name="minRuntimeVersion" value="3.0.0.0" />' +
1312'<param name="autoUpgrade" value="true" />' +
1313'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
1314'</object>';
1315					break;
1316
1317			case 'flash':
1318
1319				if (mejs.MediaFeatures.isIE) {
1320					specialIEContainer = document.createElement('div');
1321					container.appendChild(specialIEContainer);
1322					specialIEContainer.outerHTML =
1323'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
1324'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' +
1325'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
1326'<param name="flashvars" value="' + initVars.join('&amp;') + '" />' +
1327'<param name="quality" value="high" />' +
1328'<param name="bgcolor" value="#000000" />' +
1329'<param name="wmode" value="transparent" />' +
1330'<param name="allowScriptAccess" value="always" />' +
1331'<param name="allowFullScreen" value="true" />' +
1332'<param name="scale" value="default" />' + 
1333'</object>';
1334
1335				} else {
1336
1337					container.innerHTML =
1338'<embed id="' + pluginid + '" name="' + pluginid + '" ' +
1339'play="true" ' +
1340'loop="false" ' +
1341'quality="high" ' +
1342'bgcolor="#000000" ' +
1343'wmode="transparent" ' +
1344'allowScriptAccess="always" ' +
1345'allowFullScreen="true" ' +
1346'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' +
1347'src="' + options.pluginPath + options.flashName + '" ' +
1348'flashvars="' + initVars.join('&') + '" ' +
1349'width="' + width + '" ' +
1350'height="' + height + '" ' +
1351'scale="default"' + 
1352'class="mejs-shim"></embed>';
1353				}
1354				break;
1355			
1356			case 'youtube':
1357			
1358				
1359				var videoId;
1360				// youtu.be url from share button
1361				if (playback.url.lastIndexOf("youtu.be") != -1) {
1362					videoId = playback.url.substr(playback.url.lastIndexOf('/')+1);
1363					if (videoId.indexOf('?') != -1) {
1364						videoId = videoId.substr(0, videoId.indexOf('?'));
1365					}
1366				}
1367				else {
1368					videoId = playback.url.substr(playback.url.lastIndexOf('=')+1);
1369				}
1370				youtubeSettings = {
1371						container: container,
1372						containerId: container.id,
1373						pluginMediaElement: pluginMediaElement,
1374						pluginId: pluginid,
1375						videoId: videoId,
1376						height: height,
1377						width: width	
1378					};				
1379				
1380				if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) {
1381					mejs.YouTubeApi.createFlash(youtubeSettings);
1382				} else {
1383					mejs.YouTubeApi.enqueueIframe(youtubeSettings);		
1384				}
1385				
1386				break;
1387			
1388			// DEMO Code. Does NOT work.
1389			case 'vimeo':
1390				var player_id = pluginid + "_player";
1391				pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1);
1392				
1393				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 + '"></iframe>';
1394				if (typeof($f) == 'function') { // froogaloop available
1395					var player = $f(container.childNodes[0]);
1396					player.addEvent('ready', function() {
1397						$.extend( player, {
1398							playVideo: function() {
1399								player.api( 'play' );
1400							}, 
1401							stopVideo: function() {
1402								player.api( 'unload' );
1403							}, 
1404							pauseVideo: function() {
1405								player.api( 'pause' );
1406							}, 
1407							seekTo: function( seconds ) {
1408								player.api( 'seekTo', seconds );
1409							}, 
1410							setVolume: function( volume ) {
1411								player.api( 'setVolume', volume );
1412							}, 
1413							setMuted: function( muted ) {
1414								if( muted ) {
1415									player.lastVolume = player.api( 'getVolume' );
1416									player.api( 'setVolume', 0 );
1417								} else {
1418									player.api( 'setVolume', player.lastVolume );
1419									delete player.lastVolume;
1420								}
1421							}
1422						});
1423
1424						function createEvent(player, pluginMediaElement, eventName, e) {
1425							var obj = {
1426								type: eventName,
1427								target: pluginMediaElement
1428							};
1429							if (eventName == 'timeupdate') {
1430								pluginMediaElement.currentTime = obj.currentTime = e.seconds;
1431								pluginMediaElement.duration = obj.duration = e.duration;
1432							}
1433							pluginMediaElement.dispatchEvent(obj.type, obj);
1434						}
1435
1436						player.addEvent('play', function() {
1437							createEvent(player, pluginMediaElement, 'play');
1438							createEvent(player, pluginMediaElement, 'playing');
1439						});
1440
1441						player.addEvent('pause', function() {
1442							createEvent(player, pluginMediaElement, 'pause');
1443						});
1444
1445						player.addEvent('finish', function() {
1446							createEvent(player, pluginMediaElement, 'ended');
1447						});
1448
1449						player.addEvent('playProgress', function(e) {
1450							createEvent(player, pluginMediaElement, 'timeupdate', e);
1451						});
1452
1453						pluginMediaElement.pluginElement = container;
1454						pluginMediaElement.pluginApi = player;
1455
1456						// init mejs
1457						mejs.MediaPluginBridge.initPlugin(pluginid);
1458					});
1459				}
1460				else {
1461					console.warn("You need to include froogaloop for vimeo to work");
1462				}
1463				break;			
1464		}
1465		// hide original element
1466		htmlMediaElement.style.display = 'none';
1467		// prevent browser from autoplaying when using a plugin
1468		htmlMediaElement.removeAttribute('autoplay');
1469
1470		// FYI: options.success will be fired by the MediaPluginBridge
1471		
1472		return pluginMediaElement;
1473	},
1474
1475	updateNative: function(playback, options, autoplay, preload) {
1476		
1477		var htmlMediaElement = playback.htmlMediaElement,
1478			m;
1479		
1480		
1481		// add methods to video object to bring it into parity with Flash Object
1482		for (m in mejs.HtmlMediaElement) {
1483			htmlMediaElement[m] = mejs.HtmlMediaElement[m];
1484		}
1485
1486		/*
1487		Chrome now supports preload="none"
1488		if (mejs.MediaFeatures.isChrome) {
1489		
1490			// special case to enforce preload attribute (Chrome doesn't respect this)
1491			if (preload === 'none' && !autoplay) {
1492			
1493				// forces the browser to stop loading (note: fails in IE9)
1494				htmlMediaElement.src = '';
1495				htmlMediaElement.load();
1496				htmlMediaElement.canceledPreload = true;
1497
1498				htmlMediaElement.addEventListener('play',function() {
1499					if (htmlMediaElement.canceledPreload) {
1500						htmlMediaElement.src = playback.url;
1501						htmlMediaElement.load();
1502						htmlMediaElement.play();
1503						htmlMediaElement.canceledPreload = false;
1504					}
1505				}, false);
1506			// for some reason Chrome forgets how to autoplay sometimes.
1507			} else if (autoplay) {
1508				htmlMediaElement.load();
1509				htmlMediaElement.play();
1510			}
1511		}
1512		*/
1513
1514		// fire success code
1515		options.success(htmlMediaElement, htmlMediaElement);
1516		
1517		return htmlMediaElement;
1518	}
1519};
1520
1521/*
1522 - test on IE (object vs. embed)
1523 - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE)
1524 - fullscreen?
1525*/
1526
1527// YouTube Flash and Iframe API
1528mejs.YouTubeApi = {
1529	isIframeStarted: false,
1530	isIframeLoaded: false,
1531	loadIframeApi: function() {
1532		if (!this.isIframeStarted) {
1533			var tag = document.createElement('script');
1534			tag.src = "//www.youtube.com/player_api";
1535			var firstScriptTag = document.getElementsByTagName('script')[0];
1536			firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
1537			this.isIframeStarted = true;
1538		}
1539	},
1540	iframeQueue: [],
1541	enqueueIframe: function(yt) {
1542		
1543		if (this.isLoaded) {
1544			this.createIframe(yt);
1545		} else {
1546			this.loadIframeApi();
1547			this.iframeQueue.push(yt);
1548		}
1549	},
1550	createIframe: function(settings) {
1551		
1552		var
1553		pluginMediaElement = settings.pluginMediaElement,	
1554		player = new YT.Player(settings.containerId, {
1555			height: settings.height,
1556			width: settings.width,
1557			videoId: settings.videoId,
1558			playerVars: {controls:0},
1559			events: {
1560				'onReady': function() {
1561					
1562					// hook up iframe object to MEjs
1563					settings.pluginMediaElement.pluginApi = player;
1564					
1565					// init mejs
1566					mejs.MediaPluginBridge.initPlugin(settings.pluginId);
1567					
1568					// create timer
1569					setInterval(function() {
1570						mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate');
1571					}, 250);					
1572				},
1573				'onStateChange': function(e) {
1574					
1575					mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement);
1576					
1577				}
1578			}
1579		});
1580	},
1581	
1582	createEvent: function (player, pluginMediaElement, eventName) {
1583		var obj = {
1584			type: eventName,
1585			target: pluginMediaElement
1586		};
1587
1588		if (player && player.getDuration) {
1589			
1590			// time 
1591			pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime();
1592			pluginMediaElement.duration = obj.duration = player.getDuration();
1593			
1594			// state
1595			obj.paused = pluginMediaElement.paused;
1596			obj.ended = pluginMediaElement.ended;			
1597			
1598			// sound
1599			obj.muted = player.isMuted();
1600			obj.volume = player.getVolume() / 100;
1601			
1602			// progress
1603			obj.bytesTotal = player.getVideoBytesTotal();
1604			obj.bufferedBytes = player.getVideoBytesLoaded();
1605			
1606			// fake the W3C buffered TimeRange
1607			var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration;
1608			
1609			obj.target.buffered = obj.buffered = {
1610				start: function(index) {
1611					return 0;
1612				},
1613				end: function (index) {
1614					return bufferedTime;
1615				},
1616				length: 1
1617			};
1618
1619		}
1620		
1621		// send event up the chain
1622		pluginMediaElement.dispatchEvent(obj.type, obj);
1623	},	
1624	
1625	iFrameReady: function() {
1626		
1627		this.isLoaded = true;
1628		this.isIframeLoaded = true;
1629		
1630		while (this.iframeQueue.length > 0) {
1631			var settings = this.iframeQueue.pop();
1632			this.createIframe(settings);
1633		}	
1634	},
1635	
1636	// FLASH!
1637	flashPlayers: {},
1638	createFlash: function(settings) {
1639		
1640		this.flashPlayers[settings.pluginId] = settings;
1641		
1642		/*
1643		settings.container.innerHTML =
1644			'<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" ' +
1645				'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' +
1646				'<param name="allowScriptAccess" value="always">' +
1647				'<param name="wmode" value="transparent">' +
1648			'</object>';
1649		*/
1650
1651		var specialIEContainer,
1652			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';
1653			
1654		if (mejs.MediaFeatures.isIE) {
1655			
1656			specialIEContainer = document.createElement('div');
1657			settings.container.appendChild(specialIEContainer);
1658			specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/s
1658hockwave/cabs/flash/swflash.cab" ' +
1659'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' +
1660	'<param name="movie" value="' + youtubeUrl + '" />' +
1661	'<param name="wmode" value="transparent" />' +
1662	'<param name="allowScriptAccess" value="always" />' +
1663	'<param name="allowFullScreen" value="true" />' +
1664'</object>';
1665		} else {
1666		settings.container.innerHTML =
1667			'<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' +
1668				'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' +
1669				'<param name="allowScriptAccess" value="always">' +
1670				'<param name="wmode" value="transparent">' +
1671			'</object>';
1672		}		
1673		
1674	},
1675	
1676	flashReady: function(id) {
1677		var
1678			settings = this.flashPlayers[id],
1679			player = document.getElementById(id),
1680			pluginMediaElement = settings.pluginMediaElement;
1681		
1682		// hook up and return to MediaELementPlayer.success	
1683		pluginMediaElement.pluginApi = 
1684		pluginMediaElement.pluginElement = player;
1685		mejs.MediaPluginBridge.initPlugin(id);
1686		
1687		// load the youtube video
1688		player.cueVideoById(settings.videoId);
1689		
1690		var callbackName = settings.containerId + '_callback';
1691		
1692		window[callbackName] = function(e) {
1693			mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement);
1694		}
1695		
1696		player.addEventListener('onStateChange', callbackName);
1697		
1698		setInterval(function() {
1699			mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate');
1700		}, 250);
1701		
1702		mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay');
1703	},
1704	
1705	handleStateChange: function(youTubeState, player, pluginMediaElement) {
1706		switch (youTubeState) {
1707			case -1: // not started
1708				pluginMediaElement.paused = true;
1709				pluginMediaElement.ended = true;
1710				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata');
1711				//createYouTubeEvent(player, pluginMediaElement, 'loadeddata');
1712				break;
1713			case 0:
1714				pluginMediaElement.paused = false;
1715				pluginMediaElement.ended = true;
1716				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended');
1717				break;
1718			case 1:
1719				pluginMediaElement.paused = false;
1720				pluginMediaElement.ended = false;				
1721				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play');
1722				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing');
1723				break;
1724			case 2:
1725				pluginMediaElement.paused = true;
1726				pluginMediaElement.ended = false;				
1727				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause');
1728				break;
1729			case 3: // buffering
1730				mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress');
1731				break;
1732			case 5:
1733				// cued?
1734				break;						
1735			
1736		}			
1737		
1738	}
1739}
1740// IFRAME
1741function onYouTubePlayerAPIReady() {
1742	mejs.YouTubeApi.iFrameReady();
1743}
1744// FLASH
1745function onYouTubePlayerReady(id) {
1746	mejs.YouTubeApi.flashReady(id);
1747}
1748
1749window.mejs = mejs;
1750window.MediaElement = mejs.MediaElement;
1751
1752/*!
1753 * Adds Internationalization and localization to mediaelement.
1754 *
1755 * This file does not contain translations, you have to add the manually.
1756 * The schema is always the same: me-i18n-locale-[ISO_639-1 Code].js
1757 *
1758 * Examples are provided both for german and chinese translation.
1759 *
1760 *
1761 * What is the concept beyond i18n?
1762 *   http://en.wikipedia.org/wiki/Internationalization_and_localization
1763 *
1764 * What langcode should i use?
1765 *   http://en.wikipedia.org/wiki/ISO_639-1
1766 *
1767 *
1768 * License?
1769 *
1770 *   The i18n file uses methods from the Drupal project (drupal.js):
1771 *     - i18n.methods.t() (modified)
1772 *     - i18n.methods.checkPlain() (full copy)
1773 *
1774 *   The Drupal project is (like mediaelementjs) licensed under GPLv2.
1775 *    - http://drupal.org/licensing/faq/#q1
1776 *    - https://github.com/johndyer/mediaelement
1777 *    - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
1778 *
1779 *
1780 * @author
1781 *   Tim Latz ([email protected])
1782 *
1783 *
1784 * @params
1785 *  - context - document, iframe ..
1786 *  - exports - CommonJS, window ..
1787 *
1788 */
1789;(function(context, exports, undefined) {
1790    "use strict";
1791    var i18n = {
1792        "locale": {
1793            "language" : '',
1794            "strings" : {}
1795        },
1796        "methods" : {}
1797    };
1798// start i18n
1799
1800
1801    /**
1802     * Get language, fallback to browser's language if empty
1803     */
1804    i18n.getLanguage = function () {
1805        var language = i18n.locale.language || window.navigator.userLanguage || window.navigator.language;
1806        // convert to iso 639-1 (2-letters, lower case)
1807        return language.substr(0, 2).toLowerCase();
1808    };
1809
1810    // i18n fixes for compatibility with WordPress
1811    if ( typeof mejsL10n != 'undefined' ) {
1812        i18n.locale.language = mejsL10n.language;
1813    }
1814
1815
1816
1817    /**
1818     * Encode special characters in a plain-text string for display as HTML.
1819     */
1820    i18n.methods.checkPlain = function (str) {
1821        var character, regex,
1822        replace = {
1823            '&': '&amp;',
1824            '"': '&quot;',
1825            '<': '&lt;',
1826            '>': '&gt;'
1827        };
1828        str = String(str);
1829        for (character in replace) {
1830            if (replace.hasOwnProperty(character)) {
1831                regex = new RegExp(character, 'g');
1832                str = str.replace(regex, replace[character]);
1833            }
1834        }
1835        return str;
1836    };
1837
1838    /**
1839     * Translate strings to the page language or a given language.
1840     *
1841     *
1842     * @param str
1843     *   A string containing the English string to translate.
1844     *
1845     * @param options
1846     *   - 'context' (defaults to the default context): The context the source string
1847     *     belongs to.
1848     *
1849     * @return
1850     *   The translated string, escaped via i18n.methods.checkPlain()
1851     */
1852    i18n.methods.t = function (str, options) {
1853
1854        // Fetch the localized version of the string.
1855        if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) {
1856            str = i18n.locale.strings[options.context][str];
1857        }
1858
1859        return i18n.methods.checkPlain(str);
1860    };
1861
1862
1863    /**
1864     * Wrapper for i18n.methods.t()
1865     *
1866     * @see i18n.methods.t()
1867     * @throws InvalidArgumentException
1868     */
1869    i18n.t = function(str, options) {
1870
1871        if (typeof str === 'string' && str.length > 0) {
1872
1873            // check every time due language can change for
1874            // different reasons (translation, lang switcher ..)
1875            var language = i18n.getLanguage();
1876
1877            options = options || {
1878                "context" : language
1879            };
1880
1881            return i18n.methods.t(str, options);
1882        }
1883        else {
1884            throw {
1885                "name" : 'InvalidArgumentException',
1886                "message" : 'First argument is either not a string or empty.'
1887            };
1888        }
1889    };
1890
1891// end i18n
1892    exports.i18n = i18n;
1893}(document, mejs));
1894
1895// i18n fixes for compatibility with WordPress
1896;(function(exports, undefined) {
1897
1898    "use strict";
1899
1900    if ( typeof mejsL10n != 'undefined' ) {
1901        exports[mejsL10n.language] = mejsL10n.strings;
1902    }
1903
1904}(mejs.i18n.locale.strings));
1905
1906/*!
1907 * This is a i18n.locale language object.
1908 *
1909 * German translation by Tim Latz, [email protected]
1910 *
1911 * @author
1912 *   Tim Latz ([email protected])
1913 *
1914 * @see
1915 *   me-i18n.js
1916 *
1917 * @params
1918 *  - exports - CommonJS, window ..
1919 */
1920;(function(exports, undefined) {
1921
1922    "use strict";
1923
1924    if (typeof exports.de === 'undefined') {
1925        exports.de = {
1926            "Fullscreen" : "Vollbild",
1927            "Go Fullscreen" : "Vollbild an",
1928            "Turn off Fullscreen" : "Vollbild aus",
1929            "Close" : "Schließen"
1930        };
1931    }
1932
1933}(mejs.i18n.locale.strings));
1934/*!
1935 * This is a i18n.locale language object.
1936 *
1937 * Traditional chinese translation by Tim Latz, [email protected]
1938 *
1939 * @author
1940 *   Tim Latz ([email protected])
1941 *
1942 * @see
1943 *   me-i18n.js
1944 *
1945 * @params
1946 *  - exports - CommonJS, window ..
1947 */
1948;(function(exports, undefined) {
1949
1950    "use strict";
1951
1952    if (typeof exports.zh === 'undefined') {
1953        exports.zh = {
1954            "Fullscreen" : "全螢幕",
1955            "Go Fullscreen" : "全屏模式",
1956            "Turn off Fullscreen" : "退出全屏模式",
1957            "Close" : "關閉"
1958        };
1959    }
1960
1961}(mejs.i18n.locale.strings));
1962
1963
1964/*!
1965 * MediaElementPlayer
1966 * http://mediaelementjs.com/
1967 *
1968 * Creates a controller bar for HTML5 <video> add <audio> tags
1969 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper)
1970 *
1971 * Copyright 2010-2013, John Dyer (http://j.hn/)
1972 * License: MIT
1973 *
1974 */
1975if (typeof jQuery != 'undefined') {
1976	mejs.$ = jQuery;
1977} else if (typeof ender != 'undefined') {
1978	mejs.$ = ender;
1979}
1980(function ($) {
1981
1982	// default player values
1983	mejs.MepDefaults = {
1984		// url to poster (to fix iOS 3.x)
1985		poster: '',
1986		// When the video is ended, we can show the poster.
1987		showPosterWhenEnded: false,
1988		// default if the <video width> is not specified
1989		defaultVideoWidth: 480,
1990		// default if the <video height> is not specified
1991		defaultVideoHeight: 270,
1992		// if set, overrides <video width>
1993		videoWidth: -1,
1994		// if set, overrides <video height>
1995		videoHeight: -1,
1996		// default if the user doesn't specify
1997		defaultAudioWidth: 400,
1998		// default if the user doesn't specify
1999		defaultAudioHeight: 30,
2000
2001		// default amount to move back when back key is pressed
2002		defaultSeekBackwardInterval: function(media) {
2003			return (media.duration * 0.05);
2004		},
2005		// default amount to move forward when forward key is pressed
2006		defaultSeekForwardInterval: function(media) {
2007			return (media.duration * 0.05);
2008		},
2009
2010		// set dimensions via JS instead of CSS
2011		setDimensions: true,
2012
2013		// width of audio player
2014		audioWidth: -1,
2015		// height of audio player
2016		audioHeight: -1,
2017		// initial volume when the player starts (overrided by user cookie)
2018		startVolume: 1,
2019		// useful for <audio> player loops
2020		loop: false,
2021		// rewind to beginning when media ends
2022                autoRewind: true,
2023		// resize to media dimensions
2024		enableAutosize: true,
2025		// forces the hour marker (##:00:00)
2026		alwaysShowHours: false,
2027
2028		// show framecount in timecode (##:00:00:00)
2029		showTimecodeFrameCount: false,
2030		// used when showTimecodeFrameCount is set to true
2031		framesPerSecond: 25,
2032
2033		// automatically calculate the width of the progress bar based on the sizes of other elements
2034		autosizeProgress : true,
2035		// Hide controls when playing and mouse is not over the video
2036		alwaysShowControls: false,
2037		// Display the video control
2038		hideVideoControlsOnLoad: false,
2039		// Enable click video element to toggle play/pause
2040		clickToPlayPause: true,
2041		// force iPad's native controls
2042		iPadUseNativeControls: false,
2043		// force iPhone's native controls
2044		iPhoneUseNativeControls: false,
2045		// force Android's native controls
2046		AndroidUseNativeControls: false,
2047		// features to show
2048		features: ['playpause','current','progress','duration','tracks','volume','fullscreen'],
2049		// only for dynamic
2050		isVideo: true,
2051
2052		// turns keyboard support on and off for this instance
2053		enableKeyboard: true,
2054
2055		// whenthis player starts, it will pause other players
2056		pauseOtherPlayers: true,
2057
2058		// array of keyboard actions such as play pause
2059		keyActions: [
2060				{
2061						keys: [
2062								32, // SPACE
2063								179 // GOOGLE play/pause button
2064							  ],
2065						action: function(player, media) {
2066								if (media.paused || media.ended) {
2067										player.play();
2068								} else {
2069										player.pause();
2070								}
2071						}
2072				},
2073				{
2074						keys: [38], // UP
2075						action: function(player, media) {
2076								player.container.find('.mejs-volume-slider').css('display','block');
2077								if (player.isVideo) {
2078										player.showControls();
2079										player.startControlsTimer();
2080								}
2081
2082								var newVolume = Math.min(media.volume + 0.1, 1);
2083								media.setVolume(newVolume);
2084						}
2085				},
2086				{
2087						keys: [40], // DOWN
2088						action: function(player, media) {
2089								player.container.find('.mejs-volume-slider').css('display','block');
2090								if (player.isVideo) {
2091										player.showControls();
2092										player.startControlsTimer();
2093								}
2094
2095								var newVolume = Math.max(media.volume - 0.1, 0);
2096								media.setVolume(newVolume);
2097						}
2098				},
2099				{
2100						keys: [
2101								37, // LEFT
2102								227 // Google TV rewind
2103						],
2104						action: function(player, media) {
2105								if (!isNaN(media.duration) && media.duration > 0) {
2106										if (player.isVideo) {
2107												player.showControls();
2108												player.startControlsTimer();
2109										}
2110
2111										// 5%
2112										var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0);
2113										media.setCurrentTime(newTime);
2114								}
2115						}
2116				},
2117				{
2118						keys: [
2119								39, // RIGHT
2120								228 // Google TV forward
2121						],
2122						action: function(player, media) {
2123								if (!isNaN(media.duration) && media.duration > 0) {
2124										if (player.isVideo) {
2125												player.showControls();
2126												player.startControlsTimer();
2127										}
2128
2129										// 5%
2130										var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration);
2131										media.setCurrentTime(newTime);
2132								}
2133						}
2134				},
2135				{
2136						keys: [70], // F
2137						action: function(player, media) {
2138								if (typeof player.enterFullScreen != 'undefined') {
2139										if (player.isFullScreen) {
2140												player.exitFullScreen();
2141										} else {
2142												player.enterFullScreen();
2143										}
2144								}
2145						}
2146				},
2147				{
2148						keys: [77], // M
2149						action: function(player, media) {
2150								player.container.find('.mejs-volume-slider').css('display','block');
2151								if (player.isVideo) {
2152										player.showControls();
2153										player.startControlsTimer();
2154								}
2155								if (player.media.muted) {
2156										player.setMuted(false);
2157								} else {
2158										player.setMuted(true);
2159								}
2160						}
2161				}
2162		]
2163	};
2164
2165	mejs.mepIndex = 0;
2166
2167	mejs.players = {};
2168
2169	// wraps a MediaElement object in player controls
2170	mejs.MediaElementPlayer = function(node, o) {
2171		// enforce object, even without "new" (via John Resig)
2172		if ( !(this instanceof mejs.MediaElementPlayer) ) {
2173			return new mejs.MediaElementPlayer(node, o);
2174		}
2175
2176		var t = this;
2177
2178		// these will be reset after the MediaElement.success fires
2179		t.$media = t.$node = $(node);
2180		t.node = t.media = t.$media[0];
2181
2182		// check for existing player
2183		if (typeof t.node.player != 'undefined') {
2184			return t.node.player;
2185		} else {
2186			// attach player to DOM node for reference
2187			t.node.player = t;
2188		}
2189
2190
2191		// try to get options from data-mejsoptions
2192		if (typeof o == 'undefined') {
2193			o = t.$node.data('mejsoptions');
2194		}
2195
2196		// extend default options
2197		t.options = $.extend({},mejs.MepDefaults,o);
2198
2199		// unique ID
2200		t.id = 'mep_' + mejs.mepIndex++;
2201
2202		// add to player array (for focus events)
2203		mejs.players[t.id] = t;
2204
2205		// start up
2206		t.init();
2207
2208		return t;
2209	};
2210
2211	// actual player
2212	mejs.MediaElementPlayer.prototype = {
2213
2214		hasFocus: false,
2215
2216		controlsAreVisible: true,
2217
2218		init: function() {
2219
2220			var
2221				t = this,
2222				mf = mejs.MediaFeatures,
2223				// options for MediaElement (shim)
2224				meOptions = $.extend(true, {}, t.options, {
2225					success: function(media, domNode) { t.meReady(media, domNode); },
2226					error: function(e) { t.handleError(e);}
2227				}),
2228				tagName = t.media.tagName.toLowerCase();
2229
2230			t.isDynamic = (tagName !== 'audio' && tagName !== 'video');
2231
2232			if (t.isDynamic) {
2233				// get video from src or href?
2234				t.isVideo = t.options.isVideo;
2235			} else {
2236				t.isVideo = (tagName !== 'audio' && t.options.isVideo);
2237			}
2238
2239			// use native controls in iPad, iPhone, and Android
2240			if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) {
2241
2242				// add controls and stop
2243				t.$media.attr('controls', 'controls');
2244
2245				// attempt to fix iOS 3 bug
2246				//t.$media.removeAttr('poster');
2247                                // no Issue found on iOS3 -ttroxell
2248
2249				// override Apple's autoplay override for iPads
2250				if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
2251					t.play();
2252				}
2253
2254			} else if (mf.isAndroid && t.options.AndroidUseNativeControls) {
2255
2256				// leave default player
2257
2258			} else {
2259
2260				// DESKTOP: use MediaElementPlayer controls
2261
2262				// remove native controls
2263				t.$media.removeAttr('controls');
2264
2265				// build container
2266				t.container =
2267					$('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+
2268						'<div class="mejs-inner">'+
2269							'<div class="mejs-mediaelement"></div>'+
2270							'<div class="mejs-layers"></div>'+
2271							'<div class="mejs-controls"></div>'+
2272							'<div class="mejs-clear"></div>'+
2273						'</div>' +
2274					'</div>')
2275					.addClass(t.$media[0].className)
2276					.insertBefore(t.$media);
2277
2278				// add classes for user and content
2279				t.container.addClass(
2280					(mf.isAndroid ? 'mejs-android ' : '') +
2281					(mf.isiOS ? 'mejs-ios ' : '') +
2282					(mf.isiPad ? 'mejs-ipad ' : '') +
2283					(mf.isiPhone ? 'mejs-iphone ' : '') +
2284					(t.isVideo ? 'mejs-video ' : 'mejs-audio ')
2285				);
2286
2287
2288				// move the <video/video> tag into the right spot
2289				if (mf.isiOS) {
2290
2291					// sadly, you can't move nodes in iOS, so we have to destroy and recreate it!
2292					var $newMedia = t.$media.clone();
2293
2294					t.container.find('.mejs-mediaelement').append($newMedia);
2295
2296					t.$media.remove();
2297					t.$node = t.$media = $newMedia;
2298					t.node = t.media = $newMedia[0]
2299
2300				} else {
2301
2302					// normal way of moving it into place (doesn't work on iOS)
2303					t.container.find('.mejs-mediaelement').append(t.$media);
2304				}
2305
2306				// find parts
2307				t.controls = t.container.find('.mejs-controls');
2308				t.layers = t.container.find('.mejs-layers');
2309
2310				// determine the size
2311
2312				/* size priority:
2313					(1) videoWidth (forced),
2314					(2) style="width;height;"
2315					(3) width attribute,
2316					(4) defaultVideoWidth (for unspecified cases)
2317				*/
2318
2319				var tagType = (t.isVideo ? 'video' : 'audio'),
2320					capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1);
2321
2322
2323
2324				if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) {
2325					t.width = t.options[tagType + 'Width'];
2326				} else if (t.media.style.width !== '' && t.media.style.width !== null) {
2327					t.width = t.media.style.width;
2328				} else if (t.media.getAttribute('width') !== null) {
2329					t.width = t.$media.attr('width');
2330				} else {
2331					t.width = t.options['default' + capsTagName + 'Width'];
2332				}
2333
2334				if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) {
2335					t.height = t.options[tagType + 'Height'];
2336				} else if (t.media.style.height !== '' && t.media.style.height !== null) {
2337					t.height = t.media.style.height;
2338				} else if (t.$media[0].getAttribute('height') !== null) {
2339					t.height = t.$media.attr('height');
2340				} else {
2341					t.height = t.options['default' + capsTagName + 'Height'];
2342				}
2343
2344				// set the size, while we wait for the plugins to load below
2345				t.setPlayerSize(t.width, t.height);
2346
2347				// create MediaElementShim
2348				meOptions.pluginWidth = t.width;
2349				meOptions.pluginHeight = t.height;
2350			}
2351
2352			// create MediaElement shim
2353			mejs.MediaElement(t.$media[0], meOptions);
2354
2355			if (typeof(t.container) != 'undefined' && t.controlsAreVisible){
2356			    // controls are shown when loaded
2357			    t.container.trigger('controlsshown');
2358			}
2359		},
2360
2361		showControls: function(doAnimation) {
2362			var t = this;
2363
2364			doAnimation = typeof doAnimation == 'undefined' || doAnimation;
2365
2366			if (t.controlsAreVisible)
2367				return;
2368
2369			if (doAnimation) {
2370				t.controls
2371					.css('visibility','visible')
2372					.stop(true, true).fadeIn(200, function() {
2373					      t.controlsAreVisible = true;
2374					      t.container.trigger('controlsshown');
2375					});
2376
2377				// any additional controls people might add and want to hide
2378				t.container.find('.mejs-control')
2379					.css('visibility','visible')
2380					.stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;});
2381
2382			} else {
2383				t.controls
2384					.css('visibility','visible')
2385					.css('display','block');
2386
2387				// any additional controls people might add and want to hide
2388				t.container.find('.mejs-control')
2389					.css('visibility','visible')
2390					.css('display','block');
2391
2392				t.controlsAreVisible = true;
2393				t.container.trigger('controlsshown');
2394			}
2395
2396			t.setControlsSize();
2397
2398		},
2399
2400		hideControls: function(doAnimation) {
2401			var t = this;
2402
2403			doAnimation = typeof doAnimation == 'undefined' || doAnimation;
2404
2405			if (!t.controlsAreVisible || t.options.alwaysShowControls)
2406				return;
2407
2408			if (doAnimation) {
2409				// fade out main controls
2410				t.controls.stop(true, true).fadeOut(200, function() {
2411					$(this)
2412						.css('visibility','hidden')
2413						.css('display','block');
2414
2415					t.controlsAreVisible = false;
2416					t.container.trigger('controlshidden');
2417				});
2418
2419				// any additional controls people might add and want to hide
2420				t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() {
2421					$(this)
2422						.css('visibility','hidden')
2423						.css('display','block');
2424				});
2425			} else {
2426
2427				// hide main controls
2428				t.controls
2429					.css('visibility','hidden')
2430					.css('display','block');
2431
2432				// hide others
2433				t.container.find('.mejs-control')
2434					.css('visibility','hidden')
2435					.css('display','block');
2436
2437				t.controlsAreVisible = false;
2438				t.container.trigger('controlshidden');
2439			}
2440		},
2441
2442		controlsTimer: null,
2443
2444		startControlsTimer: function(timeout) {
2445
2446			var t = this;
2447
2448			timeout = typeof timeout != 'undefined' ? timeout : 1500;
2449
2450			t.killControlsTimer('start');
2451
2452			t.controlsTimer = setTimeout(function() {
2453				//
2454				t.hideControls();
2455				t.killControlsTimer('hide');
2456			}, timeout);
2457		},
2458
2459		killControlsTimer: function(src) {
2460
2461			var t = this;
2462
2463			if (t.controlsTimer !== null) {
2464				clearTimeout(t.controlsTimer);
2465				delete t.controlsTimer;
2466				t.controlsTimer = null;
2467			}
2468		},
2469
2470		controlsEnabled: true,
2471
2472		disableControls: function() {
2473			var t= this;
2474
2475			t.killControlsTimer();
2476			t.hideControls(false);
2477			this.controlsEnabled = false;
2478		},
2479
2480		enableControls: function() {
2481			var t= this;
2482
2483			t.showControls(false);
2484
2485			t.controlsEnabled = true;
2486		},
2487
2488
2489		// Sets up all controls and events
2490		meReady: function(media, domNode) {
2491
2492
2493			var t = this,
2494				mf = mejs.MediaFeatures,
2495				autoplayAttr = domNode.getAttribute('autoplay'),
2496				autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'),
2497				featureIndex,
2498				feature;
2499
2500			// make sure it can't create itself again if a plugin reloads
2501			if (t.created) {
2502				return;
2503			} else {
2504				t.created = true;
2505			}
2506
2507			t.media = media;
2508			t.domNode = domNode;
2509
2510			if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) {
2511
2512				// two built in features
2513				t.buildposter(t, t.controls, t.layers, t.media);
2514				t.buildkeyboard(t, t.controls, t.layers, t.media);
2515				t.buildoverlays(t, t.controls, t.layers, t.media);
2516
2517				// grab for use by features
2518				t.findTracks();
2519
2520				// add user-defined features/controls
2521				for (featureIndex in t.options.features) {
2522					feature = t.options.features[featureIndex];
2523					if (t['build' + feature]) {
2524						try {
2525							t['build' + feature](t, t.controls, t.layers, t.media);
2526						} catch (e) {
2527							// TODO: report control error
2528							//throw e;
2529							
2530							
2531						}
2532					}
2533				}
2534
2535				t.container.trigger('controlsready');
2536
2537				// reset all layers and controls
2538				t.setPlayerSize(t.width, t.height);
2539				t.setControlsSize();
2540
2541
2542				// controls fade
2543				if (t.isVideo) {
2544
2545					if (mejs.MediaFeatures.hasTouch) {
2546
2547						// for touch devices (iOS, Android)
2548						// show/hide without animation on touch
2549
2550						t.$media.bind('touchstart', function() {
2551
2552
2553							// toggle controls
2554							if (t.controlsAreVisible) {
2555								t.hideControls(false);
2556							} else {
2557								if (t.controlsEnabled) {
2558									t.showControls(false);
2559								}
2560							}
2561						});
2562
2563					} else {
2564
2565						// create callback here since it needs access to current
2566						// MediaElement object
2567						t.clickToPlayPauseCallback = function() {
2568							//
2569
2570							if (t.options.clickToPlayPause) {
2571								if (t.media.paused) {
2572									t.play();
2573								} else {
2574									t.pause();
2575								}
2576							}
2577						};
2578
2579			            // click to play/pause
2580			            t.media.addEventListener('click', t.clickToPlayPauseCallback, false);
2581
2582						// show/hide controls
2583						t.container
2584							.bind('mouseenter mouseover', function () {
2585								if (t.controlsEnabled) {
2586									if (!t.options.alwaysShowControls ) {
2587										t.killControlsTimer('enter');
2588										t.showControls();
2589										t.startControlsTimer(2500);
2590									}
2591								}
2592							})
2593							.bind('mousemove', function() {
2594								if (t.controlsEnabled) {
2595									if (!t.controlsAreVisible) {
2596										t.showControls();
2597									}
2598									if (!t.options.alwaysShowControls) {
2599										t.startControlsTimer(2500);
2600									}
2601								}
2602							})
2603							.bind('mouseleave', function () {
2604								if (t.controlsEnabled) {
2605									if (!t.media.paused && !t.options.alwaysShowControls) {
2606										t.startControlsTimer(1000);
2607									}
2608								}
2609							});
2610					}
2611
2612					if(t.options.hideVideoControlsOnLoad) {
2613						t.hideControls(false);
2614					}
2615
2616					// check for autoplay
2617					if (autoplay && !t.options.alwaysShowControls) {
2618						t.hideControls();
2619					}
2620
2621					// resizer
2622					if (t.options.enableAutosize) {
2623						t.media.addEventListener('loadedmetadata', function(e) {
2624							// if the <video height> was not set and the options.videoHeight was not set
2625							// then resize to the real dimensions
2626							if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
2627								t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
2628								t.setControlsSize();
2629								t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
2630							}
2631						}, false);
2632					}
2633				}
2634
2635				// EVENTS
2636
2637				// FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them)
2638				media.addEventListener('play', function() {
2639					var playerIndex;
2640
2641					// go through all other players
2642					for (playerIndex in mejs.players) {
2643						var p = mejs.players[playerIndex];
2644						if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) {
2645							p.pause();
2646						}
2647						p.hasFocus = false;
2648					}
2649
2650					t.hasFocus = true;
2651				},false);
2652
2653
2654				// ended for all
2655				t.media.addEventListener('ended', function (e) {
2656					if(t.options.autoRewind) {
2657						try{
2658							t.media.setCurrentTime(0);
2659						} catch (exp) {
2660
2661						}
2662					}
2663					t.media.pause();
2664
2665					if (t.setProgressRail) {
2666						t.setProgressRail();
2667					}
2668					if (t.setCurrentRail) {
2669						t.setCurrentRail();
2670					}
2671
2672					if (t.options.loop) {
2673						t.play();
2674					} else if (!t.options.alwaysShowControls && t.controlsEnabled) {
2675						t.showControls();
2676					}
2677				}, false);
2678
2679				// resize on the first play
2680				t.media.addEventListener('loadedmetadata', function(e) {
2681					if (t.updateDuration) {
2682						t.updateDuration();
2683					}
2684					if (t.updateCurrent) {
2685						t.updateCurrent();
2686					}
2687
2688					if (!t.isFullScreen) {
2689						t.setPlayerSize(t.width, t.height);
2690						t.setControlsSize();
2691					}
2692				}, false);
2693
2694
2695				// webkit has trouble doing this without a delay
2696				setTimeout(function () {
2697					t.setPlayerSize(t.width, t.height);
2698					t.setControlsSize();
2699				}, 50);
2700
2701				// adjust controls whenever window sizes (used to be in fullscreen only)
2702				t.globalBind('resize', function() {
2703
2704					// don't resize for fullscreen mode
2705					if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) {
2706						t.setPlayerSize(t.width, t.height);
2707					}
2708
2709					// always adjust controls
2710					t.setControlsSize();
2711				});
2712
2713				// TEMP: needs to be moved somewhere else
2714				if (t.media.pluginType == 'youtube' && t.options.autoplay) {
2715				//LOK-Soft: added t.options.autoplay to if -- I can only guess this is for hiding play button when autoplaying youtube, general hiding play button layer causes missing button on player load
2716					t.container.find('.mejs-overlay-play').hide();
2717				}
2718			}
2719
2720			// force autoplay for HTML5
2721			if (autoplay && media.pluginType == 'native') {
2722				t.play();
2723			}
2724
2725
2726			if (t.options.success) {
2727
2728				if (typeof t.options.success == 'string') {
2729					window[t.options.success](t.media, t.domNode, t);
2730				} else {
2731					t.options.success(t.media, t.domNode, t);
2732				}
2733			}
2734		},
2735
2736		handleError: function(e) {
2737			var t = this;
2738
2739			t.controls.hide();
2740
2741			// Tell user that the file cannot be played
2742			if (t.options.error) {
2743				t.options.error(e);
2744			}
2745		},
2746
2747		setPlayerSize: function(width,height) {
2748			var t = this;
2749
2750			if( !t.options.setDimensions ) {
2751				return false;
2752			}
2753
2754			if (typeof width != 'undefined') {
2755				t.width = width;
2756			}
2757
2758			if (typeof height != 'undefined') {
2759				t.height = height;
2760			}
2761
2762			// detect 100% mode - use currentStyle for IE since css() doesn't return percentages
2763			if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '90%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) {
2764
2765				// do we have the native dimensions yet?
2766				var nativeWidth = (function() {
2767					if (t.isVideo) {
2768						if (t.media.videoWidth && t.media.videoWidth > 0) {
2769							return t.media.videoWidth;
2770						} else if (t.media.getAttribute('width') !== null) {
2771							return t.media.getAttribute('width');
2772						} else {
2773							return t.options.defaultVideoWidth;
2774						}
2775					} else {
2776						return t.options.defaultAudioWidth;
2777					}
2778				})();
2779
2780				var nativeHeight = (function() {
2781					if (t.isVideo) {
2782						if (t.media.videoHeight && t.media.videoHeight > 0) {
2783							return t.media.videoHeight;
2784						} else if (t.media.getAttribute('height') !== null) {
2785							return t.media.getAttribute('height');
2786						} else {
2787							return t.options.defaultVideoHeight;
2788						}
2789					} else {
2790						return t.options.defaultAudioHeight;
2791					}
2792				})();
2793
2794				var
2795					parentWidth = t.container.parent().closest(':visible').width(),
2796					parentHeight = t.container.parent().closest(':visible').height(),
2797					newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight;
2798
2799				// When we use percent, the newHeight can't be calculated so we get the container height
2800				if(isNaN(newHeight) || ( parentHeight != 0 && newHeight > parentHeight )) {
2801					newHeight = parentHeight;
2802				}
2803
2804				if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
2805					parentWidth = $(window).width();
2806					newHeight = $(window).height();
2807				}
2808
2809				if ( newHeight != 0 && parentWidth != 0 ) {
2810					// set outer container size
2811					t.container
2812						.width(parentWidth)
2813						.height(newHeight);
2814
2815					// set native <video> or <audio> and shims
2816					t.$media.add(t.container.find('.mejs-shim'))
2817						.width('100%')
2818						.height('100%');
2819
2820					// if shim is ready, send the size to the embeded plugin
2821					if (t.isVideo) {
2822						if (t.media.setVideoSize) {
2823							t.media.setVideoSize(parentWidth, newHeight);
2824						}
2825					}
2826
2827					// set the layers
2828					t.layers.children('.mejs-layer')
2829						.width('100%')
2830						.height('100%');
2831				}
2832
2833
2834			} else {
2835
2836				t.container
2837					.width(t.width)
2838					.height(t.height);
2839
2840				t.layers.children('.mejs-layer')
2841					.width(t.width)
2842					.height(t.height);
2843
2844			}
2845
2846			// special case for big play button so it doesn't go over the controls area
2847			var playLayer = t.layers.find('.mejs-overlay-play'),
2848				playButton = playLayer.find('.mejs-overlay-button');
2849
2850			playLayer.height(t.container.height() - t.controls.height());
2851			playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px'  );
2852
2853		},
2854
2855		setControlsSize: function() {
2856			var t = this,
2857				usedWidth = 0,
2858				railWidth = 0,
2859				rail = t.controls.find('.mejs-time-rail'),
2860				total = t.controls.find('.mejs-time-total'),
2861				current = t.controls.find('.mejs-time-current'),
2862				loaded = t.controls.find('.mejs-time-loaded'),
2863				others = rail.siblings(),
2864				lastControl = others.last(),
2865				lastControlPosition = null;
2866
2867			// skip calculation if hidden
2868			if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) {
2869				return;
2870			}
2871
2872
2873			// allow the size to come from custom CSS
2874			if (t.options && !t.options.autosizeProgress) {
2875				// Also, frontends devs can be more flexible
2876				// due the opportunity of absolute positioning.
2877				railWidth = parseInt(rail.css('width'));
2878			}
2879
2880			// attempt to autosize
2881			if (railWidth === 0 || !railWidth) {
2882
2883				// find the size of all the other controls besides the rail
2884				others.each(function() {
2885					var $this = $(this);
2886					if ($this.css('position') != 'absolute' && $this.is(':visible')) {
2887						usedWidth += $(this).outerWidth(true);
2888					}
2889				});
2890
2891				// fit the rail into the remaining space
2892				railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width());
2893			}
2894
2895			// resize the rail,
2896			// but then check if the last control (say, the fullscreen button) got pushed down
2897			// this often happens when zoomed
2898			do {
2899				// outer area
2900				rail.width(railWidth);
2901				// dark space
2902				total.width(railWidth - (total.outerWidth(true) - total.width()));
2903
2904				if (lastControl.css('position') != 'absolute') {
2905					lastControlPosition = lastControl.position();
2906					railWidth--;
2907				}
2908			} while (lastControlPosition != null && lastControlPosition.top > 0 && railWidth > 0);
2909
2910			if (t.setProgressRail)
2911				t.setProgressRail();
2912			if (t.setCurrentRail)
2913				t.setCurrentRail();
2914		},
2915
2916
2917		buildposter: function(player, controls, layers, media) {
2918			var t = this,
2919				poster =
2920				$('<div class="mejs-poster mejs-layer">' +
2921				'</div>')
2922					.appendTo(layers),
2923				posterUrl = player.$media.attr('poster');
2924
2925			// prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
2926			if (player.options.poster !== '') {
2927				posterUrl = player.options.poster;
2928			}
2929
2930			// second, try the real poster
2931			if (posterUrl !== '' && posterUrl != null) {
2932				t.setPoster(posterUrl);
2933			} else {
2934				poster.hide();
2935			}
2936
2937			media.addEventListener('play',function() {
2938				poster.hide();
2939			}, false);
2940
2941			if(player.options.showPosterWhenEnded && player.options.autoRewind){
2942				media.addEventListener('ended',function() {
2943					poster.show();
2944				}, false);
2945			}
2946		},
2947
2948		setPoster: function(url) {
2949			var t = this,
2950				posterDiv = t.container.find('.mejs-poster'),
2951				posterImg = posterDiv.find('img');
2952
2953			if (posterImg.length == 0) {
2954				posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv);
2955			}
2956
2957			posterImg.attr('src', url);
2958			posterDiv.css({'background-image' : 'url(' + url + ')'});
2959		},
2960
2961		buildoverlays: function(player, controls, layers, media) {
2962            var t = this;
2963			if (!player.isVideo)
2964				return;
2965
2966			var
2967			loading =
2968				$('<div class="mejs-overlay mejs-layer">'+
2969					'<div class="mejs-overlay-loading"><span></span></div>'+
2970				'</div>')
2971				.hide() // start out hidden
2972				.appendTo(layers),
2973			error =
2974				$('<div class="mejs-overlay mejs-layer">'+
2975					'<div class="mejs-overlay-error"></div>'+
2976				'</div>')
2977				.hide() // start out hidden
2978				.appendTo(layers),
2979			// this needs to come last so it's on top
2980			bigPlay =
2981				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
2982					'<div class="mejs-overlay-button"></div>'+
2983				'</div>')
2984				.appendTo(layers)
2985				.bind('click', function() {  // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video
2986					if (t.options.clickToPlayPause) {
2987						if (media.paused) {
2988							media.play();
2989						}
2990					}
2991				});
2992
2993			/*
2994			if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) {
2995				bigPlay.remove();
2996				loading.remove();
2997			}
2998			*/
2999
3000
3001			// show/hide big play button
3002			media.addEventListener('play',function() {
3003				bigPlay.hide();
3004				loading.hide();
3005				controls.find('.mejs-time-buffering').hide();
3006				error.hide();
3007			}, false);
3008
3009			media.addEventListener('playing', function() {
3010				bigPlay.hide();
3011				loading.hide();
3012				controls.find('.mejs-time-buffering').hide();
3013				error.hide();
3014			}, false);
3015
3016			media.addEventListener('seeking', function() {
3017				loading.show();
3018				controls.find('.mejs-time-buffering').show();
3019			}, false);
3020
3021			media.addEventListener('seeked', function() {
3022				loading.hide();
3023				controls.find('.mejs-time-buffering').hide();
3024			}, false);
3025
3026			media.addEventListener('pause',function() {
3027				if (!mejs.MediaFeatures.isiPhone) {
3028					bigPlay.show();
3029				}
3030			}, false);
3031
3032			media.addEventListener('waiting', function() {
3033				loading.show();
3034				controls.find('.mejs-time-buffering').show();
3035			}, false);
3036
3037
3038			// show/hide loading
3039			media.addEventListener('loadeddata',function() {
3040				// for some reason Chrome is firing this event
3041				//if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
3042				//	return;
3043
3044				loading.show();
3045				controls.find('.mejs-time-buffering').show();
3046			}, false);
3047			media.addEventListener('canplay',function() {
3048				loading.hide();
3049				controls.find('.mejs-time-buffering').hide();
3050			}, false);
3051
3052			// error handling
3053			media.addEventListener('error',function() {
3054				loading.hide();
3055				controls.find('.mejs-time-buffering').hide();
3056				error.show();
3057				error.find('mejs-overlay-error').html("Error loading this resource");
3058			}, false);
3059
3060			media.addEventListener('keydown', function(e) {
3061				t.onkeydown(player, media, e);
3062			}, false);
3063		},
3064
3065		buildkeyboard: function(player, controls, layers, media) {
3066
3067				var t = this;
3068
3069				// listen for key presses
3070				t.globalBind('keydown', function(e) {
3071					return t.onkeydown(player, media, e);
3072				});
3073
3074				// check if someone clicked outside a player region, then kill its focus
3075				t.globalBind('click', function(event) {
3076					player.hasFocus = $(event.target).closest('.mejs-container').length != 0;
3077				});
3078
3079		},
3080		onkeydown: function(player, media, e) {
3081			if (player.hasFocus && player.options.enableKeyboard) {
3082				// find a matching key
3083				for (var i = 0, il = player.options.keyActions.length; i < il; i++) {
3084					var keyAction = player.options.keyActions[i];
3085
3086					for (var j = 0, jl = keyAction.keys.length; j < jl; j++) {
3087						if (e.keyCode == keyAction.keys[j]) {
3088							if (typeof(e.preventDefault) == "function") e.preventDefault();
3089							keyAction.action(player, media, e.keyCode);
3090							return false;
3091						}
3092					}
3093				}
3094			}
3095
3096			return true;
3097		},
3098
3099		findTracks: function() {
3100			var t = this,
3101				tracktags = t.$media.find('track');
3102
3103			// store for use by plugins
3104			t.tracks = [];
3105			tracktags.each(function(index, track) {
3106
3107				track = $(track);
3108
3109				t.tracks.push({
3110					srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '',
3111					src: track.attr('src'),
3112					kind: track.attr('kind'),
3113					label: track.attr('label') || '',
3114					entries: [],
3115					isLoaded: false
3116				});
3117			});
3118		},
3119		changeSkin: function(className) {
3120			this.container[0].className = 'mejs-container ' + className;
3121			this.setPlayerSize(this.width, this.height);
3122			this.setControlsSize();
3123		},
3124		play: function() {
3125			this.load();
3126			this.media.play();
3127		},
3128		pause: function() {
3129			try {
3130				this.media.pause();
3131			} catch (e) {}
3132		},
3133		load: function() {
3134			if (!this.isLoaded) {
3135				this.media.load();
3136			}
3137
3138			this.isLoaded = true;
3139		},
3140		setMuted: function(muted) {
3141			this.media.setMuted(muted);
3142		},
3143		setCurrentTime: function(time) {
3144			this.media.setCurrentTime(time);
3145		},
3146		getCurrentTime: function() {
3147			return this.media.currentTime;
3148		},
3149		setVolume: function(volume) {
3150			this.media.setVolume(volume);
3151		},
3152		getVolume: function() {
3153			return this.media.volume;
3154		},
3155		setSrc: function(src) {
3156			this.media.setSrc(src);
3157		},
3158		remove: function() {
3159			var t = this, featureIndex, feature;
3160
3161			// invoke features cleanup
3162			for (featureIndex in t.options.features) {
3163				feature = t.options.features[featureIndex];
3164				if (t['clean' + feature]) {
3165					try {
3166						t['clean' + feature](t);
3167					} catch (e) {
3168						// TODO: report control error
3169						//throw e;
3170						//
3171						//
3172					}
3173				}
3174			}
3175
3176			// grab video and put it back in place
3177			if (!t.isDynamic) {
3178				t.$media.prop('controls', true);
3179				// detach events from the video
3180				// TODO: detach event listeners better than this;
3181				//       also detach ONLY the events attached by this plugin!
3182				t.$node.clone().insertBefore(t.container).show();
3183				t.$node.remove();
3184			} else {
3185				t.$node.insertBefore(t.container);
3186			}
3187
3188			if (t.media.pluginType !== 'native') {
3189				t.media.remove();
3190			}
3191
3192			// Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api.
3193			delete mejs.players[t.id];
3194
3195			if (typeof t.container == 'object') {
3196				t.container.remove();
3197			}
3198			t.globalUnbind();
3199			delete t.node.player;
3200		}
3201	};
3202
3203	(function(){
3204		var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;
3205
3206		function splitEvents(events, id) {
3207			// add player ID as an event namespace so it's easier to unbind them all later
3208			var ret = {d: [], w: []};
3209			$.each((events || '').split(' '), function(k, v){
3210				var eventname = v + '.' + id;
3211				if (eventname.indexOf('.') === 0) {
3212					ret.d.push(eventname);
3213					ret.w.push(eventname);
3214				}
3215				else {
3216					ret[rwindow.test(v) ? 'w' : 'd'].push(eventname);
3217				}
3218			});
3219			ret.d = ret.d.join(' ');
3220			ret.w = ret.w.join(' ');
3221			return ret;
3222		}
3223
3224		mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) {
3225			var t = this;
3226			events = splitEvents(events, t.id);
3227			if (events.d) $(document).bind(events.d, data, callback);
3228			if (events.w) $(window).bind(events.w, data, callback);
3229		};
3230
3231		mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) {
3232			var t = this;
3233			events = splitEvents(events, t.id);
3234			if (events.d) $(document).unbind(events.d, callback);
3235			if (events.w) $(window).unbind(events.w, callback);
3236		};
3237	})();
3238
3239	// turn into jQuery plugin
3240	if (typeof $ != 'undefined') {
3241		$.fn.mediaelementplayer = function (options) {
3242			if (options === false) {
3243				this.each(function () {
3244					var player = $(this).data('mediaelementplayer');
3245					if (player) {
3246						player.remove();
3247					}
3248					$(this).removeData('mediaelementplayer');
3249				});
3250			}
3251			else {
3252				this.each(function () {
3253					$(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options));
3254				});
3255			}
3256			return this;
3257		};
3258
3259
3260		$(document).ready(function() {
3261			// auto enable using JSON attribute
3262			$('.mejs-player').mediaelementplayer();
3263		});
3264	}
3265
3266	// push out to window
3267	window.MediaElementPlayer = mejs.MediaElementPlayer;
3268
3269})(mejs.$);
3270
3271(function($) {
3272
3273	$.extend(mejs.MepDefaults, {
3274		playpauseText: mejs.i18n.t('Play/Pause')
3275	});
3276
3277	// PLAY/pause BUTTON
3278	$.extend(MediaElementPlayer.prototype, {
3279		buildplaypause: function(player, controls, layers, media) {
3280			var 
3281				t = this,
3282				play = 
3283				$('<div class="mejs-button mejs-playpause-button mejs-play" >' +
3284					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '" aria-label="' + t.options.playpauseText + '"></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
3299			media.addEventListener('play',function() {
3300				play.removeClass('mejs-play').addClass('mejs-pause');
3301			}, false);
3302			media.addEventListener('playing',function() {
3303				play.removeClass('mejs-play').addClass('mejs-pause');
3304			}, false);
3305
3306
3307			media.addEventListener('pause',function() {
3308				play.removeClass('mejs-pause').addClass('mejs-play');
3309			}, false);
3310			media.addEventListener('paused',function() {
3311				play.removeClass('mejs-pause').addClass('mejs-play');
3312			}, false);
3313		}
3314	});
3315	
3316})(mejs.$);
3317
3318(function($) {
3319
3320	$.extend(mejs.MepDefaults, {
3321		stopText: 'Stop'
3322	});
3323
3324	// STOP BUTTON
3325	$.extend(MediaElementPlayer.prototype, {
3326		buildstop: function(player, controls, layers, media) {
3327			var t = this,
3328				stop = 
3329				$('<div class="mejs-button mejs-stop-button mejs-stop">' +
3330					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' +
3331				'</div>')
3332				.appendTo(controls)
3333				.click(function() {
3334					if (!media.paused) {
3335						media.pause();
3336					}
3337					if (media.currentTime > 0) {
3338						media.setCurrentTime(0);
3339                        media.pause();
3340						controls.find('.mejs-time-current').width('0px');
3341						controls.find('.mejs-time-handle').css('left', '0px');
3342						controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) );
3343						controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) );					
3344						layers.find('.mejs-poster').show();
3345					}
3346				});
3347		}
3348	});
3349	
3350})(mejs.$);
3351
3352(function($) {
3353	// progress/loaded bar
3354	$.extend(MediaElementPlayer.prototype, {
3355		buildprogress: function(player, controls, layers, media) {
3356
3357			$('<div class="mejs-time-rail">'+
3358				'<span class="mejs-time-total">'+
3359					'<span class="mejs-time-buffering"></span>'+
3360					'<span class="mejs-time-loaded"></span>'+
3361					'<span class="mejs-time-current"></span>'+
3362					'<span class="mejs-time-handle"></span>'+
3363					'<span class="mejs-time-float">' + 
3364						'<span class="mejs-time-float-current">00:00</span>' + 
3365						'<span class="mejs-time-float-corner"></span>' + 
3366					'</span>'+
3367				'</span>'+
3368			'</div>')
3369				.appendTo(controls);
3370				controls.find('.mejs-time-buffering').hide();
3371
3372			var 
3373				t = this,
3374				total = controls.find('.mejs-time-total'),
3375				loaded  = controls.find('.mejs-time-loaded'),
3376				current  = controls.find('.mejs-time-current'),
3377				handle  = controls.find('.mejs-time-handle'),
3378				timefloat  = controls.find('.mejs-time-float'),
3379				timefloatcurrent  = controls.find('.mejs-time-float-current'),
3380				handleMouseMove = function (e) {
3381					// mouse or touch position relative to the object
3382					if (e.originalEvent.changedTouches) {
3383						var x = e.originalEvent.changedTouches[0].pageX;
3384					}else{
3385						var x = e.pageX;
3386					}
3387					
3388					var offset = total.offset(),
3389						width = total.outerWidth(true),
3390						percentage = 0,
3391						newTime = 0,
3392						pos = 0;
3393
3394
3395					if (media.duration) {
3396						if (x < offset.left) {
3397							x = offset.left;
3398						} else if (x > width + offset.left) {
3399							x = width + offset.left;
3400						}
3401						
3402						pos = x - offset.left;
3403						percentage = (pos / width);
3404						newTime = (percentage <= 0.02) ? 0 : percentage * media.duration;
3405
3406						// seek to where the mouse is
3407						if (mouseIsDown && newTime !== media.currentTime) {
3408							media.setCurrentTime(newTime);
3409						}
3410
3411						// position floating time box
3412						if (!mejs.MediaFeatures.hasTouch) {
3413								timefloat.css('left', pos);
3414								timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) );
3415								timefloat.show();
3416						}
3417					}
3418				},
3419				mouseIsDown = false,
3420				mouseIsOver = false;
3421
3422			// handle clicks
3423			//controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove);
3424			total
3425				.bind('mousedown touchstart', function (e) {
3426					// only handle left clicks or touch
3427					if (e.which === 1 || e.which === 0) {
3428						mouseIsDown = true;
3429						handleMouseMove(e);
3430						t.globalBind('mousemove.dur touchmove.dur', function(e) {
3431							handleMouseMove(e);
3432						});
3433						t.globalBind('mouseup.dur touchend.dur', function (e) {
3434							mouseIsDown = false;
3435							timefloat.hide();
3436							t.globalUnbind('.dur');
3437						});
3438						return false;
3439					}
3440				})
3441				.bind('mouseenter', function(e) {
3442					mouseIsOver = true;
3443					t.globalBind('mousemove.dur', function(e) {
3444						handleMouseMove(e);
3445					});
3446					if (!mejs.MediaFeatures.hasTouch) {
3447						timefloat.show();
3448					}
3449				})
3450				.bind('mouseleave',function(e) {
3451					mouseIsOver = false;
3452					if (!mouseIsDown) {
3453						t.globalUnbind('.dur');
3454						timefloat.hide();
3455					}
3456				});
3457
3458			// loading
3459			media.addEventListener('progress', function (e) {
3460				player.setProgressRail(e);
3461				player.setCurrentRail(e);
3462			}, false);
3463
3464			// current time
3465			media.addEventListener('timeupdate', function(e) {
3466				player.setProgressRail(e);
3467				player.setCurrentRail(e);
3468			}, false);
3469			
3470			
3471			// store for later use
3472			t.loaded = loaded;
3473			t.total = total;
3474			t.current = current;
3475			t.handle = handle;
3476		},
3477		setProgressRail: function(e) {
3478
3479			var
3480				t = this,
3481				target = (e != undefined) ? e.target : t.media,
3482				percent = null;			
3483
3484			// newest HTML5 spec has buffered array (FF4, Webkit)
3485			if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) {
3486				// TODO: account for a real array with multiple values (only Firefox 4 has this so far) 
3487				percent = target.buffered.end(0) / target.duration;
3488			} 
3489			// Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
3490			// to be anything other than 0. If the byte count is available we use this instead.
3491			// Browsers that support the else if do not seem to have the bufferedBytes value and
3492			// should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8.
3493			else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) {
3494				percent = target.bufferedBytes / target.bytesTotal;
3495			}
3496			// Firefox 3 with an Ogg file seems to go this way
3497			else if (e && e.lengthComputable && e.total != 0) {
3498				percent = e.loaded/e.total;
3499			}
3500
3501			// finally update the progress bar
3502			if (percent !== null) {
3503				percent = Math.min(1, Math.max(0, percent));
3504				// update loaded bar
3505				if (t.loaded && t.total) {
3506					t.loaded.width(t.total.width() * percent);
3507				}
3508			}
3509		},
3510		setCurrentRail: function() {
3511
3512			var t = this;
3513		
3514			if (t.media.currentTime != undefined && t.media.duration) {
3515
3516				// update bar and handle
3517				if (t.total && t.handle) {
3518					var 
3519						newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration),
3520						handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2);
3521
3522					t.current.width(newWidth);
3523					t.handle.css('left', handlePos);
3524				}
3525			}
3526
3527		}	
3528	});
3529})(mejs.$);
3530
3531(function($) {
3532	
3533	// options
3534	$.extend(mejs.MepDefaults, {
3535		duration: -1,
3536		timeAndDurationSeparator: '<span> | </span>'
3537	});
3538
3539		// Add download links to Light Player.
3540	$( document ).ready(function() {
3541    	$('audio').each(function( index ) {
3542 
3543        	var source = $(this).find('source').attr('src');
3544 
3545        	if ( source != '' ) {
3546        	    $(this).after('<div class="lightplayer-download-link-container"><a href="' + source + '" class="lightplayer-download-link-icon" download>' + '<span class="lightplayer-download-link"></span></a></div>');
3547        	}
3548 
3549    	});
3550	});
3551
3552	// current and duration 00:00 / 00:00
3553	$.extend(MediaElementPlayer.prototype, {
3554		buildcurrent: function(player, controls, layers, media) {
3555			var t = this;
3556			
3557			$('<div class="lightplayer-logo"><div class="lightplayer-logo-container"></div></div>'+'<div class="mejs-time">'+
3558					'<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '')
3559					+ (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+
3560					'</div>')
3561					.appendTo(controls);
3562			
3563			t.currenttime = t.controls.find('.mejs-currenttime');
3564
3565			media.addEventListener('timeupdate',function() {
3566				player.updateCurrent();
3567			}, false);
3568		},
3569
3570
3571		buildduration: function(player, controls, layers, media) {
3572			var t = this;
3573			
3574			if (controls.children().last().find('.mejs-currenttime').length > 0) {
3575				$(t.options.timeAndDurationSeparator +
3576					'<span class="mejs-duration">' + 
3577						(t.options.duration > 0 ? 
3578							mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration >
3578 3600, t.options.showTimecodeFrameCount,  t.options.framesPerSecond || 25) :
3579				   			((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 
3580				   		) + 
3581					'</span>')
3582					.appendTo(controls.find('.mejs-time'));
3583			} else {
3584
3585				// add class to current time
3586				controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container');
3587				
3588				$('<div class="mejs-time mejs-duration-container">'+
3589					'<span class="mejs-duration">' + 
3590						(t.options.duration > 0 ? 
3591							mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount,  t.options.framesPerSecond || 25) :
3592				   			((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 
3593				   		) + 
3594					'</span>' +
3595				'</div>')
3596				.appendTo(controls);
3597			}
3598			
3599			t.durationD = t.controls.find('.mejs-duration');
3600
3601			media.addEventListener('timeupdate',function() {
3602				player.updateDuration();
3603			}, false);
3604		},
3605		
3606		updateCurrent:  function() {
3607			var t = this;
3608
3609			if (t.currenttime) {
3610				t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount,  t.options.framesPerSecond || 25));
3611			}
3612		},
3613		
3614		updateDuration: function() {
3615			var t = this;
3616
3617			//Toggle the long video class if the video is longer than an hour.
3618			t.container.toggleClass("mejs-long-video", t.media.duration > 3600);
3619			
3620			if (t.durationD && (t.options.duration > 0 || t.media.duration)) {
3621				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));
3622			}		
3623		}
3624	});
3625
3626})(mejs.$);
3627
3628(function($) {
3629
3630	$.extend(mejs.MepDefaults, {
3631		muteText: mejs.i18n.t('Mute Toggle'),
3632		hideVolumeOnTouchDevices: true,
3633		
3634		audioVolume: 'horizontal',
3635		videoVolume: 'vertical'
3636	});
3637
3638	$.extend(MediaElementPlayer.prototype, {
3639		buildvolume: function(player, controls, layers, media) {
3640				
3641			// Android and iOS don't support volume controls
3642			if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices)
3643				return;
3644			
3645			var t = this,
3646				mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume,
3647				mute = (mode == 'horizontal') ?
3648				
3649				// horizontal version
3650				$('<div class="mejs-button mejs-volume-button mejs-mute">'+
3651					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+
3652				'</div>' +
3653				'<div class="mejs-horizontal-volume-slider">'+ // outer background
3654					'<div class="lightplayer-time-total"><div class="mejs-horizontal-volume-total"></div>'+ // line background
3655					'<div class="mejs-horizontal-volume-current"></div>'+ // current volume
3656					'<div class="mejs-horizontal-volume-handle"></div></div>'+ // handle
3657				'</div>'
3658				)
3659					.appendTo(controls) :
3660				
3661				// vertical version
3662				$('<div class="mejs-button mejs-volume-button mejs-mute">'+
3663					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+
3664					'<div class="mejs-volume-slider">'+ // outer background
3665						'<div class="mejs-volume-total"></div>'+ // line background
3666						'<div class="mejs-volume-current"></div>'+ // current volume
3667						'<div class="mejs-volume-handle"></div>'+ // handle
3668					'</div>'+
3669				'</div>')
3670					.appendTo(controls),
3671			volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'),
3672			volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'),
3673			volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'),
3674			volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'),
3675
3676			positionVolumeHandle = function(volume, secondTry) {
3677
3678				if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') {
3679					volumeSlider.show();
3680					positionVolumeHandle(volume, true);
3681					volumeSlider.hide()
3682					return;
3683				}
3684			
3685				// correct to 0-1
3686				volume = Math.max(0,volume);
3687				volume = Math.min(volume,1);					
3688				
3689				// ajust mute button style
3690				if (volume == 0) {
3691					mute.removeClass('mejs-mute').addClass('mejs-unmute');
3692				} else {
3693					mute.removeClass('mejs-unmute').addClass('mejs-mute');
3694				}				
3695
3696				// position slider 
3697				if (mode == 'vertical') {
3698					var 
3699					
3700						// height of the full size volume slider background
3701						totalHeight = volumeTotal.height(),
3702						
3703						// top/left of full size volume slider background
3704						totalPosition = volumeTotal.position(),
3705						
3706						// the new top position based on the current volume
3707						// 70% volume on 100px height == top:30px
3708						newTop = totalHeight - (totalHeight * volume);
3709	
3710					// handle
3711					volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2)));
3712	
3713					// show the current visibility
3714					volumeCurrent.height(totalHeight - newTop );
3715					volumeCurrent.css('top', totalPosition.top + newTop);
3716				} else {
3717					var 
3718					
3719						// height of the full size volume slider background
3720						totalWidth = volumeTotal.width(),
3721						
3722						// top/left of full size volume slider background
3723						totalPosition = volumeTotal.position(),
3724						
3725						// the new left position based on the current volume
3726						newLeft = totalWidth * volume;
3727	
3728					// handle
3729					volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2)));
3730	
3731					// rezize the current part of the volume bar
3732					volumeCurrent.width( Math.round(newLeft) );
3733				}
3734			},
3735			handleVolumeMove = function(e) {
3736				
3737				var volume = null,
3738					totalOffset = volumeTotal.offset();
3739				
3740				// calculate the new volume based on the moust position
3741				if (mode == 'vertical') {
3742				
3743					var
3744						railHeight = volumeTotal.height(),
3745						totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
3746						newY = e.pageY - totalOffset.top;
3747						
3748					volume = (railHeight - newY) / railHeight;
3749						
3750					// the controls just hide themselves (usually when mouse moves too far up)
3751					if (totalOffset.top == 0 || totalOffset.left == 0)
3752						return;
3753					
3754				} else {
3755					var
3756						railWidth = volumeTotal.width(),
3757						newX = e.pageX - totalOffset.left;
3758						
3759					volume = newX / railWidth;
3760				}
3761				
3762				// ensure the volume isn't outside 0-1
3763				volume = Math.max(0,volume);
3764				volume = Math.min(volume,1);
3765				
3766				// position the slider and handle			
3767				positionVolumeHandle(volume);
3768				
3769				// set the media object (this will trigger the volumechanged event)
3770				if (volume == 0) {
3771					media.setMuted(true);
3772				} else {
3773					media.setMuted(false);
3774				}
3775				media.setVolume(volume);			
3776			},
3777			mouseIsDown = false,
3778			mouseIsOver = false;
3779
3780			// SLIDER
3781			
3782			mute
3783				.hover(function() {
3784					volumeSlider.show();
3785					mouseIsOver = true;
3786				}, function() {
3787					mouseIsOver = false;	
3788						
3789					if (!mouseIsDown && mode == 'vertical')	{
3790						volumeSlider.hide();
3791					}
3792				});
3793			
3794			volumeSlider
3795				.bind('mouseover', function() {
3796					mouseIsOver = true;	
3797				})
3798				.bind('mousedown', function (e) {
3799					handleVolumeMove(e);
3800					t.globalBind('mousemove.vol', function(e) {
3801						handleVolumeMove(e);
3802					});
3803					t.globalBind('mouseup.vol', function () {
3804						mouseIsDown = false;
3805						t.globalUnbind('.vol');
3806
3807						if (!mouseIsOver && mode == 'vertical') {
3808							volumeSlider.hide();
3809						}
3810					});
3811					mouseIsDown = true;
3812						
3813					return false;
3814				});
3815
3816
3817			// MUTE button
3818			mute.find('button').click(function() {
3819				media.setMuted( !media.muted );
3820			});
3821
3822			// listen for volume change events from other sources
3823			media.addEventListener('volumechange', function(e) {
3824				if (!mouseIsDown) {
3825					if (media.muted) {
3826						positionVolumeHandle(0);
3827						mute.removeClass('mejs-mute').addClass('mejs-unmute');
3828					} else {
3829						positionVolumeHandle(media.volume);
3830						mute.removeClass('mejs-unmute').addClass('mejs-mute');
3831					}
3832				}
3833			}, false);
3834
3835			if (t.container.is(':visible')) {
3836				// set initial volume
3837				positionVolumeHandle(player.options.startVolume);
3838
3839				// mutes the media and sets the volume icon muted if the initial volume is set to 0
3840        if (player.options.startVolume === 0) {
3841          media.setMuted(true);
3842        }
3843
3844				// shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
3845				if (media.pluginType === 'native') {
3846					media.setVolume(player.options.startVolume);
3847				}
3848			}
3849		}
3850	});
3851	
3852})(mejs.$);
3853
3854(function($) {
3855
3856	$.extend(mejs.MepDefaults, {
3857		usePluginFullScreen: true,
3858		newWindowCallback: function() { return '';},
3859		fullscreenText: mejs.i18n.t('Fullscreen')
3860	});
3861
3862	$.extend(MediaElementPlayer.prototype, {
3863
3864		isFullScreen: false,
3865
3866		isNativeFullScreen: false,
3867
3868		isInIframe: false,
3869
3870		buildfullscreen: function(player, controls, layers, media) {
3871
3872			if (!player.isVideo)
3873				return;
3874
3875			player.isInIframe = (window.location != window.parent.location);
3876
3877			// native events
3878			if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
3879
3880				// chrome doesn't alays fire this in an iframe
3881				var func = function(e) {
3882					if (player.isFullScreen) {
3883						if (mejs.MediaFeatures.isFullScreen()) {
3884							player.isNativeFullScreen = true;
3885							// reset the controls once we are fully in full screen
3886							player.setControlsSize();
3887						} else {
3888							player.isNativeFullScreen = false;
3889							// when a user presses ESC
3890							// make sure to put the player back into place
3891							player.exitFullScreen();
3892						}
3893					}
3894				};
3895
3896				player.globalBind(mejs.MediaFeatures.fullScreenEventName, func);
3897			}
3898
3899			var t = this,
3900				normalHeight = 0,
3901				normalWidth = 0,
3902				container = player.container,
3903				fullscreenBtn =
3904					$('<div class="mejs-button mejs-fullscreen-button">' +
3905						'<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' +
3906					'</div>')
3907					.appendTo(controls);
3908
3909				if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) {
3910
3911					fullscreenBtn.click(function() {
3912						var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen;
3913
3914						if (isFullScreen) {
3915							player.exitFullScreen();
3916						} else {
3917							player.enterFullScreen();
3918						}
3919					});
3920
3921				} else {
3922
3923					var hideTimeout = null,
3924						supportsPointerEvents = (function() {
3925							// TAKEN FROM MODERNIZR
3926							var element = document.createElement('x'),
3927								documentElement = document.documentElement,
3928								getComputedStyle = window.getComputedStyle,
3929								supports;
3930							if(!('pointerEvents' in element.style)){
3931								return false;
3932							}
3933							element.style.pointerEvents = 'auto';
3934							element.style.pointerEvents = 'x';
3935							documentElement.appendChild(element);
3936							supports = getComputedStyle &&
3937								getComputedStyle(element, '').pointerEvents === 'auto';
3938							documentElement.removeChild(element);
3939							return !!supports;
3940						})();
3941
3942					//
3943
3944					if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :(
3945
3946						// allows clicking through the fullscreen button and controls down directly to Flash
3947
3948						/*
3949						 When a user puts his mouse over the fullscreen button, the controls are disabled
3950						 So we put a div over the video and another one on iether side of the fullscreen button
3951						 that caputre mouse movement
3952						 and restore the controls once the mouse moves outside of the fullscreen button
3953						*/
3954
3955						var fullscreenIsDisabled = false,
3956							restoreControls = function() {
3957								if (fullscreenIsDisabled) {
3958									// hide the hovers
3959									for (var i in hoverDivs) {
3960										hoverDivs[i].hide();
3961									}
3962
3963									// restore the control bar
3964									fullscreenBtn.css('pointer-events', '');
3965									t.controls.css('pointer-events', '');
3966
3967									// prevent clicks from pausing video
3968									t.media.removeEventListener('click', t.clickToPlayPauseCallback);
3969
3970									// store for later
3971									fullscreenIsDisabled = false;
3972								}
3973							},
3974							hoverDivs = {},
3975							hoverDivNames = ['top', 'left', 'right', 'bottom'],
3976							i, len,
3977							positionHoverDivs = function() {
3978								var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left,
3979									fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top,
3980									fullScreenBtnWidth = fullscreenBtn.outerWidth(true),
3981									fullScreenBtnHeight = fullscreenBtn.outerHeight(true),
3982									containerWidth = t.container.width(),
3983									containerHeight = t.container.height();
3984
3985								for (i in hoverDivs) {
3986									hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'});
3987								}
3988
3989								// over video, but not controls
3990								hoverDivs['top']
3991									.width( containerWidth )
3992									.height( fullScreenBtnOffsetTop );
3993
3994								// over controls, but not the fullscreen button
3995								hoverDivs['left']
3996									.width( fullScreenBtnOffsetLeft )
3997									.height( fullScreenBtnHeight )
3998									.css({top: fullScreenBtnOffsetTop});
3999
4000								// after the fullscreen button
4001								hoverDivs['right']
4002									.width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth )
4003									.height( fullScreenBtnHeight )
4004									.css({top: fullScreenBtnOffsetTop,
4005										 left: fullScreenBtnOffsetLeft + fullScreenBtnWidth});
4006
4007								// under the fullscreen button
4008								hoverDivs['bottom']
4009									.width( containerWidth )
4010									.height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop )
4011									.css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight});
4012							};
4013
4014						t.globalBind('resize', function() {
4015							positionHoverDivs();
4016						});
4017
4018						for (i = 0, len = hoverDivNames.length; i < len; i++) {
4019							hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide();
4020						}
4021
4022						// on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash
4023						fullscreenBtn.on('mouseover',function() {
4024
4025							if (!t.isFullScreen) {
4026
4027								var buttonPos = fullscreenBtn.offset(),
4028									containerPos = player.container.offset();
4029
4030								// move the button in Flash into place
4031								media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false);
4032
4033								// allows click through
4034								fullscreenBtn.css('pointer-events', 'none');
4035								t.controls.css('pointer-events', 'none');
4036
4037								// restore click-to-play
4038								t.media.addEventListener('click', t.clickToPlayPauseCallback);
4039
4040								// show the divs that will restore things
4041								for (i in hoverDivs) {
4042									hoverDivs[i].show();
4043								}
4044
4045								positionHoverDivs();
4046
4047								fullscreenIsDisabled = true;
4048							}
4049
4050						});
4051
4052						// restore controls anytime the user enters or leaves fullscreen
4053						media.addEventListener('fullscreenchange', function(e) {
4054							t.isFullScreen = !t.isFullScreen;
4055							// don't allow plugin click to pause video - messes with
4056							// plugin's controls
4057							if (t.isFullScreen) {
4058								t.media.removeEventListener('click', t.clickToPlayPauseCallback);
4059							} else {
4060								t.media.addEventListener('click', t.clickToPlayPauseCallback);
4061							}
4062							restoreControls();
4063						});
4064
4065
4066						// the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events
4067						// so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button
4068
4069						t.globalBind('mousemove', function(e) {
4070
4071							// if the mouse is anywhere but the fullsceen button, then restore it all
4072							if (fullscreenIsDisabled) {
4073
4074								var fullscreenBtnPos = fullscreenBtn.offset();
4075
4076
4077								if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) ||
4078									e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true)
4079									) {
4080
4081									fullscreenBtn.css('pointer-events', '');
4082									t.controls.css('pointer-events', '');
4083
4084									fullscreenIsDisabled = false;
4085								}
4086							}
4087						});
4088
4089
4090
4091					} else {
4092
4093						// the hover state will show the fullscreen button in Flash to hover up and click
4094
4095						fullscreenBtn
4096							.on('mouseover', function() {
4097
4098								if (hideTimeout !== null) {
4099									clearTimeout(hideTimeout);
4100									delete hideTimeout;
4101								}
4102
4103								var buttonPos = fullscreenBtn.offset(),
4104									containerPos = player.container.offset();
4105
4106								media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true);
4107
4108							})
4109							.on('mouseout', function() {
4110
4111								if (hideTimeout !== null) {
4112									clearTimeout(hideTimeout);
4113									delete hideTimeout;
4114								}
4115
4116								hideTimeout = setTimeout(function() {
4117									media.hideFullscreenButton();
4118								}, 1500);
4119
4120
4121							});
4122					}
4123				}
4124
4125			player.fullscreenBtn = fullscreenBtn;
4126
4127			t.globalBind('keydown',function (e) {
4128				if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) {
4129					player.exitFullScreen();
4130				}
4131			});
4132
4133		},
4134
4135		cleanfullscreen: function(player) {
4136			player.exitFullScreen();
4137		},
4138
4139        containerSizeTimeout: null,
4140
4141		enterFullScreen: function() {
4142
4143			var t = this;
4144
4145			// firefox+flash can't adjust plugin sizes without resetting :(
4146			if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) {
4147				//t.media.setFullscreen(true);
4148				//player.isFullScreen = true;
4149				return;
4150			}
4151
4152			// set it to not show scroll bars so 100% will work
4153            $(document.documentElement).addClass('mejs-fullscreen');
4154
4155			// store sizing
4156			normalHeight = t.container.height();
4157			normalWidth = t.container.width();
4158
4159			// attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now)
4160			if (t.media.pluginType === 'native') {
4161				if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
4162
4163					mejs.MediaFeatures.requestFullScreen(t.container[0]);
4164					//return;
4165
4166					if (t.isInIframe) {
4167						// sometimes exiting from fullscreen doesn't work
4168						// notably in Chrome <iframe>. Fixed in version 17
4169						setTimeout(function checkFullscreen() {
4170
4171							if (t.isNativeFullScreen) {
4172								var zoomMultiplier = window["devicePixelRatio"] || 1;
4173								// Use a percent error margin since devicePixelRatio is a float and not exact.
4174								var percentErrorMargin = 0.002; // 0.2%
4175								var windowWidth = zoomMultiplier * $(window).width();
4176								var screenWidth = screen.width;
4177								var absDiff = Math.abs(screenWidth - windowWidth);
4178								var marginError = screenWidth * percentErrorMargin;
4179
4180								// check if the video is suddenly not really fullscreen
4181								if (absDiff > marginError) {
4182									// manually exit
4183									t.exitFullScreen();
4184								} else {
4185									// test again
4186									setTimeout(checkFullscreen, 500);
4187								}
4188							}
4189
4190
4191						}, 500);
4192					}
4193
4194				} else if (mejs.MediaFeatures.hasSemiNativeFullScreen) {
4195					t.media.webkitEnterFullscreen();
4196					return;
4197				}
4198			}
4199
4200			// check for iframe launch
4201			if (t.isInIframe) {
4202				var url = t.options.newWindowCallback(this);
4203
4204
4205				if (url !== '') {
4206
4207					// launch immediately
4208					if (!mejs.MediaFeatures.hasTrueNativeFullScreen) {
4209						t.pause();
4210						window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no');
4211						return;
4212					} else {
4213						setTimeout(function() {
4214							if (!t.isNativeFullScreen) {
4215								t.pause();
4216								window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no');
4217							}
4218						}, 250);
4219					}
4220				}
4221
4222			}
4223
4224			// full window code
4225
4226
4227
4228			// make full size
4229			t.container
4230				.addClass('mejs-container-fullscreen')
4231				.width('100%')
4232				.height('100%');
4233				//.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000});
4234
4235			// Only needed for safari 5.1 native full screen, can cause display issues elsewhere
4236			// Actually, it seems to be needed for IE8, too
4237			//if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
4238				t.containerSizeTimeout = setTimeout(function() {
4239					t.container.css({width: '100%', height: '100%'});
4240					t.setControlsSize();
4241				}, 500);
4242			//}
4243
4244			if (t.media.pluginType === 'native') {
4245				t.$media
4246					.width('100%')
4247					.height('100%');
4248			} else {
4249				t.container.find('.mejs-shim')
4250					.width('100%')
4251					.height('100%');
4252
4253				//if (!mejs.MediaFeatures.hasTrueNativeFullScreen) {
4254					t.media.setVideoSize($(window).width(),$(window).height());
4255				//}
4256			}
4257
4258			t.layers.children('div')
4259				.width('100%')
4260				.height('100%');
4261
4262			if (t.fullscreenBtn) {
4263				t.fullscreenBtn
4264					.removeClass('mejs-fullscreen')
4265					.addClass('mejs-unfullscreen');
4266			}
4267
4268			t.setControlsSize();
4269			t.isFullScreen = true;
4270
4271			t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%');
4272			t.container.find('.mejs-captions-position').css('bottom', '45px');
4273		},
4274
4275		exitFullScreen: function() {
4276
4277			var t = this;
4278
4279            // Prevent container from attempting to stretch a second time
4280            clearTimeout(t.containerSizeTimeout);
4281
4282			// firefox can't adjust plugins
4283			if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) {
4284				t.media.setFullscreen(false);
4285				//player.isFullScreen = false;
4286				return;
4287			}
4288
4289			// come outo of native fullscreen
4290			if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) {
4291				mejs.MediaFeatures.cancelFullScreen();
4292			}
4293
4294			// restore scroll bars to document
4295            $(document.documentElement).removeClass('mejs-fullscreen');
4296
4297			t.container
4298				.removeClass('mejs-container-fullscreen')
4299				.width(normalWidth)
4300				.height(normalHeight);
4301				//.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1});
4302
4303			if (t.media.pluginType === 'native') {
4304				t.$media
4305					.width(normalWidth)
4306					.height(normalHeight);
4307			} else {
4308				t.container.find('.mejs-shim')
4309					.width(normalWidth)
4310					.height(normalHeight);
4311
4312				t.media.setVideoSize(normalWidth, normalHeight);
4313			}
4314
4315			t.layers.children('div')
4316				.width(normalWidth)
4317				.height(normalHeight);
4318
4319			t.fullscreenBtn
4320				.removeClass('mejs-unfullscreen')
4321				.addClass('mejs-fullscreen');
4322
4323			t.setControlsSize();
4324			t.isFullScreen = false;
4325
4326			t.container.find('.mejs-captions-text').css('font-size','');
4327			t.container.find('.mejs-captions-position').css('bottom', '');
4328		}
4329	});
4330
4331})(mejs.$);
4332
4333(function($) {
4334
4335	// Speed
4336	$.extend(mejs.MepDefaults, {
4337
4338		speeds: ['1.50', '1.25', '1.00', '0.75'],
4339
4340		defaultSpeed: '1.00'
4341
4342	});
4343
4344	$.extend(MediaElementPlayer.prototype, {
4345
4346		buildspeed: function(player, controls, layers, media) {
4347			var t = this;
4348
4349			if (t.media.pluginType == 'native') {
4350				var s = '<div class="mejs-button mejs-speed-button"><button type="button">'+t.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>';
4351				var i, ss;
4352
4353				if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) {
4354					t.options.speeds.push(t.options.defaultSpeed);
4355				}
4356
4357				t.options.speeds.sort(function(a, b) {
4358					return parseFloat(b) - parseFloat(a);
4359				});
4360
4361				for (i = 0; i < t.options.speeds.length; i++) {
4362					s += '<li><input type="radio" name="speed" value="' + t.options.speeds[i] + '" id="' + t.options.speeds[i] + '" ';
4363					if (t.options.speeds[i] == t.options.defaultSpeed) {
4364						s += 'checked=true ';
4365						s += '/><label for="' + t.options.speeds[i] + '" class="mejs-speed-selected">'+ t.options.speeds[i] + 'x</label></li>';
4366					} else {
4367						s += '/><label for="' + t.options.speeds[i] + '">'+ t.options.speeds[i] + 'x</label></li>';
4368					}
4369				}
4370				s += '</ul></div></div>';
4371
4372				player.speedButton = $(s).appendTo(controls);
4373
4374				player.playbackspeed = t.options.defaultSpeed;
4375
4376				player.speedButton
4377				.on('click', 'input[type=radio]', function() {
4378					player.playbackspeed = $(this).attr('value');
4379					media.playbackRate = parseFloat(player.playbackspeed);
4380					player.speedButton.find('button').text(player.playbackspeed + 'x');
4381					player.speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected');
4382					player.speedButton.find('input[type=radio]:checked').next().addClass('mejs-speed-selected');
4383				});
4384
4385				ss = player.speedButton.find('.mejs-speed-selector');
4386				ss.height(this.speedButton.find('.mejs-speed-selector ul').outerHeight(true) + player.speedButton.find('.mejs-speed-translations').outerHeight(true));
4387				ss.css('top', (-1 * ss.height()) + 'px');
4388			}
4389		}
4390	});
4391
4392})(mejs.$);
4393
4394(function($) {
4395
4396	// add extra default options
4397	$.extend(mejs.MepDefaults, {
4398		// this will automatically turn on a <track>
4399		startLanguage: '',
4400
4401		tracksText: mejs.i18n.t('Captions/Subtitles'),
4402
4403		// option to remove the [cc] button when no <track kind="subtitles"> are present
4404		hideCaptionsButtonWhenEmpty: true,
4405
4406		// If true and we only have one track, change captions to popup
4407		toggleCaptionsButtonWhenOnlyOne: false,
4408
4409		// #id or .class
4410		slidesSelector: ''
4411	});
4412
4413	$.extend(MediaElementPlayer.prototype, {
4414
4415		hasChapters: false,
4416
4417		buildtracks: function(player, controls, layers, media) {
4418			if (player.tracks.length === 0)
4419				return;
4420
4421			var t = this,
4422				i,
4423				options = '';
4424
4425			if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide
4426				for (i = t.domNode.textTracks.length - 1; i >= 0; i--) {
4427					t.domNode.textTracks[i].mode = "hidden";
4428				}
4429			}
4430			player.chapters =
4431					$('<div class="mejs-chapters mejs-layer"></div>')
4432						.prependTo(layers).hide();
4433			player.captions =
4434					$('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>')
4435						.prependTo(layers).hide();
4436			player.captionsText = player.captions.find('.mejs-captions-text');
4437			player.captionsButton =
4438					$('<div class="mejs-button mejs-captions-button">'+
4439						'<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+
4440						'<div class="mejs-captions-selector">'+
4441							'<ul>'+
4442								'<li>'+
4443									'<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' +
4444									'<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+
4445								'</li>'	+
4446							'</ul>'+
4447						'</div>'+
4448					'</div>')
4449						.appendTo(controls);
4450
4451
4452			var subtitleCount = 0;
4453			for (i=0; i<player.tracks.length; i++) {
4454				if (player.tracks[i].kind == 'subtitles') {
4455					subtitleCount++;
4456				}
4457			}
4458
4459			// if only one language then just make the button a toggle
4460			if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){
4461				// click
4462				player.captionsButton.on('click',function() {
4463					if (player.selectedTrack === null) {
4464						lang = player.tracks[0].srclang;
4465					} else {
4466						lang = 'none';
4467					}
4468					player.setTrack(lang);
4469				});
4470			} else {
4471				// hover or keyboard focus
4472				player.captionsButton.on( 'mouseenter focusin', function() {
4473					$(this).find('.mejs-captions-selector').css('visibility','visible');
4474				})
4475
4476				// handle clicks to the language radio buttons
4477				.on('click','input[type=radio]',function() {
4478					lang = this.value;
4479					player.setTrack(lang);
4480				});
4481
4482				player.captionsButton.on( 'mouseleave focusout', function() {
4483					$(this).find(".mejs-captions-selector").css("visibility","hidden");
4484				});
4485
4486			}
4487
4488			if (!player.options.alwaysShowControls) {
4489				// move with controls
4490				player.container
4491					.bind('controlsshown', function () {
4492						// push captions above controls
4493						player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
4494
4495					})
4496					.bind('controlshidden', function () {
4497						if (!media.paused) {
4498							// move back to normal place
4499							player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover');
4500						}
4501					});
4502			} else {
4503				player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
4504			}
4505
4506			player.trackToLoad = -1;
4507			player.selectedTrack = null;
4508			player.isLoadingTrack = false;
4509
4510			// add to list
4511			for (i=0; i<player.tracks.length; i++) {
4512				if (player.tracks[i].kind == 'subtitles') {
4513					player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label);
4514				}
4515			}
4516
4517			// start loading tracks
4518			player.loadNextTrack();
4519
4520			media.addEventListener('timeupdate',function(e) {
4521				player.displayCaptions();
4522			}, false);
4523
4524			if (player.options.slidesSelector !== '') {
4525				player.slidesContainer = $(player.options.slidesSelector);
4526
4527				media.addEventListener('timeupdate',function(e) {
4528					player.displaySlides();
4529				}, false);
4530
4531			}
4532
4533			media.addEventListener('loadedmetadata', function(e) {
4534				player.displayChapters();
4535			}, false);
4536
4537			player.container.hover(
4538				function () {
4539					// chapters
4540					if (player.hasChapters) {
4541						player.chapters.css('visibility','visible');
4542						player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight());
4543					}
4544				},
4545				function () {
4546					if (player.hasChapters && !media.paused) {
4547						player.chapters.fadeOut(200, function() {
4548							$(this).css('visibility','hidden');
4549							$(this).css('display','block');
4550						});
4551					}
4552				});
4553
4554			// check for autoplay
4555			if (player.node.getAttribute('autoplay') !== null) {
4556				player.chapters.css('visibility','hidden');
4557			}
4558		},
4559
4560		setTrack: function(lang){
4561
4562			var t = this,
4563				i;
4564
4565			if (lang == 'none') {
4566				t.selectedTrack = null;
4567				t.captionsButton.removeClass('mejs-captions-enabled');
4568			} else {
4569				for (i=0; i<t.tracks.length; i++) {
4570					if (t.tracks[i].srclang == lang) {
4571						if (t.selectedTrack === null)
4572							t.captionsButton.addClass('mejs-captions-enabled');
4573						t.selectedTrack = t.tracks[i];
4574						t.captions.attr('lang', t.selectedTrack.srclang);
4575						t.displayCaptions();
4576						break;
4577					}
4578				}
4579			}
4580		},
4581
4582		loadNextTrack: function() {
4583			var t = this;
4584
4585			t.trackToLoad++;
4586			if (t.trackToLoad < t.tracks.length) {
4587				t.isLoadingTrack = true;
4588				t.loadTrack(t.trackToLoad);
4589			} else {
4590				// add done?
4591				t.isLoadingTrack = false;
4592
4593				t.checkForTracks();
4594			}
4595		},
4596
4597		loadTrack: function(index){
4598			var
4599				t = this,
4600				track = t.tracks[index],
4601				after = function() {
4602
4603					track.isLoaded = true;
4604
4605					// create button
4606					//t.addTrackButton(track.srclang);
4607					t.enableTrackButton(track.srclang, track.label);
4608
4609					t.loadNextTrack();
4610
4611				};
4612
4613
4614			$.ajax({
4615				url: track.src,
4616				dataType: "text",
4617				success: function(d) {
4618
4619					// parse the loaded file
4620					if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) {
4621						track.entries = mejs.TrackFormatParser.dfxp.parse(d);
4622					} else {
4623						track.entries = mejs.TrackFormatParser.webvtt.parse(d);
4624					}
4625
4626					after();
4627
4628					if (track.kind == 'chapters') {
4629						t.media.addEventListener('play', function(e) {
4630							if (t.media.duration > 0) {
4631								t.displayChapters(track);
4632							}
4633						}, false);
4634					}
4635
4636					if (track.kind == 'slides') {
4637						t.setupSlides(track);
4638					}
4639				},
4640				error: function() {
4641					t.loadNextTrack();
4642				}
4643			});
4644		},
4645
4646		enableTrackButton: function(lang, label) {
4647			var t = this;
4648
4649			if (label === '') {
4650				label = mejs.language.codes[lang] || lang;
4651			}
4652
4653			t.captionsButton
4654				.find('input[value=' + lang + ']')
4655					.prop('disabled',false)
4656				.siblings('label')
4657					.html( label );
4658
4659			// auto select
4660			if (t.options.startLanguage == lang) {
4661				$('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click');
4662			}
4663
4664			t.adjustLanguageBox();
4665		},
4666
4667		addTrackButton: function(lang, label) {
4668			var t = this;
4669			if (label === '') {
4670				label = mejs.language.codes[lang] || lang;
4671			}
4672
4673			t.captionsButton.find('ul').append(
4674				$('<li>'+
4675					'<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' +
4676					'<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+
4677				'</li>')
4678			);
4679
4680			t.adjustLanguageBox();
4681
4682			// remove this from the dropdownlist (if it exists)
4683			t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove();
4684		},
4685
4686		adjustLanguageBox:function() {
4687			var t = this;
4688			// adjust the size of the outer box
4689			t.captionsButton.find('.mejs-captions-selector').height(
4690				t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) +
4691				t.captionsButton.find('.mejs-captions-translations').outerHeight(true)
4692			);
4693		},
4694
4695		checkForTracks: function() {
4696			var
4697				t = this,
4698				hasSubtitles = false;
4699
4700			// check if any subtitles
4701			if (t.options.hideCaptionsButtonWhenEmpty) {
4702				for (i=0; i<t.tracks.length; i++) {
4703					if (t.tracks[i].kind == 'subtitles') {
4704						hasSubtitles = true;
4705						break;
4706					}
4707				}
4708
4709				if (!hasSubtitles) {
4710					t.captionsButton.hide();
4711					t.setControlsSize();
4712				}
4713			}
4714		},
4715
4716		displayCaptions: function() {
4717
4718			if (typeof this.tracks == 'undefined')
4719				return;
4720
4721			var
4722				t = this,
4723				i,
4724				track = t.selectedTrack;
4725
4726			if (track !== null && track.isLoaded) {
4727				for (i=0; i<track.entries.times.length; i++) {
4728					if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) {
4729						// Set the line before the timecode as a class so the cue can be targeted if needed
4730						t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || ''));
4731						t.captions.show().height(0);
4732						return; // exit out if one is visible;
4733					}
4734				}
4735				t.captions.hide();
4736			} else {
4737				t.captions.hide();
4738			}
4739		},
4740
4741		setupSlides: function(track) {
4742			var t = this;
4743
4744			t.slides = track;
4745			t.slides.entries.imgs = [t.slides.entries.text.length];
4746			t.showSlide(0);
4747
4748		},
4749
4750		showSlide: function(index) {
4751			if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') {
4752				return;
4753			}
4754
4755			var t = this,
4756				url = t.slides.entries.text[index],
4757				img = t.slides.entries.imgs[index];
4758
4759			if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') {
4760
4761				t.slides.entries.imgs[index] = img = $('<img src="' + url + '">')
4762						.on('load', function() {
4763							img.appendTo(t.slidesContainer)
4764								.hide()
4765								.fadeIn()
4766								.siblings(':visible')
4767									.fadeOut();
4768
4769						});
4770
4771			} else {
4772
4773				if (!img.is(':visible') && !img.is(':animated')) {
4774
4775					//
4776
4777					img.fadeIn()
4778						.siblings(':visible')
4779							.fadeOut();
4780				}
4781			}
4782
4783		},
4784
4785		displaySlides: function() {
4786
4787			if (typeof this.slides == 'undefined')
4788				return;
4789
4790			var
4791				t = this,
4792				slides = t.slides,
4793				i;
4794
4795			for (i=0; i<slides.entries.times.length; i++) {
4796				if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){
4797
4798					t.showSlide(i);
4799
4800					return; // exit out if one is visible;
4801				}
4802			}
4803		},
4804
4805		displayChapters: function() {
4806			var
4807				t = this,
4808				i;
4809
4810			for (i=0; i<t.tracks.length; i++) {
4811				if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) {
4812					t.drawChapters(t.tracks[i]);
4813					t.hasChapters = true;
4814					break;
4815				}
4816			}
4817		},
4818
4819		drawChapters: function(chapters) {
4820			var
4821				t = this,
4822				i,
4823				dur,
4824				//width,
4825				//left,
4826				percent = 0,
4827				usedPercent = 0;
4828
4829			t.chapters.empty();
4830
4831			for (i=0; i<chapters.entries.times.length; i++) {
4832				dur = chapters.entries.times[i].stop - chapters.entries.times[i].start;
4833				percent = Math.floor(dur / t.media.duration * 100);
4834				if (percent + usedPercent > 100 || // too large
4835					i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in
4836					{
4837					percent = 100 - usedPercent;
4838				}
4839				//width = Math.floor(t.width * dur / t.media.duration);
4840				//left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration);
4841				//if (left + width > t.width) {
4842				//	width = t.width - left;
4843				//}
4844
4845				t.chapters.append( $(
4846					'<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' +
4847						'<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' +
4848							'<span class="ch-title">' + chapters.entries.text[i] + '</span>' +
4849							'<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' +
4850						'</div>' +
4851					'</div>'));
4852				usedPercent += percent;
4853			}
4854
4855			t.chapters.find('div.mejs-chapter').click(function() {
4856				t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) );
4857				if (t.media.paused) {
4858					t.media.play();
4859				}
4860			});
4861
4862			t.chapters.show();
4863		}
4864	});
4865
4866
4867
4868	mejs.language = {
4869		codes:  {
4870			af:'Afrikaans',
4871			sq:'Albanian',
4872			ar:'Arabic',
4873			be:'Belarusian',
4874			bg:'Bulgarian',
4875			ca:'Catalan',
4876			zh:'Chinese',
4877			'zh-cn':'Chinese Simplified',
4878			'zh-tw':'Chinese Traditional',
4879			hr:'Croatian',
4880			cs:'Czech',
4881			da:'Danish',
4882			nl:'Dutch',
4883			en:'English',
4884			et:'Estonian',
4885			fl:'Filipino',
4886			fi:'Finnish',
4887			fr:'French',
4888			gl:'Galician',
4889			de:'German',
4890			el:'Greek',
4891			ht:'Haitian Creole',
4892			iw:'Hebrew',
4893			hi:'Hindi',
4894			hu:'Hungarian',
4895			is:'Icelandic',
4896			id:'Indonesian',
4897			ga:'Irish',
4898			it:'Italian',
4899			ja:'Japanese',
4900			ko:'Korean',
4901			lv:'Latvian',
4902			lt:'Lithuanian',
4903			mk:'Macedonian',
4904			ms:'Malay',
4905			mt:'Maltese',
4906			no:'Norwegian',
4907			fa:'Persian',
4908			pl:'Polish',
4909			pt:'Portuguese',
4910			// 'pt-pt':'Portuguese (Portugal)',
4911			ro:'Romanian',
4912			ru:'Russian',
4913			sr:'Serbian',
4914			sk:'Slovak',
4915			sl:'Slovenian',
4916			es:'Spanish',
4917			sw:'Swahili',
4918			sv:'Swedish',
4919			tl:'Tagalog',
4920			th:'Thai',
4921			tr:'Turkish',
4922			uk:'Ukrainian',
4923			vi:'Vietnamese',
4924			cy:'Welsh',
4925			yi:'Yiddish'
4926		}
4927	};
4928
4929	/*
4930	Parses WebVTT format which should be formatted as
4931	================================
4932	WEBVTT
4933
4934	1
4935	00:00:01,1 --> 00:00:05,000
4936	A line of text
4937
4938	2
4939	00:01:15,1 --> 00:02:05,000
4940	A second line of text
4941
4942	===============================
4943
4944	Adapted from: http://www.delphiki.com/html5/playr
4945	*/
4946	mejs.TrackFormatParser = {
4947		webvtt: {
4948			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})?)(.*)$/,
4949
4950			parse: function(trackText) {
4951				var
4952					i = 0,
4953					lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/),
4954					entries = {text:[], times:[]},
4955					timecode,
4956					text,
4957					identifier;
4958				for(; i<lines.length; i++) {
4959					timecode = this.pattern_timecode.exec(lines[i]);
4960
4961					if (timecode && i<lines.length) {
4962						if ((i - 1) >= 0 && lines[i - 1] !== '') {
4963							identifier = lines[i - 1];
4964						}
4965						i++;
4966						// grab all the (possibly multi-line) text that follows
4967						text = lines[i];
4968						i++;
4969						while(lines[i] !== '' && i<lines.length){
4970							text = text + '\n' + lines[i];
4971							i++;
4972						}
4973						text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
4974						// Text is in a different array so I can use .join
4975						entries.text.push(text);
4976						entries.times.push(
4977						{
4978							identifier: identifier,
4979							start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]),
4980							stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]),
4981							settings: timecode[5]
4982						});
4983					}
4984					identifier = '';
4985				}
4986				return entries;
4987			}
4988		},
4989		// Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420
4990		dfxp: {
4991			parse: function(trackText) {
4992				trackText = $(trackText).filter("tt");
4993				var
4994					i = 0,
4995					container = trackText.children("div").eq(0),
4996					lines = container.find("p"),
4997					styleNode = trackText.find("#" + container.attr("style")),
4998					styles,
4999					begin,
5000					end,
5001					text,
5002					entries = {text:[], times:[]};
5003
5004
5005				if (styleNode.length) {
5006					var attributes = styleNode.removeAttr("id").get(0).attributes;
5007					if (attributes.length) {
5008						styles = {};
5009						for (i = 0; i < attributes.length; i++) {
5010							styles[attributes[i].name.split(":")[1]] = attributes[i].value;
5011						}
5012					}
5013				}
5014
5015				for(i = 0; i<lines.length; i++) {
5016					var style;
5017					var _temp_times = {
5018						start: null,
5019						stop: null,
5020						style: null
5021					};
5022					if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin"));
5023					if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end"));
5024					if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end"));
5025					if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin"));
5026					if (styles) {
5027						style = "";
5028						for (var _style in styles) {
5029							style += _style + ":" + styles[_style] + ";";
5030						}
5031					}
5032					if (style) _temp_times.style = style;
5033					if (_temp_times.start === 0) _temp_times.start = 0.200;
5034					entries.times.push(_temp_times);
5035					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>");
5036					entries.text.push(text);
5037					if (entries.times.start === 0) entries.times.start = 2;
5038				}
5039				return entries;
5040			}
5041		},
5042		split2: function (text, regex) {
5043			// normal version for compliant browsers
5044			// see below for IE fix
5045			return text.split(regex);
5046		}
5047	};
5048
5049	// test for browsers with bad String.split method.
5050	if ('x\n\ny'.split(/\n/gi).length != 3) {
5051		// add super slow IE8 and below version
5052		mejs.TrackFormatParser.split2 = function(text, regex) {
5053			var
5054				parts = [],
5055				chunk = '',
5056				i;
5057
5058			for (i=0; i<text.length; i++) {
5059				chunk += text.substring(i,i+1);
5060				if (regex.test(chunk)) {
5061					parts.push(chunk.replace(regex, ''));
5062					chunk = '';
5063				}
5064			}
5065			parts.push(chunk);
5066			return parts;
5067		};
5068	}
5069
5070})(mejs.$);
5071
5072/*
5073* ContextMenu Plugin
5074* 
5075*
5076*/
5077
5078(function($) {
5079
5080$.extend(mejs.MepDefaults,
5081	{ 'contextMenuItems': [
5082		// demo of a fullscreen option
5083		{ 
5084			render: function(player) {
5085				
5086				// check for fullscreen plugin
5087				if (typeof player.enterFullScreen == 'undefined')
5088					return null;
5089			
5090				if (player.isFullScreen) {
5091					return mejs.i18n.t('Turn off Fullscreen');
5092				} else {
5093					return mejs.i18n.t('Go Fullscreen');
5094				}
5095			},
5096			click: function(player) {
5097				if (player.isFullScreen) {
5098					player.exitFullScreen();
5099				} else {
5100					player.enterFullScreen();
5101				}
5102			}
5103		}
5104		,
5105		// demo of a mute/unmute button
5106		{ 
5107			render: function(player) {
5108				if (player.media.muted) {
5109					return mejs.i18n.t('Unmute');
5110				} else {
5111					return mejs.i18n.t('Mute');
5112				}
5113			},
5114			click: function(player) {
5115				if (player.media.muted) {
5116					player.setMuted(false);
5117				} else {
5118					player.setMuted(true);
5119				}
5120			}
5121		},
5122		// separator
5123		{
5124			isSeparator: true
5125		}
5126		,
5127		// demo of simple download video
5128		{ 
5129			render: function(player) {
5130				return mejs.i18n.t('Download Video');
5131			},
5132			click: function(player) {
5133				window.location.href = player.media.currentSrc;
5134			}
5135		}	
5136	]}
5137);
5138
5139
5140	$.extend(MediaElementPlayer.prototype, {
5141		buildcontextmenu: function(player, controls, layers, media) {
5142			
5143			// create context menu
5144			player.contextMenu = $('<div class="mejs-contextmenu"></div>')
5145								.appendTo($('body'))
5146								.hide();
5147			
5148			// create events for showing context menu
5149			player.container.bind('contextmenu', function(e) {
5150				if (player.isContextMenuEnabled) {
5151					e.preventDefault();
5152					player.renderContextMenu(e.clientX-1, e.clientY-1);
5153					return false;
5154				}
5155			});
5156			player.container.bind('click', function() {
5157				player.contextMenu.hide();
5158			});	
5159			player.contextMenu.bind('mouseleave', function() {
5160
5161				//
5162				player.startContextMenuTimer();
5163				
5164			});		
5165		},
5166
5167		cleancontextmenu: function(player) {
5168			player.contextMenu.remove();
5169		},
5170		
5171		isContextMenuEnabled: true,
5172		enableContextMenu: function() {
5173			this.isContextMenuEnabled = true;
5174		},
5175		disableContextMenu: function() {
5176			this.isContextMenuEnabled = false;
5177		},
5178		
5179		contextMenuTimeout: null,
5180		startContextMenuTimer: function() {
5181			//
5182			
5183			var t = this;
5184			
5185			t.killContextMenuTimer();
5186			
5187			t.contextMenuTimer = setTimeout(function() {
5188				t.hideContextMenu();
5189				t.killContextMenuTimer();
5190			}, 750);
5191		},
5192		killContextMenuTimer: function() {
5193			var timer = this.contextMenuTimer;
5194			
5195			//
5196			
5197			if (timer != null) {				
5198				clearTimeout(timer);
5199				delete timer;
5200				timer = null;
5201			}
5202		},		
5203		
5204		hideContextMenu: function() {
5205			this.contextMenu.hide();
5206		},
5207		
5208		renderContextMenu: function(x,y) {
5209			
5210			// alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly
5211			var t = this,
5212				html = '',
5213				items = t.options.contextMenuItems;
5214			
5215			for (var i=0, il=items.length; i<il; i++) {
5216				
5217				if (items[i].isSeparator) {
5218					html += '<div class="mejs-contextmenu-separator"></div>';
5219				} else {
5220				
5221					var rendered = items[i].render(t);
5222				
5223					// render can return null if the item doesn't need to be used at the moment
5224					if (rendered != null) {
5225						html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>';
5226					}
5227				}
5228			}
5229			
5230			// position and show the context menu
5231			t.contextMenu
5232				.empty()
5233				.append($(html))
5234				.css({top:y, left:x})
5235				.show();
5236				
5237			// bind events
5238			t.contextMenu.find('.mejs-contextmenu-item').each(function() {
5239							
5240				// which one is this?
5241				var $dom = $(this),
5242					itemIndex = parseInt( $dom.data('itemindex'), 10 ),
5243					item = t.options.contextMenuItems[itemIndex];
5244				
5245				// bind extra functionality?
5246				if (typeof item.show != 'undefined')
5247					item.show( $dom , t);
5248				
5249				// bind click action
5250				$dom.click(function() {			
5251					// perform click action
5252					if (typeof item.click != 'undefined')
5253						item.click(t);
5254					
5255					// close
5256					t.contextMenu.hide();				
5257				});				
5258			});	
5259			
5260			// stop the controls from hiding
5261			setTimeout(function() {
5262				t.killControlsTimer('rev3');	
5263			}, 100);
5264						
5265		}
5266	});
5267	
5268})(mejs.$);
5269/**
5270 * Postroll plugin
5271 */
5272(function($) {
5273
5274	$.extend(mejs.MepDefaults, {
5275		postrollCloseText: mejs.i18n.t('Close')
5276	});
5277
5278	// Postroll
5279	$.extend(MediaElementPlayer.prototype, {
5280		buildpostroll: function(player, controls, layers, media) {
5281			var
5282				t = this,
5283				postrollLink = t.container.find('link[rel="postroll"]').attr('href');
5284
5285			if (typeof postrollLink !== 'undefined') {
5286				player.postroll =
5287					$('<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();
5288
5289				t.media.addEventListener('ended', function (e) {
5290					$.ajax({
5291						dataType: 'html',
5292						url: postrollLink,
5293						success: function (data, textStatus) {
5294							layers.find('.mejs-postroll-layer-content').html(data);
5295						}
5296					});
5297					player.postroll.show();
5298				}, false);
5299			}
5300		}
5301	});
5302
5303})(mejs.$);
5304

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.