diff johndyer-mediaelement-13fa20a/build/mediaelement-and-player.js @ 0:032bc65ebafc

added core components
author George Fazekas <gyorgy.fazekas@eecs.qmul.ac.uk>
date Wed, 06 Mar 2013 15:45:48 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/johndyer-mediaelement-13fa20a/build/mediaelement-and-player.js	Wed Mar 06 15:45:48 2013 +0000
@@ -0,0 +1,2646 @@
+/*!
+* MediaElement.js
+* HTML5 <video> and <audio> shim and player
+* http://mediaelementjs.com/
+*
+* Creates a JavaScript object that mimics HTML5 MediaElement API
+* for browsers that don't understand HTML5 or can't play the provided codec
+* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3
+*
+* Copyright 2010-2011, John Dyer (http://j.hn)
+* Dual licensed under the MIT or GPL Version 2 licenses.
+*
+*/
+// Namespace
+var mejs = mejs || {};
+
+// version number
+mejs.version = '2.1.9';
+
+// player number (for missing, same id attr)
+mejs.meIndex = 0;
+
+// media types accepted by plugins
+mejs.plugins = {
+	silverlight: [
+		{version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']}
+	],
+	flash: [
+		{version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg']}
+		//,{version: [11,0], types: ['video/webm']} // for future reference
+	]
+};
+
+/*
+Utility methods
+*/
+mejs.Utility = {
+	encodeUrl: function(url) {
+		return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26');
+	},
+	escapeHTML: function(s) {
+		return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
+	},
+	absolutizeUrl: function(url) {
+		var el = document.createElement('div');
+		el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>';
+		return el.firstChild.href;
+	},
+	getScriptPath: function(scriptNames) {
+		var
+			i = 0,
+			j,
+			path = '',
+			name = '',
+			script,
+			scripts = document.getElementsByTagName('script');
+
+		for (; i < scripts.length; i++) {
+			script = scripts[i].src;
+			for (j = 0; j < scriptNames.length; j++) {
+				name = scriptNames[j];
+				if (script.indexOf(name) > -1) {
+					path = script.substring(0, script.indexOf(name));
+					break;
+				}
+			}
+			if (path !== '') {
+				break;
+			}
+		}
+		return path;
+	},
+	secondsToTimeCode: function(seconds,forceHours) {
+		seconds = Math.round(seconds);
+		var hours,
+		    minutes = Math.floor(seconds / 60);
+		if (minutes >= 60) {
+		    hours = Math.floor(minutes / 60);
+		    minutes = minutes % 60;
+		}
+		hours = hours === undefined ? "00" : (hours >= 10) ? hours : "0" + hours;
+		minutes = (minutes >= 10) ? minutes : "0" + minutes;
+		seconds = Math.floor(seconds % 60);
+		seconds = (seconds >= 10) ? seconds : "0" + seconds;
+		return ((hours > 0 || forceHours === true) ? hours + ":" :'') + minutes + ":" + seconds;
+	},
+	timeCodeToSeconds: function(timecode){
+		var tab = timecode.split(':');
+		return tab[0]*60*60 + tab[1]*60 + parseFloat(tab[2].replace(',','.'));
+	}
+};
+
+
+// Core detector, plugins are added below
+mejs.PluginDetector = {
+
+	// main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]);
+	hasPluginVersion: function(plugin, v) {
+		var pv = this.plugins[plugin];
+		v[1] = v[1] || 0;
+		v[2] = v[2] || 0;
+		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;
+	},
+
+	// cached values
+	nav: window.navigator,
+	ua: window.navigator.userAgent.toLowerCase(),
+
+	// stored version numbers
+	plugins: [],
+
+	// runs detectPlugin() and stores the version number
+	addPlugin: function(p, pluginName, mimeType, activeX, axDetect) {
+		this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect);
+	},
+
+	// get the version number from the mimetype (all but IE) or ActiveX (IE)
+	detectPlugin: function(pluginName, mimeType, activeX, axDetect) {
+
+		var version = [0,0,0],
+			description,
+			i,
+			ax;
+
+		// Firefox, Webkit, Opera
+		if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') {
+			description = this.nav.plugins[pluginName].description;
+			if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) {
+				version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.');
+				for (i=0; i<version.length; i++) {
+					version[i] = parseInt(version[i].match(/\d+/), 10);
+				}
+			}
+		// Internet Explorer / ActiveX
+		} else if (typeof(window.ActiveXObject) != 'undefined') {
+			try {
+				ax = new ActiveXObject(activeX);
+				if (ax) {
+					version = axDetect(ax);
+				}
+			}
+			catch (e) { }
+		}
+		return version;
+	}
+};
+
+// Add Flash detection
+mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) {
+	// adapted from SWFObject
+	var version = [],
+		d = ax.GetVariable("$version");
+	if (d) {
+		d = d.split(" ")[1].split(",");
+		version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+	}
+	return version;
+});
+
+// Add Silverlight detection
+mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) {
+	// Silverlight cannot report its version number to IE
+	// but it does have a isVersionSupported function, so we have to loop through it to get a version number.
+	// adapted from http://www.silverlightversion.com/
+	var v = [0,0,0,0],
+		loopMatch = function(ax, v, i, n) {
+			while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){
+				v[i]+=n;
+			}
+			v[i] -= n;
+		};
+	loopMatch(ax, v, 0, 1);
+	loopMatch(ax, v, 1, 1);
+	loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx)
+	loopMatch(ax, v, 2, 1000);
+	loopMatch(ax, v, 2, 100);
+	loopMatch(ax, v, 2, 10);
+	loopMatch(ax, v, 2, 1);
+	loopMatch(ax, v, 3, 1);
+
+	return v;
+});
+// add adobe acrobat
+/*
+PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) {
+	var version = [],
+		d = ax.GetVersions().split(',')[0].split('=')[1].split('.');
+
+	if (d) {
+		version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+	}
+	return version;
+});
+*/
+// necessary detection (fixes for <IE9)
+mejs.MediaFeatures = {
+	init: function() {
+		var
+			nav = mejs.PluginDetector.nav,
+			ua = mejs.PluginDetector.ua.toLowerCase(),
+			i,
+			v,
+			html5Elements = ['source','track','audio','video'];
+
+		// detect browsers (only the ones that have some kind of quirk we need to work around)
+		this.isiPad = (ua.match(/ipad/i) !== null);
+		this.isiPhone = (ua.match(/iphone/i) !== null);
+		this.isAndroid = (ua.match(/android/i) !== null);
+		this.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null);
+		this.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1);
+		this.isChrome = (ua.match(/chrome/gi) !== null);
+		this.isFirefox = (ua.match(/firefox/gi) !== null);
+
+		// create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection
+		for (i=0; i<html5Elements.length; i++) {
+			v = document.createElement(html5Elements[i]);
+		}
+
+		// detect native JavaScript fullscreen (Safari only, Chrome fails)
+		this.hasNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined');
+		if (this.isChrome) {
+			this.hasNativeFullScreen = false;
+		}
+		// OS X 10.5 can't do this even if it says it can :(
+		if (this.hasNativeFullScreen && ua.match(/mac os x 10_5/i)) {
+			this.hasNativeFullScreen = false;
+		}
+	}
+};
+mejs.MediaFeatures.init();
+
+
+/*
+extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below)
+*/
+mejs.HtmlMediaElement = {
+	pluginType: 'native',
+	isFullScreen: false,
+
+	setCurrentTime: function (time) {
+		this.currentTime = time;
+	},
+
+	setMuted: function (muted) {
+		this.muted = muted;
+	},
+
+	setVolume: function (volume) {
+		this.volume = volume;
+	},
+
+	// for parity with the plugin versions
+	stop: function () {
+		this.pause();
+	},
+
+	// This can be a url string
+	// or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
+	setSrc: function (url) {
+		if (typeof url == 'string') {
+			this.src = url;
+		} else {
+			var i, media;
+
+			for (i=0; i<url.length; i++) {
+				media = url[i];
+				if (this.canPlayType(media.type)) {
+					this.src = media.src;
+				}
+			}
+		}
+	},
+
+	setVideoSize: function (width, height) {
+		this.width = width;
+		this.height = height;
+	}
+};
+
+/*
+Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember]
+*/
+mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) {
+	this.id = pluginid;
+	this.pluginType = pluginType;
+	this.src = mediaUrl;
+	this.events = {};
+};
+
+// JavaScript values and ExternalInterface methods that match HTML5 video properties methods
+// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
+mejs.PluginMediaElement.prototype = {
+
+	// special
+	pluginElement: null,
+	pluginType: '',
+	isFullScreen: false,
+
+	// not implemented :(
+	playbackRate: -1,
+	defaultPlaybackRate: -1,
+	seekable: [],
+	played: [],
+
+	// HTML5 read-only properties
+	paused: true,
+	ended: false,
+	seeking: false,
+	duration: 0,
+	error: null,
+
+	// HTML5 get/set properties, but only set (updated by event handlers)
+	muted: false,
+	volume: 1,
+	currentTime: 0,
+
+	// HTML5 methods
+	play: function () {
+		if (this.pluginApi != null) {
+			this.pluginApi.playMedia();
+			this.paused = false;
+		}
+	},
+	load: function () {
+		if (this.pluginApi != null) {
+			this.pluginApi.loadMedia();
+			this.paused = false;
+		}
+	},
+	pause: function () {
+		if (this.pluginApi != null) {
+			this.pluginApi.pauseMedia();
+			this.paused = true;
+		}
+	},
+	stop: function () {
+		if (this.pluginApi != null) {
+			this.pluginApi.stopMedia();
+			this.paused = true;
+		}
+	},
+	canPlayType: function(type) {
+		var i,
+			j,
+			pluginInfo,
+			pluginVersions = mejs.plugins[this.pluginType];
+
+		for (i=0; i<pluginVersions.length; i++) {
+			pluginInfo = pluginVersions[i];
+
+			// test if user has the correct plugin version
+			if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) {
+
+				// test for plugin playback types
+				for (j=0; j<pluginInfo.types.length; j++) {
+					// find plugin that can play the type
+					if (type == pluginInfo.types[j]) {
+						return true;
+					}
+				}
+			}
+		}
+
+		return false;
+	},
+
+	// custom methods since not all JavaScript implementations support get/set
+
+	// This can be a url string
+	// or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
+	setSrc: function (url) {
+		if (typeof url == 'string') {
+			this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url));
+			this.src = mejs.Utility.absolutizeUrl(url);
+		} else {
+			var i, media;
+
+			for (i=0; i<url.length; i++) {
+				media = url[i];
+				if (this.canPlayType(media.type)) {
+					this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src));
+					this.src = mejs.Utility.absolutizeUrl(url);
+				}
+			}
+		}
+
+	},
+	setCurrentTime: function (time) {
+		if (this.pluginApi != null) {
+			this.pluginApi.setCurrentTime(time);
+			this.currentTime = time;
+		}
+	},
+	setVolume: function (volume) {
+		if (this.pluginApi != null) {
+			this.pluginApi.setVolume(volume);
+			this.volume = volume;
+		}
+	},
+	setMuted: function (muted) {
+		if (this.pluginApi != null) {
+			this.pluginApi.setMuted(muted);
+			this.muted = muted;
+		}
+	},
+
+	// additional non-HTML5 methods
+	setVideoSize: function (width, height) {
+		if ( this.pluginElement.style) {
+			this.pluginElement.style.width = width + 'px';
+			this.pluginElement.style.height = height + 'px';
+		}
+		if (this.pluginApi != null) {
+			this.pluginApi.setVideoSize(width, height);
+		}
+	},
+
+	setFullscreen: function (fullscreen) {
+		if (this.pluginApi != null) {
+			this.pluginApi.setFullscreen(fullscreen);
+		}
+	},
+
+	// start: fake events
+	addEventListener: function (eventName, callback, bubble) {
+		this.events[eventName] = this.events[eventName] || [];
+		this.events[eventName].push(callback);
+	},
+	removeEventListener: function (eventName, callback) {
+		if (!eventName) { this.events = {}; return true; }
+		var callbacks = this.events[eventName];
+		if (!callbacks) return true;
+		if (!callback) { this.events[eventName] = []; return true; }
+		for (i = 0; i < callbacks.length; i++) {
+			if (callbacks[i] === callback) {
+				this.events[eventName].splice(i, 1);
+				return true;
+			}
+		}
+		return false;
+	},	
+	dispatchEvent: function (eventName) {
+		var i,
+			args,
+			callbacks = this.events[eventName];
+
+		if (callbacks) {
+			args = Array.prototype.slice.call(arguments, 1);
+			for (i = 0; i < callbacks.length; i++) {
+				callbacks[i].apply(null, args);
+			}
+		}
+	}
+	// end: fake events
+};
+
+
+// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties
+mejs.MediaPluginBridge = {
+
+	pluginMediaElements:{},
+	htmlMediaElements:{},
+
+	registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) {
+		this.pluginMediaElements[id] = pluginMediaElement;
+		this.htmlMediaElements[id] = htmlMediaElement;
+	},
+
+	// when Flash/Silverlight is ready, it calls out to this method
+	initPlugin: function (id) {
+
+		var pluginMediaElement = this.pluginMediaElements[id],
+			htmlMediaElement = this.htmlMediaElements[id];
+
+		// find the javascript bridge
+		switch (pluginMediaElement.pluginType) {
+			case "flash":
+				pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id);
+				break;
+			case "silverlight":
+				pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
+				pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS;
+				break;
+		}
+
+		if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) {
+			pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
+		}
+	},
+
+	// receives events from Flash/Silverlight and sends them out as HTML5 media events
+	// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
+	fireEvent: function (id, eventName, values) {
+
+		var
+			e,
+			i,
+			bufferedTime,
+			pluginMediaElement = this.pluginMediaElements[id];
+
+		pluginMediaElement.ended = false;
+		pluginMediaElement.paused = true;
+
+		// fake event object to mimic real HTML media event.
+		e = {
+			type: eventName,
+			target: pluginMediaElement
+		};
+
+		// attach all values to element and event object
+		for (i in values) {
+			pluginMediaElement[i] = values[i];
+			e[i] = values[i];
+		}
+
+		// fake the newer W3C buffered TimeRange (loaded and total have been removed)
+		bufferedTime = values.bufferedTime || 0;
+
+		e.target.buffered = e.buffered = {
+			start: function(index) {
+				return 0;
+			},
+			end: function (index) {
+				return bufferedTime;
+			},
+			length: 1
+		};
+
+		pluginMediaElement.dispatchEvent(e.type, e);
+	}
+};
+
+/*
+Default options
+*/
+mejs.MediaElementDefaults = {
+	// allows testing on HTML5, flash, silverlight
+	// auto: attempts to detect what the browser can do
+	// native: forces HTML5 playback
+	// shim: disallows HTML5, will attempt either Flash or Silverlight
+	// none: forces fallback view
+	mode: 'auto',
+	// remove or reorder to change plugin priority and availability
+	plugins: ['flash','silverlight'],
+	// shows debug errors on screen
+	enablePluginDebug: false,
+	// overrides the type specified, useful for dynamic instantiation
+	type: '',
+	// path to Flash and Silverlight plugins
+	pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
+	// name of flash file
+	flashName: 'flashmediaelement.swf',
+	// turns on the smoothing filter in Flash
+	enablePluginSmoothing: false,
+	// name of silverlight file
+	silverlightName: 'silverlightmediaelement.xap',
+	// default if the <video width> is not specified
+	defaultVideoWidth: 480,
+	// default if the <video height> is not specified
+	defaultVideoHeight: 270,
+	// overrides <video width>
+	pluginWidth: -1,
+	// overrides <video height>
+	pluginHeight: -1,
+	// rate in milliseconds for Flash and Silverlight to fire the timeupdate event
+	// larger number is less accurate, but less strain on plugin->JavaScript bridge
+	timerRate: 250,
+	success: function () { },
+	error: function () { }
+};
+
+/*
+Determines if a browser supports the <video> or <audio> element
+and returns either the native element or a Flash/Silverlight version that
+mimics HTML5 MediaElement
+*/
+mejs.MediaElement = function (el, o) {
+	return mejs.HtmlMediaElementShim.create(el,o);
+};
+
+mejs.HtmlMediaElementShim = {
+
+	create: function(el, o) {
+		var
+			options = mejs.MediaElementDefaults,
+			htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
+			isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'),
+			supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'),
+			playback = {method:'', url:''},
+			poster = htmlMediaElement.getAttribute('poster'),
+			autoplay =  htmlMediaElement.getAttribute('autoplay'),
+			preload =  htmlMediaElement.getAttribute('preload'),
+			controls =  htmlMediaElement.getAttribute('controls'),
+			prop;
+
+		// extend options
+		for (prop in o) {
+			options[prop] = o[prop];
+		}
+
+		// check for real poster
+		poster = (typeof poster == 'undefined' || poster === null) ? '' : poster;
+		preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload;
+		autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false');
+		controls = !(typeof controls == 'undefined' || controls === null || controls === 'false');
+
+		// test for HTML5 and plugin capabilities
+		playback = this.determinePlayback(htmlMediaElement, options, isVideo, supportsMediaTag);
+
+		if (playback.method == 'native') {
+			// second fix for android
+			if (mejs.MediaFeatures.isBustedAndroid) {
+				htmlMediaElement.src = playback.url;
+				htmlMediaElement.addEventListener('click', function() {
+						htmlMediaElement.play();
+				}, true);
+			}
+		
+			// add methods to native HTMLMediaElement
+			return this.updateNative( htmlMediaElement, options, autoplay, preload, playback);
+		} else if (playback.method !== '') {
+			// create plugin to mimic HTMLMediaElement
+			return this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls);
+		} else {
+			// boo, no HTML5, no Flash, no Silverlight.
+			this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster );
+		}
+	},
+
+	determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) {
+		var
+			mediaFiles = [],
+			i,
+			j,
+			k,
+			l,
+			n,
+			type,
+			result = { method: '', url: ''},
+			src = htmlMediaElement.getAttribute('src'),
+			pluginName,
+			pluginVersions,
+			pluginInfo;
+			
+		// clean up src attr
+		if (src == 'undefined' || src == '' || src === null) 
+			src = null;
+			
+		// STEP 1: Get URL and type from <video src> or <source src>
+
+		// supplied type overrides all HTML
+		if (typeof (options.type) != 'undefined' && options.type !== '') {
+			mediaFiles.push({type:options.type, url:src});
+
+		// test for src attribute first
+		} else if (src  !== null) {
+			type = this.checkType(src, htmlMediaElement.getAttribute('type'), isVideo);
+			mediaFiles.push({type:type, url:src});
+
+		// then test for <source> elements
+		} else {
+			// test <source> types to see if they are usable
+			for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
+				n = htmlMediaElement.childNodes[i];
+				if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
+					src = n.getAttribute('src');
+					type = this.checkType(src, n.getAttribute('type'), isVideo);
+					mediaFiles.push({type:type, url:src});
+				}
+			}
+		}
+
+		// STEP 2: Test for playback method
+		
+		// special case for Android which sadly doesn't implement the canPlayType function (always returns '')
+		if (mejs.MediaFeatures.isBustedAndroid) {
+			htmlMediaElement.canPlayType = function(type) {
+				return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : '';
+			};
+		}		
+		
+
+		// test for native playback first
+		if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) {
+			for (i=0; i<mediaFiles.length; i++) {
+				// normal check
+				if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' 
+					// special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg')
+					|| htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') {
+					result.method = 'native';
+					result.url = mediaFiles[i].url;
+					return result;
+				}
+			}
+		}
+
+		// if native playback didn't work, then test plugins
+		if (options.mode === 'auto' || options.mode === 'shim') {
+			for (i=0; i<mediaFiles.length; i++) {
+				type = mediaFiles[i].type;
+
+				// test all plugins in order of preference [silverlight, flash]
+				for (j=0; j<options.plugins.length; j++) {
+
+					pluginName = options.plugins[j];
+
+					// test version of plugin (for future features)
+					pluginVersions = mejs.plugins[pluginName];
+					for (k=0; k<pluginVersions.length; k++) {
+						pluginInfo = pluginVersions[k];
+
+						// test if user has the correct plugin version
+						if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {
+
+							// test for plugin playback types
+							for (l=0; l<pluginInfo.types.length; l++) {
+								// find plugin that can play the type
+								if (type == pluginInfo.types[l]) {
+									result.method = pluginName;
+									result.url = mediaFiles[i].url;
+									return result;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		
+		// what if there's nothing to play? just grab the first available
+		if (result.method === '') {
+			result.url = mediaFiles[0].url;
+		}
+
+		return result;
+	},
+
+	checkType: function(url, type, isVideo) {
+		var ext;
+
+		// if no type is supplied, fake it with the extension
+		if (url && !type) {
+			ext = url.substring(url.lastIndexOf('.') + 1);
+			return ((isVideo) ? 'video' : 'audio') + '/' + ext;
+		} else {
+			// only return the mime part of the type in case the attribute contains the codec
+			// see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element
+			// `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4`
+			
+			if (type && ~type.indexOf(';')) {
+				return type.substr(0, type.indexOf(';')); 
+			} else {
+				return type;
+			}
+		}
+	},
+
+	createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) {
+		var errorContainer = document.createElement('div');
+		errorContainer.className = 'me-cannotplay';
+
+		try {
+			errorContainer.style.width = htmlMediaElement.width + 'px';
+			errorContainer.style.height = htmlMediaElement.height + 'px';
+		} catch (e) {}
+
+		errorContainer.innerHTML = (poster !== '') ?
+			'<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' :
+			'<a href="' + downloadUrl + '"><span>Download File</span></a>';
+
+		htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
+		htmlMediaElement.style.display = 'none';
+
+		options.error(htmlMediaElement);
+	},
+
+	createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) {
+		var width = 1,
+			height = 1,
+			pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++),
+			pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl),
+			container = document.createElement('div'),
+			specialIEContainer,
+			node,
+			initVars;
+
+		// check for placement inside a <p> tag (sometimes WYSIWYG editors do this)
+		node = htmlMediaElement.parentNode;
+		while (node !== null && node.tagName.toLowerCase() != 'body') {
+			if (node.parentNode.tagName.toLowerCase() == 'p') {
+				node.parentNode.parentNode.insertBefore(node, node.parentNode);
+				break;
+			}
+			node = node.parentNode;
+		}
+
+		if (isVideo) {
+			width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
+			height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
+		} else {
+			if (options.enablePluginDebug) {
+				width = 320;
+				height = 240;
+			}
+		}
+
+		// register plugin
+		pluginMediaElement.success = options.success;
+		mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement);
+
+		// add container (must be added to DOM before inserting HTML for IE)
+		container.className = 'me-plugin';
+		htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);
+
+		// flash/silverlight vars
+		initVars = [
+			'id=' + pluginid,
+			'isvideo=' + ((isVideo) ? "true" : "false"),
+			'autoplay=' + ((autoplay) ? "true" : "false"),
+			'preload=' + preload,
+			'width=' + width,
+			'startvolume=' + options.startVolume,
+			'timerrate=' + options.timerRate,
+			'height=' + height];
+
+		if (mediaUrl !== null) {
+			if (pluginType == 'flash') {
+				initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl));
+			} else {
+				initVars.push('file=' + mediaUrl);
+			}
+		}
+		if (options.enablePluginDebug) {
+			initVars.push('debug=true');
+		}
+		if (options.enablePluginSmoothing) {
+			initVars.push('smoothing=true');
+		}
+		if (controls) {
+			initVars.push('controls=true'); // shows controls in the plugin if desired
+		}
+
+		switch (pluginType) {
+			case 'silverlight':
+				container.innerHTML =
+'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' +
+'<param name="initParams" value="' + initVars.join(',') + '" />' +
+'<param name="windowless" value="true" />' +
+'<param name="background" value="black" />' +
+'<param name="minRuntimeVersion" value="3.0.0.0" />' +
+'<param name="autoUpgrade" value="true" />' +
+'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
+'</object>';
+					break;
+
+			case 'flash':
+
+				if (mejs.MediaFeatures.isIE) {
+					specialIEContainer = document.createElement('div');
+					container.appendChild(specialIEContainer);
+					specialIEContainer.outerHTML =
+'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
+'id="' + pluginid + '" width="' + width + '" height="' + height + '">' +
+'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
+'<param name="flashvars" value="' + initVars.join('&amp;') + '" />' +
+'<param name="quality" value="high" />' +
+'<param name="bgcolor" value="#000000" />' +
+'<param name="wmode" value="transparent" />' +
+'<param name="allowScriptAccess" value="always" />' +
+'<param name="allowFullScreen" value="true" />' +
+'</object>';
+
+				} else {
+
+					container.innerHTML =
+'<embed id="' + pluginid + '" name="' + pluginid + '" ' +
+'play="true" ' +
+'loop="false" ' +
+'quality="high" ' +
+'bgcolor="#000000" ' +
+'wmode="transparent" ' +
+'allowScriptAccess="always" ' +
+'allowFullScreen="true" ' +
+'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
+'src="' + options.pluginPath + options.flashName + '" ' +
+'flashvars="' + initVars.join('&') + '" ' +
+'width="' + width + '" ' +
+'height="' + height + '"></embed>';
+				}
+				break;
+		}
+		// hide original element
+		htmlMediaElement.style.display = 'none';
+
+		// FYI: options.success will be fired by the MediaPluginBridge
+		
+		return pluginMediaElement;
+	},
+
+	updateNative: function(htmlMediaElement, options, autoplay, preload, playback) {
+		// add methods to video object to bring it into parity with Flash Object
+		for (var m in mejs.HtmlMediaElement) {
+			htmlMediaElement[m] = mejs.HtmlMediaElement[m];
+		}
+
+		/*
+		Chrome now supports preload="none"
+		if (mejs.MediaFeatures.isChrome) {
+		
+			// special case to enforce preload attribute (Chrome doesn't respect this)
+			if (preload === 'none' && !autoplay) {
+			
+				// forces the browser to stop loading (note: fails in IE9)
+				htmlMediaElement.src = '';
+				htmlMediaElement.load();
+				htmlMediaElement.canceledPreload = true;
+
+				htmlMediaElement.addEventListener('play',function() {
+					if (htmlMediaElement.canceledPreload) {
+						htmlMediaElement.src = playback.url;
+						htmlMediaElement.load();
+						htmlMediaElement.play();
+						htmlMediaElement.canceledPreload = false;
+					}
+				}, false);
+			// for some reason Chrome forgets how to autoplay sometimes.
+			} else if (autoplay) {
+				htmlMediaElement.load();
+				htmlMediaElement.play();
+			}
+		}
+		*/
+
+		// fire success code
+		options.success(htmlMediaElement, htmlMediaElement);
+		
+		return htmlMediaElement;
+	}
+};
+
+window.mejs = mejs;
+window.MediaElement = mejs.MediaElement;
+
+/*!
+ * MediaElementPlayer
+ * http://mediaelementjs.com/
+ *
+ * Creates a controller bar for HTML5 <video> add <audio> tags
+ * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper)
+ *
+ * Copyright 2010-2011, John Dyer (http://j.hn/)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ */
+if (typeof jQuery != 'undefined') {
+	mejs.$ = jQuery;
+} else if (typeof ender != 'undefined') {
+	mejs.$ = ender;
+}
+(function ($) {
+
+	// default player values
+	mejs.MepDefaults = {
+		// url to poster (to fix iOS 3.x)
+		poster: '',
+		// default if the <video width> is not specified
+		defaultVideoWidth: 480,
+		// default if the <video height> is not specified
+		defaultVideoHeight: 270,
+		// if set, overrides <video width>
+		videoWidth: -1,
+		// if set, overrides <video height>
+		videoHeight: -1,
+		// width of audio player
+		audioWidth: 400,
+		// height of audio player
+		audioHeight: 30,
+		// initial volume when the player starts (overrided by user cookie)
+		startVolume: 0.8,
+		// useful for <audio> player loops
+		loop: false,
+		// resize to media dimensions
+		enableAutosize: true,
+		// forces the hour marker (##:00:00)
+		alwaysShowHours: false,
+		// Hide controls when playing and mouse is not over the video
+		alwaysShowControls: false,
+		// force iPad's native controls
+		iPadUseNativeControls: true,
+		// features to show
+		features: ['playpause','current','progress','duration','tracks','volume','fullscreen']		
+	};
+
+	mejs.mepIndex = 0;
+
+	// wraps a MediaElement object in player controls
+	mejs.MediaElementPlayer = function(node, o) {
+		// enforce object, even without "new" (via John Resig)
+		if ( !(this instanceof mejs.MediaElementPlayer) ) {
+			return new mejs.MediaElementPlayer(node, o);
+		} 
+
+		var
+			t = this,
+			mf = mejs.MediaFeatures;
+			
+		// create options
+		t.options = $.extend({},mejs.MepDefaults,o);		
+		
+		// these will be reset after the MediaElement.success fires
+		t.$media = t.$node = $(node);
+		t.node = t.media = t.$media[0];		
+		
+		// check for existing player
+		if (typeof t.node.player != 'undefined') {
+			return t.node.player;
+		} else {
+			// attach player to DOM node for reference
+			t.node.player = t;
+		}
+		
+		t.isVideo = (t.media.tagName.toLowerCase() === 'video');
+				
+		/* FUTURE WORK = create player without existing <video> or <audio> node
+		
+		// if not a video or audio tag, then we'll dynamically create it
+		if (tagName == 'video' || tagName == 'audio') {
+			t.$media = $($node);
+		} else if (o.tagName !== '' && o.src !== '') {
+			// create a new node
+			if (o.mode == 'auto' || o.mode == 'native') {
+				
+				$media = $(o.tagName);
+				if (typeof o.src == 'string') {
+					$media.attr('src',o.src);
+				} else if (typeof o.src == 'object') {
+					// create source nodes
+					for (var x in o.src) {
+						$media.append($('<source src="' + o.src[x].src + '" type="' + o.src[x].type + '" />'));
+					}
+				}
+				if (o.type != '') {
+					$media.attr('type',o.type);
+				}
+				if (o.poster != '') {
+					$media.attr('poster',o.poster);
+				}
+				if (o.videoWidth > 0) {
+					$media.attr('width',o.videoWidth);
+				}
+				if (o.videoHeight > 0) {
+					$media.attr('height',o.videoHeight);
+				}
+				
+				$node.clear();
+				$node.append($media);
+				t.$media = $media;
+			} else if (o.mode == 'shim') {
+				$media = $();
+				// doesn't want a media node
+				// let MediaElement object handle this
+			}
+		} else {
+			// fail?
+			return;
+		}	
+		*/
+		
+		t.init();
+
+		return t;
+	};
+
+	// actual player
+	mejs.MediaElementPlayer.prototype = {
+		init: function() {
+
+			var
+				t = this,
+				mf = mejs.MediaFeatures,
+				// options for MediaElement (shim)
+				meOptions = $.extend(true, {}, t.options, {
+					success: function(media, domNode) { t.meReady(media, domNode); },
+					error: function(e) { t.handleError(e);}
+				});
+		
+		
+			// use native controls in iPad, iPhone, and Android	
+			if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) {
+				// add controls and stop
+				t.$media.attr('controls', 'controls');
+
+				// attempt to fix iOS 3 bug
+				t.$media.removeAttr('poster');
+
+				// override Apple's autoplay override for iPads
+				if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
+					t.media.load();
+					t.media.play();
+				}
+					
+			} else if (mf.isAndroid && t.isVideo) {
+				
+				// leave default player
+
+			} else {
+
+				// DESKTOP: use MediaElementPlayer controls
+				
+				// remove native controls 			
+				t.$media.removeAttr('controls');					
+				
+				// unique ID
+				t.id = 'mep_' + mejs.mepIndex++;
+
+				// build container
+				t.container =
+					$('<div id="' + t.id + '" class="mejs-container">'+
+						'<div class="mejs-inner">'+
+							'<div class="mejs-mediaelement"></div>'+
+							'<div class="mejs-layers"></div>'+
+							'<div class="mejs-controls"></div>'+
+							'<div class="mejs-clear"></div>'+
+						'</div>' +
+					'</div>')
+					.addClass(t.$media[0].className)
+					.insertBefore(t.$media);
+
+				// move the <video/video> tag into the right spot
+				t.container.find('.mejs-mediaelement').append(t.$media);
+
+				// find parts
+				t.controls = t.container.find('.mejs-controls');
+				t.layers = t.container.find('.mejs-layers');
+
+				// determine the size
+				if (t.isVideo) {
+					// priority = videoWidth (forced), width attribute, defaultVideoWidth
+					t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth;
+					t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight;
+				} else {
+					t.width = t.options.audioWidth;
+					t.height = t.options.audioHeight;
+				}
+
+				// set the size, while we wait for the plugins to load below
+				t.setPlayerSize(t.width, t.height);
+				
+				// create MediaElementShim
+				meOptions.pluginWidth = t.height;
+				meOptions.pluginHeight = t.width;				
+			}
+
+			// create MediaElement shim
+			mejs.MediaElement(t.$media[0], meOptions);
+		},
+
+		// Sets up all controls and events
+		meReady: function(media, domNode) {			
+		
+		
+			var t = this,
+				mf = mejs.MediaFeatures,
+				f,
+				feature;
+
+			// make sure it can't create itself again if a plugin reloads
+			if (t.created)
+				return;
+			else
+				t.created = true;			
+
+			t.media = media;
+			t.domNode = domNode;
+			
+			if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) {				
+				
+				// two built in features
+				t.buildposter(t, t.controls, t.layers, t.media);
+				t.buildoverlays(t, t.controls, t.layers, t.media);
+
+				// grab for use by features
+				t.findTracks();
+
+				// add user-defined features/controls
+				for (f in t.options.features) {
+					feature = t.options.features[f];
+					if (t['build' + feature]) {
+						try {
+							t['build' + feature](t, t.controls, t.layers, t.media);
+						} catch (e) {
+							// TODO: report control error
+							//throw e;
+						}
+					}
+				}
+
+				t.container.trigger('controlsready');
+				
+				// reset all layers and controls
+				t.setPlayerSize(t.width, t.height);
+				t.setControlsSize();
+				
+
+				// controls fade
+				if (t.isVideo) {
+					// show/hide controls
+					t.container
+						.bind('mouseenter', function () {
+							if (!t.options.alwaysShowControls) {
+								t.controls.css('visibility','visible');
+								t.controls.stop(true, true).fadeIn(200);
+							}
+						})
+						.bind('mouseleave', function () {
+							if (!t.media.paused && !t.options.alwaysShowControls) {
+								t.controls.stop(true, true).fadeOut(200, function() {
+									$(this).css('visibility','hidden');
+									$(this).css('display','block');
+								});
+							}
+						});
+						
+					// check for autoplay
+					if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) {
+						t.controls.css('visibility','hidden');
+					}
+
+					// resizer
+					if (t.options.enableAutosize) {
+						t.media.addEventListener('loadedmetadata', function(e) {
+							// if the <video height> was not set and the options.videoHeight was not set
+							// then resize to the real dimensions
+							if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
+								t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
+								t.setControlsSize();
+								t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
+							}
+						}, false);
+					}
+				}
+
+				// ended for all
+				t.media.addEventListener('ended', function (e) {
+					t.media.setCurrentTime(0);
+					t.media.pause();
+					
+					if (t.setProgressRail)
+						t.setProgressRail();
+					if (t.setCurrentRail)
+						t.setCurrentRail();						
+
+					if (t.options.loop) {
+						t.media.play();
+					} else if (!t.options.alwaysShowControls) {
+						t.controls.css('visibility','visible');
+					}
+				}, true);
+				
+				// resize on the first play
+				t.media.addEventListener('loadedmetadata', function(e) {
+					if (t.updateDuration) {
+						t.updateDuration();
+					}
+					if (t.updateCurrent) {
+						t.updateCurrent();
+					}
+					
+					t.setControlsSize();
+				}, true);
+
+
+				// webkit has trouble doing this without a delay
+				setTimeout(function () {
+					t.setControlsSize();
+					t.setPlayerSize(t.width, t.height);
+				}, 50);
+				
+			}
+
+
+			if (t.options.success) {
+				t.options.success(t.media, t.domNode);
+			}
+		},
+
+		handleError: function(e) {
+			// Tell user that the file cannot be played
+			if (this.options.error) {
+				this.options.error(e);
+			}
+		},
+
+		setPlayerSize: function(width,height) {
+			var t = this;
+
+			// ie9 appears to need this (jQuery bug?)
+			t.width = parseInt(width, 10);
+			t.height = parseInt(height, 10);
+
+			t.container
+				.width(t.width)
+				.height(t.height);
+
+			t.layers.children('.mejs-layer')
+				.width(t.width)
+				.height(t.height);
+		},
+
+		setControlsSize: function() {
+			var t = this,
+				usedWidth = 0,
+				railWidth = 0,
+				rail = t.controls.find('.mejs-time-rail'),
+				total = t.controls.find('.mejs-time-total'),
+				current = t.controls.find('.mejs-time-current'),
+				loaded = t.controls.find('.mejs-time-loaded');
+				others = rail.siblings();
+
+			// find the size of all the other controls besides the rail
+			others.each(function() {
+				if ($(this).css('position') != 'absolute') {
+					usedWidth += $(this).outerWidth(true);
+				}
+			});
+			// fit the rail into the remaining space
+			railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false));
+
+			// outer area
+			rail.width(railWidth);
+			// dark space
+			total.width(railWidth - (total.outerWidth(true) - total.width()));
+			
+			if (t.setProgressRail)
+				t.setProgressRail();
+			if (t.setCurrentRail)
+				t.setCurrentRail();				
+		},
+
+
+		buildposter: function(player, controls, layers, media) {
+			var poster = 
+				$('<div class="mejs-poster mejs-layer">'+
+					'<img />'+
+				'</div>')
+					.appendTo(layers),
+				posterUrl = player.$media.attr('poster'),
+				posterImg = poster.find('img').width(player.width).height(player.height);
+
+			// prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
+			if (player.options.poster != '') {
+				posterImg.attr('src',player.options.poster);
+			// second, try the real poster
+			} else if (posterUrl !== '' && posterUrl != null) {
+				posterImg.attr('src',posterUrl);
+			} else {
+				poster.remove();
+			}
+
+			media.addEventListener('play',function() {
+				poster.hide();
+			}, false);
+		},
+
+		buildoverlays: function(player, controls, layers, media) {
+			if (!player.isVideo)
+				return;
+
+			var 
+			loading = 
+				$('<div class="mejs-overlay mejs-layer">'+
+					'<div class="mejs-overlay-loading"><span></span></div>'+
+				'</div>')
+				.hide() // start out hidden
+				.appendTo(layers),
+			error = 
+				$('<div class="mejs-overlay mejs-layer">'+
+					'<div class="mejs-overlay-error"></div>'+
+				'</div>')
+				.hide() // start out hidden
+				.appendTo(layers),				
+				
+			// this needs to come last so it's on top
+			bigPlay = 
+				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
+					'<div class="mejs-overlay-button"></div>'+
+				'</div>')
+				.appendTo(layers)
+				.click(function() {
+					if (media.paused) {
+						media.play();
+					} else {
+						media.pause();
+					}
+				});
+	
+
+			// show/hide big play button
+			media.addEventListener('play',function() {
+				bigPlay.hide();
+				error.hide();
+			}, false);
+			media.addEventListener('pause',function() {
+				bigPlay.show();
+			}, false);
+			
+			// show/hide loading			
+			media.addEventListener('loadstart',function() {
+				// for some reason Chrome is firing this event
+				if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
+					return;
+					
+				loading.show();
+			}, false);	
+			media.addEventListener('canplay',function() {
+				loading.hide();
+			}, false);	
+
+			// error handling
+			media.addEventListener('error',function() {
+				loading.hide();
+				error.show();
+				error.find('mejs-overlay-error').html("Error loading this resource");
+			}, false);				
+		},
+
+		findTracks: function() {
+			var t = this,
+				tracktags = t.$media.find('track');
+
+			// store for use by plugins
+			t.tracks = [];
+			tracktags.each(function() {
+				t.tracks.push({
+					srclang: $(this).attr('srclang').toLowerCase(),
+					src: $(this).attr('src'),
+					kind: $(this).attr('kind'),
+					entries: [],
+					isLoaded: false
+				});
+			});
+		},
+		changeSkin: function(className) {
+			this.container[0].className = 'mejs-container ' + className;
+			this.setPlayerSize();
+			this.setControlsSize();
+		},
+		play: function() {
+			this.media.play();
+		},
+		pause: function() {
+			this.media.pause();
+		},
+		load: function() {
+			this.media.load();
+		},
+		setMuted: function(muted) {
+			this.media.setMuted(muted);
+		},
+		setCurrentTime: function(time) {
+			this.media.setCurrentTime(time);
+		},
+		getCurrentTime: function() {
+			return this.media.currentTime;
+		},
+		setVolume: function(volume) {
+			this.media.setVolume(volume);
+		},
+		getVolume: function() {
+			return this.media.volume;
+		},
+		setSrc: function(src) {
+			this.media.setSrc(src);
+		}
+	};
+
+	// turn into jQuery plugin
+	if (typeof jQuery != 'undefined') {
+		jQuery.fn.mediaelementplayer = function (options) {
+			return this.each(function () {
+				new mejs.MediaElementPlayer(this, options);
+			});
+		};
+	}
+	
+	// push out to window
+	window.MediaElementPlayer = mejs.MediaElementPlayer;
+
+})(mejs.$);
+(function($) {
+	// PLAY/pause BUTTON
+	MediaElementPlayer.prototype.buildplaypause = function(player, controls, layers, media) {
+		var play = 
+			$('<div class="mejs-button mejs-playpause-button mejs-play" type="button">' +
+				'<button type="button"></button>' +
+			'</div>')
+			.appendTo(controls)
+			.click(function(e) {
+				e.preventDefault();
+			
+				if (media.paused) {
+					media.play();
+				} else {
+					media.pause();
+				}
+				
+				return false;
+			});
+
+		media.addEventListener('play',function() {
+			play.removeClass('mejs-play').addClass('mejs-pause');
+		}, false);
+		media.addEventListener('playing',function() {
+			play.removeClass('mejs-play').addClass('mejs-pause');
+		}, false);
+
+
+		media.addEventListener('pause',function() {
+			play.removeClass('mejs-pause').addClass('mejs-play');
+		}, false);
+		media.addEventListener('paused',function() {
+			play.removeClass('mejs-pause').addClass('mejs-play');
+		}, false);
+	}
+	
+})(mejs.$);
+(function($) {
+	// STOP BUTTON
+	MediaElementPlayer.prototype.buildstop = function(player, controls, layers, media) {
+		var stop = 
+			$('<div class="mejs-button mejs-stop-button mejs-stop">' +
+				'<button type="button"></button>' +
+			'</div>')
+			.appendTo(controls)
+			.click(function() {
+				if (!media.paused) {
+					media.pause();
+				}
+				if (media.currentTime > 0) {
+					media.setCurrentTime(0);	
+					controls.find('.mejs-time-current').width('0px');
+					controls.find('.mejs-time-handle').css('left', '0px');
+					controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) );
+					controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) );					
+					layers.find('.mejs-poster').show();
+				}
+			});
+	}
+	
+})(mejs.$);
+(function($) {
+	// progress/loaded bar
+	MediaElementPlayer.prototype.buildprogress = function(player, controls, layers, media) {
+
+		$('<div class="mejs-time-rail">'+
+			'<span class="mejs-time-total">'+
+				'<span class="mejs-time-loaded"></span>'+
+				'<span class="mejs-time-current"></span>'+
+				'<span class="mejs-time-handle"></span>'+
+				'<span class="mejs-time-float">' + 
+					'<span class="mejs-time-float-current">00:00</span>' + 
+					'<span class="mejs-time-float-corner"></span>' + 
+				'</span>'+
+			'</span>'+
+		'</div>')
+			.appendTo(controls);
+
+		var 
+			t = this,
+			total = controls.find('.mejs-time-total'),
+			loaded  = controls.find('.mejs-time-loaded'),
+			current  = controls.find('.mejs-time-current'),
+			handle  = controls.find('.mejs-time-handle'),
+			timefloat  = controls.find('.mejs-time-float'),
+			timefloatcurrent  = controls.find('.mejs-time-float-current'),
+			handleMouseMove = function (e) {
+				// mouse position relative to the object
+				var x = e.pageX,
+					offset = total.offset(),
+					width = total.outerWidth(),
+					percentage = 0,
+					newTime = 0;
+
+
+				if (x > offset.left && x <= width + offset.left && media.duration) {
+					percentage = ((x - offset.left) / width);
+					newTime = (percentage <= 0.02) ? 0 : percentage * media.duration;
+
+					// seek to where the mouse is
+					if (mouseIsDown) {
+						media.setCurrentTime(newTime);
+					}
+
+					// position floating time box
+					var pos = x - offset.left;
+					timefloat.css('left', pos);
+					timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) );
+				}
+			},
+			mouseIsDown = false,
+			mouseIsOver = false;
+
+		// handle clicks
+		//controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove);
+		total
+			.bind('mousedown', function (e) {
+				mouseIsDown = true;
+				handleMouseMove(e);
+				return false;
+			});
+
+		controls.find('.mejs-time-rail')
+			.bind('mouseenter', function(e) {
+				mouseIsOver = true;
+			})
+			.bind('mouseleave',function(e) {
+				mouseIsOver = false;
+			});
+
+		$(document)
+			.bind('mouseup', function (e) {
+				mouseIsDown = false;
+				//handleMouseMove(e);
+			})
+			.bind('mousemove', function (e) {
+				if (mouseIsDown || mouseIsOver) {
+					handleMouseMove(e);
+				}
+			});
+
+		// loading
+		media.addEventListener('progress', function (e) {
+			player.setProgressRail(e);
+			player.setCurrentRail(e);
+		}, false);
+
+		// current time
+		media.addEventListener('timeupdate', function(e) {
+			player.setProgressRail(e);
+			player.setCurrentRail(e);
+		}, false);
+		
+		
+		// store for later use
+		t.loaded = loaded;
+		t.total = total;
+		t.current = current;
+		t.handle = handle;
+	}
+	MediaElementPlayer.prototype.setProgressRail = function(e) {
+
+		var
+			t = this,
+			target = (e != undefined) ? e.target : t.media,
+			percent = null;			
+
+		// newest HTML5 spec has buffered array (FF4, Webkit)
+		if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) {
+			// TODO: account for a real array with multiple values (only Firefox 4 has this so far) 
+			percent = target.buffered.end(0) / target.duration;
+		} 
+		// Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
+		// to be anything other than 0. If the byte count is available we use this instead.
+		// Browsers that support the else if do not seem to have the bufferedBytes value and
+		// should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8.
+		else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) {
+			percent = target.bufferedBytes / target.bytesTotal;
+		}
+		// Firefox 3 with an Ogg file seems to go this way
+		else if (e && e.lengthComputable && e.total != 0) {
+			percent = e.loaded/e.total;
+		}
+
+		// finally update the progress bar
+		if (percent !== null) {
+			percent = Math.min(1, Math.max(0, percent));
+			// update loaded bar
+			if (t.loaded && t.total) {
+				t.loaded.width(t.total.width() * percent);
+			}
+		}
+	}
+	MediaElementPlayer.prototype.setCurrentRail = function() {
+
+		var t = this;
+	
+		if (t.media.currentTime != undefined && t.media.duration) {
+
+			// update bar and handle
+			if (t.total && t.handle) {
+				var 
+					newWidth = t.total.width() * t.media.currentTime / t.media.duration,
+					handlePos = newWidth - (t.handle.outerWidth(true) / 2);
+
+				t.current.width(newWidth);
+				t.handle.css('left', handlePos);
+			}
+		}
+
+	}	
+
+})(mejs.$);
+(function($) {
+	// current and duration 00:00 / 00:00
+	MediaElementPlayer.prototype.buildcurrent = function(player, controls, layers, media) {
+		var t = this;
+		
+		$('<div class="mejs-time">'+
+				'<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+
+			'</div>')
+			.appendTo(controls);
+		
+		t.currenttime = t.controls.find('.mejs-currenttime');
+
+		media.addEventListener('timeupdate',function() {
+			player.updateCurrent();
+		}, false);
+	};
+
+	MediaElementPlayer.prototype.buildduration = function(player, controls, layers, media) {
+		var t = this;
+		
+		if (controls.children().last().find('.mejs-currenttime').length > 0) {
+			$(' <span> | </span> '+
+			   '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>')
+				.appendTo(controls.find('.mejs-time'));
+		} else {
+
+			// add class to current time
+			controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container');
+			
+			$('<div class="mejs-time mejs-duration-container">'+
+				'<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+
+			'</div>')
+			.appendTo(controls);
+		}
+		
+		t.durationD = t.controls.find('.mejs-duration');
+
+		media.addEventListener('timeupdate',function() {
+			player.updateDuration();
+		}, false);
+	};
+	
+	MediaElementPlayer.prototype.updateCurrent = function() {
+		var t = this;
+
+		if (t.currenttime) {
+			t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime | 0, t.options.alwaysShowHours || t.media.duration > 3600 ));
+		}
+	}
+	MediaElementPlayer.prototype.updateDuration = function() {	
+		var t = this;
+		
+		if (t.media.duration && t.durationD) {
+			t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours));
+		}		
+	};	
+
+})(mejs.$);
+(function($) {
+	MediaElementPlayer.prototype.buildvolume = function(player, controls, layers, media) {
+		var mute = 
+			$('<div class="mejs-button mejs-volume-button mejs-mute">'+
+				'<button type="button"></button>'+
+				'<div class="mejs-volume-slider">'+ // outer background
+					'<div class="mejs-volume-total"></div>'+ // line background
+					'<div class="mejs-volume-current"></div>'+ // current volume
+					'<div class="mejs-volume-handle"></div>'+ // handle
+				'</div>'+
+			'</div>')
+			.appendTo(controls),
+		volumeSlider = mute.find('.mejs-volume-slider'),
+		volumeTotal = mute.find('.mejs-volume-total'),
+		volumeCurrent = mute.find('.mejs-volume-current'),
+		volumeHandle = mute.find('.mejs-volume-handle'),
+
+		positionVolumeHandle = function(volume) {
+
+			var 
+				top = volumeTotal.height() - (volumeTotal.height() * volume);
+
+			// handle
+			volumeHandle.css('top', top - (volumeHandle.height() / 2));
+
+			// show the current visibility
+			volumeCurrent.height(volumeTotal.height() - top + parseInt(volumeTotal.css('top').replace(/px/,''),10));
+			volumeCurrent.css('top',  top);
+		},
+		handleVolumeMove = function(e) {
+			var
+				railHeight = volumeTotal.height(),
+				totalOffset = volumeTotal.offset(),
+				totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
+				newY = e.pageY - totalOffset.top,
+				volume = (railHeight - newY) / railHeight
+
+			// TODO: handle vertical and horizontal CSS
+			// only allow it to move within the rail
+			if (newY < 0)
+				newY = 0;
+			else if (newY > railHeight)
+				newY = railHeight;
+
+			// move the handle to match the mouse
+			volumeHandle.css('top', newY - (volumeHandle.height() / 2) + totalTop );
+
+			// show the current visibility
+			volumeCurrent.height(railHeight-newY);
+			volumeCurrent.css('top',newY+totalTop);
+
+			// set mute status
+			if (volume == 0) {
+				media.setMuted(true);
+				mute.removeClass('mejs-mute').addClass('mejs-unmute');
+			} else {
+				media.setMuted(false);
+				mute.removeClass('mejs-unmute').addClass('mejs-mute');
+			}
+
+			volume = Math.max(0,volume);
+			volume = Math.min(volume,1);
+
+			// set the volume
+			media.setVolume(volume);
+		},
+		mouseIsDown = false;
+
+		// SLIDER
+		volumeSlider
+			.bind('mousedown', function (e) {
+				handleVolumeMove(e);
+				mouseIsDown = true;
+				return false;
+			});
+		$(document)
+			.bind('mouseup', function (e) {
+				mouseIsDown = false;
+			})
+			.bind('mousemove', function (e) {
+				if (mouseIsDown) {
+					handleVolumeMove(e);
+				}
+			});
+
+
+		// MUTE button
+		mute.find('button').click(function() {
+			if (media.muted) {
+				media.setMuted(false);
+				mute.removeClass('mejs-unmute').addClass('mejs-mute');
+				positionVolumeHandle(1);
+			} else {
+				media.setMuted(true);
+				mute.removeClass('mejs-mute').addClass('mejs-unmute');
+				positionVolumeHandle(0);
+			}
+		});
+
+		// listen for volume change events from other sources
+		media.addEventListener('volumechange', function(e) {
+			if (!mouseIsDown) {
+				positionVolumeHandle(e.target.volume);
+			}
+		}, true);
+
+		// set initial volume
+		positionVolumeHandle(player.options.startVolume);
+		
+		// shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
+		if (media.pluginType === 'native') {
+			media.setVolume(player.options.startVolume);
+		}
+	}
+
+})(mejs.$);
+
+(function($) {
+	mejs.MediaElementDefaults.forcePluginFullScreen = false;
+	
+	MediaElementPlayer.prototype.isFullScreen = false;
+	MediaElementPlayer.prototype.buildfullscreen = function(player, controls, layers, media) {
+
+		if (!player.isVideo)
+			return;
+			
+		// native events
+		if (mejs.MediaFeatures.hasNativeFullScreen) {
+			player.container.bind('webkitfullscreenchange', function(e) {
+			
+				if (document.webkitIsFullScreen) {
+					// reset the controls once we are fully in full screen
+					player.setControlsSize();
+				} else {				
+					// when a user presses ESC
+					// make sure to put the player back into place								
+					player.exitFullScreen();				
+				}
+			});
+		}
+
+		var 			
+			normalHeight = 0,
+			normalWidth = 0,
+			container = player.container,
+			docElement = document.documentElement,
+			docStyleOverflow,
+			parentWindow = window.parent,
+			parentiframes,			
+			iframe,
+			fullscreenBtn = 
+				$('<div class="mejs-button mejs-fullscreen-button"><button type="button"></button></div>')
+				.appendTo(controls)
+				.click(function() {
+					var isFullScreen = (mejs.MediaFeatures.hasNativeFullScreen) ?
+									document.webkitIsFullScreen :
+									player.isFullScreen;													
+					
+					if (isFullScreen) {
+						player.exitFullScreen();
+					} else {						
+						player.enterFullScreen();
+					}
+				});
+		
+		player.enterFullScreen = function() {
+			
+			// firefox can't adjust plugin sizes without resetting :(
+			if (player.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || player.options.forcePluginFullScreen)) {
+				media.setFullscreen(true);
+				//player.isFullScreen = true;
+				return;
+			}		
+			
+			// attempt to set fullscreen
+			if (mejs.MediaFeatures.hasNativeFullScreen) {
+				player.container[0].webkitRequestFullScreen();									
+			}
+								
+			// store overflow 
+			docStyleOverflow = docElement.style.overflow;
+			// set it to not show scroll bars so 100% will work
+			docElement.style.overflow = 'hidden';				
+		
+			// store
+			normalHeight = player.container.height();
+			normalWidth = player.container.width();
+
+			// make full size
+			container
+				.addClass('mejs-container-fullscreen')
+				.width('100%')
+				.height('100%')
+				.css('z-index', 1000);
+				//.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000});				
+				
+			if (player.pluginType === 'native') {
+				player.$media
+					.width('100%')
+					.height('100%');
+			} else {
+				container.find('object embed')
+					.width('100%')
+					.height('100%');
+				player.media.setVideoSize($(window).width(),$(window).height());
+			}
+			
+			layers.children('div')
+				.width('100%')
+				.height('100%');
+
+			fullscreenBtn
+				.removeClass('mejs-fullscreen')
+				.addClass('mejs-unfullscreen');
+
+			player.setControlsSize();
+			player.isFullScreen = true;
+		};
+		player.exitFullScreen = function() {
+
+			// firefox can't adjust plugins
+			if (player.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) {				
+				media.setFullscreen(false);
+				//player.isFullScreen = false;
+				return;
+			}		
+		
+			// come outo of native fullscreen
+			if (mejs.MediaFeatures.hasNativeFullScreen && document.webkitIsFullScreen) {							
+				document.webkitCancelFullScreen();									
+			}	
+
+			// restore scroll bars to document
+			docElement.style.overflow = docStyleOverflow;					
+				
+			container
+				.removeClass('mejs-container-fullscreen')
+				.width(normalWidth)
+				.height(normalHeight)
+				.css('z-index', 1);
+				//.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1});
+			
+			if (player.pluginType === 'native') {
+				player.$media
+					.width(normalWidth)
+					.height(normalHeight);
+			} else {
+				container.find('object embed')
+					.width(normalWidth)
+					.height(normalHeight);
+					
+				player.media.setVideoSize(normalWidth, normalHeight);
+			}				
+
+			layers.children('div')
+				.width(normalWidth)
+				.height(normalHeight);
+
+			fullscreenBtn
+				.removeClass('mejs-unfullscreen')
+				.addClass('mejs-fullscreen');
+
+			player.setControlsSize();
+			player.isFullScreen = false;
+		};
+		
+		$(window).bind('resize',function (e) {
+			player.setControlsSize();
+		});		
+
+		$(document).bind('keydown',function (e) {
+			if (player.isFullScreen && e.keyCode == 27) {
+				player.exitFullScreen();
+			}
+		});
+			
+	}
+
+})(mejs.$);
+(function($) {
+
+	// add extra default options 
+	$.extend(mejs.MepDefaults, {
+		// this will automatically turn on a <track>
+		startLanguage: '',
+		// a list of languages to auto-translate via Google
+		translations: [],
+		// a dropdownlist of automatic translations
+		translationSelector: false,
+		// key for tranlsations
+		googleApiKey: ''
+	});
+
+	$.extend(MediaElementPlayer.prototype, {
+
+		buildtracks: function(player, controls, layers, media) {
+			if (!player.isVideo)
+				return;
+
+			if (player.tracks.length == 0)
+				return;
+
+			var i, options = '';
+
+			player.chapters = 
+					$('<div class="mejs-chapters mejs-layer"></div>')
+						.prependTo(layers).hide();
+			player.captions = 
+					$('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position"><span class="mejs-captions-text"></span></div></div>')
+						.prependTo(layers).hide();
+			player.captionsText = player.captions.find('.mejs-captions-text');
+			player.captionsButton = 
+					$('<div class="mejs-button mejs-captions-button">'+
+						'<button type="button" ></button>'+
+						'<div class="mejs-captions-selector">'+
+							'<ul>'+
+								'<li>'+
+									'<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' +
+									'<label for="' + player.id + '_captions_none">None</label>'+
+								'</li>'	+
+							'</ul>'+
+						'</div>'+
+					'</button>')
+						.appendTo(controls)
+						// handle clicks to the language radio buttons
+						.delegate('input[type=radio]','click',function() {
+							lang = this.value;
+
+							if (lang == 'none') {
+								player.selectedTrack = null;
+							} else {
+								for (i=0; i<player.tracks.length; i++) {
+									if (player.tracks[i].srclang == lang) {
+										player.selectedTrack = player.tracks[i];
+										player.captions.attr('lang', player.selectedTrack.srclang);
+										player.displayCaptions();
+										break;
+									}
+								}
+							}
+						});
+						//.bind('mouseenter', function() {
+						//	player.captionsButton.find('.mejs-captions-selector').css('visibility','visible')
+						//});
+
+			if (!player.options.alwaysShowControls) {
+				// move with controls
+				player.container
+					.bind('mouseenter', function () {
+						// push captions above controls
+						player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
+
+					})
+					.bind('mouseleave', function () {
+						if (!media.paused) {
+							// move back to normal place
+							player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover');
+						}
+					});
+			} else {
+				player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
+			}
+
+			player.trackToLoad = -1;
+			player.selectedTrack = null;
+			player.isLoadingTrack = false;
+
+			// add user-defined translations
+			if (player.tracks.length > 0 && player.options.translations.length > 0) {
+				for (i=0; i<player.options.translations.length; i++) {
+					player.tracks.push({
+						srclang: player.options.translations[i].toLowerCase(),
+						src: null,
+						kind: 'subtitles', 
+						entries: [],
+						isLoaded: false,
+						isTranslation: true
+					});
+				}
+			}
+
+			// add to list
+			for (i=0; i<player.tracks.length; i++) {
+				if (player.tracks[i].kind == 'subtitles') {
+					player.addTrackButton(player.tracks[i].srclang, player.tracks[i].isTranslation);
+				}
+			}
+
+			player.loadNextTrack();
+
+
+			media.addEventListener('timeupdate',function(e) {
+				player.displayCaptions();
+			}, false);
+
+			media.addEventListener('loadedmetadata', function(e) {
+				player.displayChapters();
+			}, false);
+
+			player.container.hover(
+				function () {
+					// chapters
+					player.chapters.css('visibility','visible');
+					player.chapters.fadeIn(200);
+				},
+				function () {
+					if (!media.paused) {
+						player.chapters.fadeOut(200, function() {
+							$(this).css('visibility','hidden');
+							$(this).css('display','block');
+						});
+					}
+				});
+				
+			// check for autoplay
+			if (player.node.getAttribute('autoplay') !== null) {
+				player.chapters.css('visibility','hidden');
+			}				
+
+			// auto selector
+			if (player.options.translationSelector) {
+				for (i in mejs.language.codes) {
+					options += '<option value="' + i + '">' + mejs.language.codes[i] + '</option>';
+				}
+				player.container.find('.mejs-captions-selector ul').before($(
+					'<select class="mejs-captions-translations">' +
+						'<option value="">--Add Translation--</option>' +
+						options +
+					'</select>'
+				));
+				// add clicks
+				player.container.find('.mejs-captions-translations').change(function() {
+					var
+						option = $(this);
+						lang = option.val();
+					// add this language to the tracks list
+					if (lang != '') {
+						player.tracks.push({
+							srclang: lang,
+							src: null,
+							entries: [],
+							isLoaded: false,
+							isTranslation: true
+						});
+
+						if (!player.isLoadingTrack) {
+							player.trackToLoad--;
+							player.addTrackButton(lang,true);
+							player.options.startLanguage = lang;
+							player.loadNextTrack();
+						}
+					}
+				});
+			}
+
+		},
+
+		loadNextTrack: function() {
+			var t = this;
+
+			t.trackToLoad++;
+			if (t.trackToLoad < t.tracks.length) {
+				t.isLoadingTrack = true;
+				t.loadTrack(t.trackToLoad);
+			} else {
+				// add done?
+				t.isLoadingTrack = false;
+			}
+		},
+
+		loadTrack: function(index){
+			var
+				t = this,
+				track = t.tracks[index],
+				after = function() {
+
+					track.isLoaded = true;
+
+					// create button
+					//t.addTrackButton(track.srclang);
+					t.enableTrackButton(track.srclang);
+
+					t.loadNextTrack();
+
+				};
+
+			if (track.isTranslation) {
+
+				// translate the first track
+				mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) {
+
+					// store the new translation
+					track.entries = newOne;
+
+					after();
+				});
+
+			} else {
+				$.ajax({
+					url: track.src,
+					success: function(d) {
+
+						// parse the loaded file
+						track.entries = mejs.TrackFormatParser.parse(d);
+						after();
+
+						if (track.kind == 'chapters' && t.media.duration > 0) {
+							t.drawChapters(track);
+						}
+					},
+					error: function() {
+						t.loadNextTrack();
+					}
+				});
+			}
+		},
+
+		enableTrackButton: function(lang) {
+			var t = this;
+
+			t.captionsButton
+				.find('input[value=' + lang + ']')
+					.prop('disabled',false)
+				.siblings('label')
+					.html( mejs.language.codes[lang] || lang );
+
+			// auto select
+			if (t.options.startLanguage == lang) {
+				$('#' + t.id + '_captions_' + lang).click();
+			}
+
+			t.adjustLanguageBox();
+		},
+
+		addTrackButton: function(lang, isTranslation) {
+			var t = this,
+				l = mejs.language.codes[lang] || lang;
+
+			t.captionsButton.find('ul').append(
+				$('<li>'+
+					'<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' +
+					'<label for="' + t.id + '_captions_' + lang + '">' + l + ((isTranslation) ? ' (translating)' : ' (loading)') + '</label>'+
+				'</li>')
+			);
+
+			t.adjustLanguageBox();
+
+			// remove this from the dropdownlist (if it exists)
+			t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove();
+		},
+
+		adjustLanguageBox:function() {
+			var t = this;
+			// adjust the size of the outer box
+			t.captionsButton.find('.mejs-captions-selector').height(
+				t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) +
+				t.captionsButton.find('.mejs-captions-translations').outerHeight(true)
+			);
+		},
+
+		displayCaptions: function() {
+
+			if (typeof this.tracks == 'undefined')
+				return;
+
+			var
+				t = this,
+				i,
+				track = t.selectedTrack;
+
+			if (track != null && track.isLoaded) {
+				for (i=0; i<track.entries.times.length; i++) {
+					if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){
+						t.captionsText.html(track.entries.text[i]);
+						t.captions.show();
+						return; // exit out if one is visible;
+					}
+				}
+				t.captions.hide();
+			} else {
+				t.captions.hide();
+			}
+		},
+
+		displayChapters: function() {
+			var 
+				t = this,
+				i;
+
+			for (i=0; i<t.tracks.length; i++) {
+				if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) {
+					t.drawChapters(t.tracks[i]);
+					break;
+				}
+			}
+		},
+
+		drawChapters: function(chapters) {
+			var 
+				t = this,
+				i,
+				dur,
+				//width,
+				//left,
+				percent = 0,
+				usedPercent = 0;
+
+			t.chapters.empty();
+
+			for (i=0; i<chapters.entries.times.length; i++) {
+				dur = chapters.entries.times[i].stop - chapters.entries.times[i].start;
+				percent = Math.floor(dur / t.media.duration * 100);
+				if (percent + usedPercent > 100 || // too large
+					i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in
+					{
+					percent = 100 - usedPercent;
+				}
+				//width = Math.floor(t.width * dur / t.media.duration);
+				//left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration);
+				//if (left + width > t.width) {
+				//	width = t.width - left;
+				//}
+
+				t.chapters.append( $(
+					'<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + 
+						'<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + 
+							'<span class="ch-title">' + chapters.entries.text[i] + '</span>' + 
+							'<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + 
+						'</div>' +
+					'</div>'));
+				usedPercent += percent;
+			}
+
+			t.chapters.find('div.mejs-chapter').click(function() {
+				t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) );
+				if (t.media.paused) {
+					t.media.play(); 
+				}
+			});
+
+			t.chapters.show();
+		}
+	});
+
+
+
+	mejs.language = {
+		codes:  {
+			af:'Afrikaans',
+			sq:'Albanian',
+			ar:'Arabic',
+			be:'Belarusian',
+			bg:'Bulgarian',
+			ca:'Catalan',
+			zh:'Chinese',
+			'zh-cn':'Chinese Simplified',
+			'zh-tw':'Chinese Traditional',
+			hr:'Croatian',
+			cs:'Czech',
+			da:'Danish',
+			nl:'Dutch',
+			en:'English',
+			et:'Estonian',
+			tl:'Filipino',
+			fi:'Finnish',
+			fr:'French',
+			gl:'Galician',
+			de:'German',
+			el:'Greek',
+			ht:'Haitian Creole',
+			iw:'Hebrew',
+			hi:'Hindi',
+			hu:'Hungarian',
+			is:'Icelandic',
+			id:'Indonesian',
+			ga:'Irish',
+			it:'Italian',
+			ja:'Japanese',
+			ko:'Korean',
+			lv:'Latvian',
+			lt:'Lithuanian',
+			mk:'Macedonian',
+			ms:'Malay',
+			mt:'Maltese',
+			no:'Norwegian',
+			fa:'Persian',
+			pl:'Polish',
+			pt:'Portuguese',
+			//'pt-pt':'Portuguese (Portugal)',
+			ro:'Romanian',
+			ru:'Russian',
+			sr:'Serbian',
+			sk:'Slovak',
+			sl:'Slovenian',
+			es:'Spanish',
+			sw:'Swahili',
+			sv:'Swedish',
+			tl:'Tagalog',
+			th:'Thai',
+			tr:'Turkish',
+			uk:'Ukrainian',
+			vi:'Vietnamese',
+			cy:'Welsh',
+			yi:'Yiddish'
+		}
+	};
+
+	/*
+	Parses WebVVT format which should be formatted as
+	================================
+	WEBVTT
+	
+	1
+	00:00:01,1 --> 00:00:05,000
+	A line of text
+
+	2
+	00:01:15,1 --> 00:02:05,000
+	A second line of text
+	
+	===============================
+
+	Adapted from: http://www.delphiki.com/html5/playr
+	*/
+	mejs.TrackFormatParser = {
+		pattern_identifier: /^[0-9]+$/,
+		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})?)(.*)$/,
+
+		split2: function (text, regex) {
+			// normal version for compliant browsers
+			// see below for IE fix
+			return text.split(regex);
+		},
+		parse: function(trackText) {
+			var 
+				i = 0,
+				lines = this.split2(trackText, /\r?\n/),
+				entries = {text:[], times:[]},
+				timecode,
+				text;
+
+			for(; i<lines.length; i++) {
+				// check for the line number
+				if (this.pattern_identifier.exec(lines[i])){
+					// skip to the next line where the start --> end time code should be
+					i++;
+					timecode = this.pattern_timecode.exec(lines[i]);
+					if (timecode && i<lines.length){
+						i++;
+						// grab all the (possibly multi-line) text that follows
+						text = lines[i];
+						i++;
+						while(lines[i] !== '' && i<lines.length){
+							text = text + '\n' + lines[i];
+							i++;
+						}
+
+						// Text is in a different array so I can use .join
+						entries.text.push(text);
+						entries.times.push(
+						{
+							start: mejs.Utility.timeCodeToSeconds(timecode[1]),
+							stop: mejs.Utility.timeCodeToSeconds(timecode[3]),
+							settings: timecode[5]
+						});
+					}
+				}
+			}
+
+			return entries;
+		},
+
+		translateTrackText: function(trackData, fromLang, toLang, googleApiKey, callback) {
+
+			var 
+				entries = {text:[], times:[]},
+				lines,
+				i
+
+			this.translateText( trackData.text.join(' <a></a>'), fromLang, toLang, googleApiKey, function(result) {
+				// split on separators
+				lines = result.split('<a></a>');
+
+				// create new entries
+				for (i=0;i<trackData.text.length; i++) {
+					// add translated line
+					entries.text[i] = lines[i];
+					// copy existing times
+					entries.times[i] = {
+						start: trackData.times[i].start,
+						stop: trackData.times[i].stop,
+						settings: trackData.times[i].settings
+					};
+				}
+
+				callback(entries);
+			});
+		},
+
+		translateText: function(text, fromLang, toLang, googleApiKey, callback) {
+
+			var
+				separatorIndex,
+				chunks = [],
+				chunk,
+				maxlength = 1000,
+				result = '',
+				nextChunk= function() {
+					if (chunks.length > 0) {
+						chunk = chunks.shift();
+						mejs.TrackFormatParser.translateChunk(chunk, fromLang, toLang, googleApiKey, function(r) {
+							if (r != 'undefined') {
+								result += r;
+							}
+							nextChunk();
+						});
+					} else {
+						callback(result);
+					}
+				};
+
+			// split into chunks
+			while (text.length > 0) {
+				if (text.length > maxlength) {
+					separatorIndex = text.lastIndexOf('.', maxlength);
+					chunks.push(text.substring(0, separatorIndex));
+					text = text.substring(separatorIndex+1);
+				} else {
+					chunks.push(text);
+					text = '';
+				}
+			}
+
+			// start handling the chunks
+			nextChunk();
+		},
+		translateChunk: function(text, fromLang, toLang, googleApiKey, callback) {
+
+			var data = {
+				q: text, 
+				langpair: fromLang + '|' + toLang,
+				v: '1.0'
+			};
+			if (googleApiKey !== '' && googleApiKey !== null) {
+				data.key = googleApiKey;
+			}
+
+			$.ajax({
+				url: 'https://ajax.googleapis.com/ajax/services/language/translate', // 'https://www.google.com/uds/Gtranslate', //'https://ajax.googleapis.com/ajax/services/language/translate', //
+				data: data,
+				type: 'GET',
+				dataType: 'jsonp',
+				success: function(d) {
+					callback(d.responseData.translatedText);
+				},
+				error: function(e) {
+					callback(null);
+				}
+			});
+		}
+	};
+	// test for browsers with bad String.split method.
+	if ('x\n\ny'.split(/\n/gi).length != 3) {
+		// add super slow IE8 and below version
+		mejs.TrackFormatParser.split2 = function(text, regex) {
+			var 
+				parts = [], 
+				chunk = '',
+				i;
+
+			for (i=0; i<text.length; i++) {
+				chunk += text.substring(i,i+1);
+				if (regex.test(chunk)) {
+					parts.push(chunk.replace(regex, ''));
+					chunk = '';
+				}
+			}
+			parts.push(chunk);
+			return parts;
+		}
+	}
+
+})(mejs.$);
+
+