PageSourceSearch

https://www.forsthaus-auerhahn.de/(cms)/module/static/default/cont…/mediaelement-and-player.js

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

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.