PageSourceSearch

https://www.afaindia.com/vendor/mediaelement/mediaelement-and-player.js

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

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.