PageSourceSearch

https://www.criquiers.fr/js/mediaelement-and-player.js

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

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.