PageSourceSearch

https://biblewebapp.com/study-v2/js/utility/mediaelementjs/mediaelement-and-player.js

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

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.