/*
 VideoJS - HTML5 Video Player
 v2.0.2
 
 This file is part of VideoJS. Copyright 2010 Zencoder, Inc.
 
 VideoJS is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 VideoJS is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public License
 along with VideoJS.  If not, see
<http://www.gnu.org/licenses/>
.
 */

// Self-executing function to prevent global vars and help with minification
(function(window, undefined) {
	var document = window.document;

	// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
	(function() {
		var initializing = false,
			fnTest = /xyz/.test(function() {
				xyz;
			}) ? /\b_super\b/ : /.*/;
		this.JRClass = function() {};
		JRClass.extend = function(prop) {
				var _super = this.prototype;
				initializing = true;
				var prototype = new this();
				initializing = false;
				for (var name in prop) {
					prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn) {
						return function() {
							var tmp = this._super;
							this._super = _super[name];
							var ret = fn.apply(this, arguments);
							this._super = tmp;
							return ret;
						};
					})(name, prop[name]) : prop[name];
				}
				function JRClass() {
					if (!initializing && this.init) this.init.apply(this, arguments);
				}
				JRClass.prototype = prototype;
				JRClass.constructor = JRClass;
				JRClass.extend = arguments.callee;
				return JRClass;
			};
	})();

	// Video JS Player Class
	var VideoJS = JRClass.extend({

		// Initialize the player for the supplied video tag element
		// element: video tag
		init: function(element, setOptions) {

			// Allow an ID string or an element
			if (typeof element == 'string') {
				this.video = document.getElementById(element);
			} else {
				this.video = element;
			}
			// Store reference to player on the video element.
			// So you can acess the player later: document.getElementById("video_id").player.play();
			this.video.player = this;
			this.values = {}; // Cache video values.
			this.elements = {}; // Store refs to controls elements.
			// Default Options
			this.options = {
				autoplay: false,
				preload: true,
				useBuiltInControls: false,
				// Use the browser's controls (iPhone)
				controlsBelow: false,
				// Display control bar below video vs. in front of
				controlsAtStart: false,
				// Make controls visible when page loads
				controlsHiding: true,
				// Hide controls when not over the video
				defaultVolume: 0.85,
				// Will be overridden by localStorage volume if available
				playerFallbackOrder: ["html5", "flash", "links"],
				// Players and order to use them
				flashPlayer: "htmlObject",
				flashPlayerVersion: false // Required flash version for fallback
			};
			// Override default options with global options
			if (typeof VideoJS.options == "object") {
				_V_.merge(this.options, VideoJS.options);
			}
			// Override default & global options with options specific to this player
			if (typeof setOptions == "object") {
				_V_.merge(this.options, setOptions);
			}
			// Override preload & autoplay with video attributes
			if (this.getPreloadAttribute() !== undefined) {
				this.options.preload = this.getPreloadAttribute();
			}
			if (this.getAutoplayAttribute() !== undefined) {
				this.options.autoplay = this.getAutoplayAttribute();
			}

			// Store reference to embed code pieces
			this.box = this.video.parentNode;
			this.linksFallback = this.getLinksFallback();
			this.hideLinksFallback(); // Will be shown again if "links" player is used
			// Loop through the player names list in options, "html5" etc.
			// For each player name, initialize the player with that name under VideoJS.players
			// If the player successfully initializes, we're done
			// If not, try the next player in the list
			this.each(this.options.playerFallbackOrder, function(playerType) {
				if (this[playerType + "Supported"]()) { // Check if player type is supported
					this[playerType + "Init"](); // Initialize player type
					return true; // Stop looping though players
				}
			});

			// Start Global Listeners - API doesn't exist before now
			this.activateElement(this, "player");
			this.activateElement(this.box, "box");
		},
		/* Behaviors
		 ================================================================================ */
		behaviors: {},
		newBehavior: function(name, activate, functions) {
			this.behaviors[name] = activate;
			this.extend(functions);
		},
		activateElement: function(element, behavior) {
			// Allow passing and ID string
			if (typeof element == "string") {
				element = document.getElementById(element);
			}
			this.behaviors[behavior].call(this, element);
		},
		/* Errors/Warnings
		 ================================================================================ */
		errors: [],
		// Array to track errors
		warnings: [],
		warning: function(warning) {
			this.warnings.push(warning);
			this.log(warning);
		},
		/* History of errors/events (not quite there yet)
		 ================================================================================ */
		history: [],
		log: function(event) {
			if (!event) {
				return;
			}
			if (typeof event == "string") {
				event = {
					type: event
				};
			}
			if (event.type) {
				this.history.push(event.type);
			}
			if (this.history.length >= 50) {
				this.history.shift();
			}
			try {
				console.log(event.type);
			} catch (e) {
				try {
					opera.postError(event.type);
				} catch (e) {}
			}
		},
		/* Local Storage
		 ================================================================================ */
		setLocalStorage: function(key, value) {
			if (!localStorage) {
				return;
			}
			try {
				localStorage[key] = value;
			} catch (e) {
				if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
					this.warning(VideoJS.warnings.localStorageFull);
				}
			}
		},
		/* Helpers
		 ================================================================================ */
		getPreloadAttribute: function() {
			if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload")) {
				var preload = this.video.getAttribute("preload");
				// Only included the attribute, thinking it was boolean
				if (preload === "" || preload === "true") {
					return "auto";
				}
				if (preload === "false") {
					return "none";
				}
				return preload;
			}
		},
		getAutoplayAttribute: function() {
			if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("autoplay")) {
				var autoplay = this.video.getAttribute("autoplay");
				if (autoplay === "false") {
					return false;
				}
				return true;
			}
		},
		// Calculates amoutn of buffer is full
		bufferedPercent: function() {
			return (this.duration()) ? this.buffered()[1] / this.duration() : 0;
		},
		// Each that maintains player as context
		// Break if true is returned
		each: function(arr, fn) {
			if (!arr || arr.length === 0) {
				return;
			}
			for (var i = 0, j = arr.length; i < j; i++) {
				if (fn.call(this, arr[i], i)) {
					break;
				}
			}
		},
		extend: function(obj) {
			for (var attrname in obj) {
				if (obj.hasOwnProperty(attrname)) {
					this[attrname] = obj[attrname];
				}
			}
		}
	});
	VideoJS.player = VideoJS.prototype;

	////////////////////////////////////////////////////////////////////////////////
	// Player Types
	////////////////////////////////////////////////////////////////////////////////
	/* Flash Object Fallback (Player Type)
	 ================================================================================ */
	VideoJS.player.extend({
		flashSupported: function() {
			if (!this.flashElement) {
				this.flashElement = this.getFlashElement();
			}
			// Check if object exists & Flash Player version is supported
			if (this.flashElement && this.flashPlayerVersionSupported()) {
				return true;
			} else {
				return false;
			}
		},
		flashInit: function() {
			this.replaceWithFlash();
			this.element = this.flashElement;
			this.video.src = ""; // Stop video from downloading if HTML5 is still supported
			var flashPlayerType = VideoJS.flashPlayers[this.options.flashPlayer];
			this.extend(VideoJS.flashPlayers[this.options.flashPlayer].api);
			(flashPlayerType.init.context(this))();
		},
		// Get Flash Fallback object element from Embed Code
		getFlashElement: function() {
			var children = this.video.children;
			for (var i = 0, j = children.length; i < j; i++) {
				if (children[i].className == "vjs-flash-fallback") {
					return children[i];
				}
			}
		},
		// Used to force a browser to fall back when it's an HTML5 browser but there's no supported sources
		replaceWithFlash: function() {
			// this.flashElement = this.video.removeChild(this.flashElement);
			if (this.flashElement) {
				this.box.insertBefore(this.flashElement, this.video);
				this.video.style.display = "none"; // Removing it was breaking later players
			}
		},
		// Check if browser can use this flash player
		flashPlayerVersionSupported: function() {
			var playerVersion = (this.options.flashPlayerVersion) ? this.options.flashPlayerVersion : VideoJS.flashPlayers[this.options.flashPlayer].flashPlayerVersion;
			return VideoJS.getFlashVersion() >= playerVersion;
		}
	});
	VideoJS.flashPlayers = {};
	VideoJS.flashPlayers.htmlObject = {
		flashPlayerVersion: 9,
		init: function() {
			return true;
		},
		api: { // No video API available with HTML Object embed method
			width: function(width) {
				if (width !== undefined) {
					this.element.width = width;
					this.box.style.width = width + "px";
					this.triggerResizeListeners();
					return this;
				}
				return this.element.width;
			},
			height: function(height) {
				if (height !== undefined) {
					this.element.height = height;
					this.box.style.height = height + "px";
					this.triggerResizeListeners();
					return this;
				}
				return this.element.height;
			}
		}
	};


	/* Download Links Fallback (Player Type)
	 ================================================================================ */
	VideoJS.player.extend({
		linksSupported: function() {
			return true;
		},
		linksInit: function() {
			this.showLinksFallback();
			this.element = this.video;
		},
		// Get the download links block element
		getLinksFallback: function() {
			return this.box.getElementsByTagName("P")[0];
		},
		// Hide no-video download paragraph
		hideLinksFallback: function() {
			if (this.linksFallback) {
				this.linksFallback.style.display = "none";
			}
		},
		// Hide no-video download paragraph
		showLinksFallback: function() {
			if (this.linksFallback) {
				this.linksFallback.style.display = "block";
			}
		}
	});

	////////////////////////////////////////////////////////////////////////////////
	// Class Methods
	// Functions that don't apply to individual videos.
	////////////////////////////////////////////////////////////////////////////////
	// Combine Objects - Use "safe" to protect from overwriting existing items
	VideoJS.merge = function(obj1, obj2, safe) {
		for (var attrname in obj2) {
			if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) {
				obj1[attrname] = obj2[attrname];
			}
		}
		return obj1;
	};
	VideoJS.extend = function(obj) {
		this.merge(this, obj, true);
	};

	VideoJS.extend({
		// Add VideoJS to all video tags with the video-js class when the DOM is ready
		setupAllWhenReady: function(options) {
			// Options is stored globally, and added ot any new player on init
			VideoJS.options = options;
			VideoJS.DOMReady(VideoJS.setup);
		},

		// Run the supplied function when the DOM is ready
		DOMReady: function(fn) {
			VideoJS.addToDOMReady(fn);
		},

		// Set up a specific video or array of video elements
		// "video" can be:
		//    false, undefined, or "All": set up all videos with the video-js class
		//    A video tag ID or video tag element: set up one video and return one player
		//    An array of video tag elements/IDs: set up each and return an array of players
		setup: function(videos, options) {
			var returnSingular = false,
				playerList = [],
				videoElement;

			// If videos is undefined or "All", set up all videos with the video-js class
			if (!videos || videos == "All") {
					videos = VideoJS.getVideoJSTags();
					// If videos is not an array, add to an array
				} else if (typeof videos != 'object' || videos.nodeType == 1) {
					videos = [videos];
					returnSingular = true;
				}

			// Loop through videos and create players for them
			for (var i = 0; i < videos.length; i++) {
					if (typeof videos[i] == 'string') {
						videoElement = document.getElementById(videos[i]);
					} else { // assume DOM object
						videoElement = videos[i];
					}
					playerList.push(new VideoJS(videoElement, options));
				}

			// Return one or all depending on what was passed in
			return (returnSingular) ? playerList[0] : playerList;
		},

		// Find video tags with the video-js class
		getVideoJSTags: function() {
			var videoTags = document.getElementsByTagName("video"),
				videoJSTags = [],
				videoTag;

			for (var i = 0, j = videoTags.length; i < j; i++) {
					videoTag = videoTags[i];
					if (videoTag.className.indexOf("video-js") != -1) {
						videoJSTags.push(videoTag);
					}
				}
			return videoJSTags;
		},

		// Check if the browser supports video.
		browserSupportsVideo: function() {
			if (typeof VideoJS.videoSupport != "undefined") {
				return VideoJS.videoSupport;
			}
			VideoJS.videoSupport = !! document.createElement('video').canPlayType;
			return VideoJS.videoSupport;
		},

		getFlashVersion: function() {
			// Cache Version
			if (typeof VideoJS.flashVersion != "undefined") {
				return VideoJS.flashVersion;
			}
			var version = 0,
				desc;
			if (typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") {
					desc = navigator.plugins["Shockwave Flash"].description;
					if (desc && !(typeof navigator.mimeTypes != "undefined" && navigator.mimeTypes["application/x-shockwave-flash"] && !navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)) {
						version = parseInt(desc.match(/^.*\s+([^\s]+)\.[^\s]+\s+[^\s]+$/)[1], 10);
					}
				} else if (typeof window.ActiveXObject != "undefined") {
					try {
						var testObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
						if (testObject) {
							version = parseInt(testObject.GetVariable("$version").match(/^[^\s]+\s(\d+)/)[1], 10);
						}
					}
					catch (e) {}
				}
			VideoJS.flashVersion = version;
			return VideoJS.flashVersion;
		},

		// Browser & Device Checks
		isIE: function() {
			return !+"\v1";
		},
		isIPad: function() {
			return navigator.userAgent.match(/iPad/i) !== null;
		},
		isIPhone: function() {
			return navigator.userAgent.match(/iPhone/i) !== null;
		},
		isIOS: function() {
			return VideoJS.isIPhone() || VideoJS.isIPad();
		},
		iOSVersion: function() {
			var match = navigator.userAgent.match(/OS (\d+)_/i);
			if (match && match[1]) {
				return match[1];
			}
		},
		isAndroid: function() {
			return navigator.userAgent.match(/Android/i) !== null;
		},
		androidVersion: function() {
			var match = navigator.userAgent.match(/Android (\d+)\./i);
			if (match && match[1]) {
				return match[1];
			}
		},

		warnings: {
			// Safari errors if you call functions on a video that hasn't loaded yet
			videoNotReady: "Video is not ready yet (try playing the video first).",
			// Getting a QUOTA_EXCEEDED_ERR when setting local storage occasionally
			localStorageFull: "Local Storage is Full"
		}
	});

	// Shim to make Video tag valid in IE
	if (VideoJS.isIE()) {
		document.createElement("video");
	}

	// Expose to global
	window.VideoJS = window._V_ = VideoJS;

	/* HTML5 Player Type
	 ================================================================================ */
	VideoJS.player.extend({
		html5Supported: function() {
			if (VideoJS.browserSupportsVideo() && this.canPlaySource()) {
				return true;
			} else {
				return false;
			}
		},
		html5Init: function() {
			this.element = this.video;

			this.fixPreloading(); // Support old browsers that used autobuffer
			this.supportProgressEvents(); // Support browsers that don't use 'buffered'
			// Set to stored volume OR 85%
			this.volume((localStorage && localStorage.volume) || this.options.defaultVolume);

			// Update interface for device needs
			if (VideoJS.isIOS()) {
				this.options.useBuiltInControls = true;
				this.iOSInterface();
			} else if (VideoJS.isAndroid()) {
				this.options.useBuiltInControls = true;
				this.androidInterface();
			}

			// Add VideoJS Controls
			if (!this.options.useBuiltInControls) {
				this.video.controls = false;

				if (this.options.controlsBelow) {
					_V_.addClass(this.box, "vjs-controls-below");
				}

				// Make a click on th video act as a play button
				this.activateElement(this.video, "playToggle");

				// Build Interface
				this.buildStylesCheckDiv(); // Used to check if style are loaded
				this.buildAndActivatePoster();
				this.buildBigPlayButton();
				this.buildAndActivateSpinner();
				this.buildAndActivateControlBar();
				this.loadInterface(); // Show everything once styles are loaded
				this.getSubtitles();
			}
		},
		/* Source Managemet
		 ================================================================================ */
		canPlaySource: function() {
			// Cache Result
			if (this.canPlaySourceResult) {
				return this.canPlaySourceResult;
			}
			// Loop through sources and check if any can play
			var children = this.video.children;
			for (var i = 0, j = children.length; i < j; i++) {
				if (children[i].tagName.toUpperCase() == "SOURCE") {
					var canPlay = this.video.canPlayType(children[i].type) || this.canPlayExt(children[i].src);
					if (canPlay == "probably" || canPlay == "maybe") {
						this.firstPlayableSource = children[i];
						this.canPlaySourceResult = true;
						return true;
					}
				}
			}
			this.canPlaySourceResult = false;
			return false;
		},
		// Check if the extention is compatible, for when type won't work
		canPlayExt: function(src) {
			if (!src) {
				return "";
			}
			var match = src.match(/\.([^\.]+)$/);
			if (match && match[1]) {
				var ext = match[1].toLowerCase();
				// Android canPlayType doesn't work
				if (VideoJS.isAndroid()) {
					if (ext == "mp4" || ext == "m4v") {
						return "maybe";
					}
					// Allow Apple HTTP Streaming for iOS
				} else if (VideoJS.isIOS()) {
					if (ext == "m3u8") {
						return "maybe";
					}
				}
			}
			return "";
		},
		// Force the video source - Helps fix loading bugs in a handful of devices, like the iPad/iPhone poster bug
		// And iPad/iPhone javascript include location bug. And Android type attribute bug
		forceTheSource: function() {
			this.video.src = this.firstPlayableSource.src; // From canPlaySource()
			this.video.load();
		},
		/* Device Fixes
		 ================================================================================ */
		// Support older browsers that used "autobuffer"
		fixPreloading: function() {
			if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload") && this.video.preload != "none") {
				this.video.autobuffer = true; // Was a boolean
			} else {
				this.video.autobuffer = false;
				this.video.preload = "none";
			}
		},

		// Listen for Video Load Progress (currently does not if html file is local)
		// Buffered does't work in all browsers, so watching progress as well
		supportProgressEvents: function(e) {
			_V_.addListener(this.video, 'progress', this.playerOnVideoProgress.context(this));
		},
		playerOnVideoProgress: function(event) {
			this.setBufferedFromProgress(event);
		},
		setBufferedFromProgress: function(event) { // HTML5 Only
			if (event.total > 0) {
				var newBufferEnd = (event.loaded / event.total) * this.duration();
				if (newBufferEnd > this.values.bufferEnd) {
					this.values.bufferEnd = newBufferEnd;
				}
			}
		},

		iOSInterface: function() {
			if (VideoJS.iOSVersion() < 4) {
				this.forceTheSource();
			} // Fix loading issues
			if (VideoJS.isIPad()) { // iPad could work with controlsBelow
				this.buildAndActivateSpinner(); // Spinner still works well on iPad, since iPad doesn't have one
			}
		},

		// Fix android specific quirks
		// Use built-in controls, but add the big play button, since android doesn't have one.
		androidInterface: function() {
			this.forceTheSource(); // Fix loading issues
			_V_.addListener(this.video, "click", function() {
				this.play();
			}); // Required to play
			this.buildBigPlayButton(); // But don't activate the normal way. Pause doesn't work right on android.
			_V_.addListener(this.bigPlayButton, "click", function() {
				this.play();
			}.context(this));
			this.positionBox();
			this.showBigPlayButtons();
		},
		/* Wait for styles (TODO: move to _V_)
		 ================================================================================ */
		loadInterface: function() {
			if (!this.stylesHaveLoaded()) {
				// Don't want to create an endless loop either.
				if (!this.positionRetries) {
					this.positionRetries = 1;
				}
				if (this.positionRetries++ < 100) {
					setTimeout(this.loadInterface.context(this), 10);
					return;
				}
			}
			this.hideStylesCheckDiv();
			this.showPoster();
			if (this.video.paused !== false) {
				this.showBigPlayButtons();
			}
			if (this.options.controlsAtStart) {
				this.showControlBars();
			}
			this.positionAll();
		},
		/* Control Bar
		 ================================================================================ */
		buildAndActivateControlBar: function() {
			/* Creating this HTML
				<div class="vjs-controls">
					<div class="vjs-play-control"> <span></span> </div>
					<div class="vjs-progress-control">
						<div class="vjs-progress-holder">
							<div class="vjs-load-progress"></div>
							<div class="vjs-play-progress"></div>
						</div>
					</div>
					<div class="vjs-time-control"> <span class="vjs-current-time-display">00:00</span><span> / </span><span class="vjs-duration-display">00:00</span> </div>
					<div class="vjs-volume-control">
						<div> <span></span><span></span><span></span><span></span><span></span><span></span> </div>
					</div>
					<div class="vjs-fullscreen-control">
						<div> <span></span><span></span><span></span><span></span> </div>
					</div>
				</div>
*/

			// Create a div to hold the different controls
			this.controls = _V_.createElement("div", {
				className: "vjs-controls"
			});
			// Add the controls to the video's container
			this.box.appendChild(this.controls);
			this.activateElement(this.controls, "controlBar");
			this.activateElement(this.controls, "mouseOverVideoReporter");

			// Build the play control
			this.playControl = _V_.createElement("div", {
				className: "vjs-play-control",
				innerHTML: "<span></span>"
			});
			this.controls.appendChild(this.playControl);
			this.activateElement(this.playControl, "playToggle");

			// Build the progress control
			this.progressControl = _V_.createElement("div", {
				className: "vjs-progress-control"
			});
			this.controls.appendChild(this.progressControl);

			// Create a holder for the progress bars
			this.progressHolder = _V_.createElement("div", {
				className: "vjs-progress-holder"
			});
			this.progressControl.appendChild(this.progressHolder);
			this.activateElement(this.progressHolder, "currentTimeScrubber");

			// Create the loading progress display
			this.loadProgressBar = _V_.createElement("div", {
				className: "vjs-load-progress"
			});
			this.progressHolder.appendChild(this.loadProgressBar);
			this.activateElement(this.loadProgressBar, "loadProgressBar");

			// Create the playing progress display
			this.playProgressBar = _V_.createElement("div", {
				className: "vjs-play-progress"
			});
			this.progressHolder.appendChild(this.playProgressBar);
			this.activateElement(this.playProgressBar, "playProgressBar");

			// Create the progress time display (00:00 / 00:00)
			this.timeControl = _V_.createElement("div", {
				className: "vjs-time-control"
			});
			this.controls.appendChild(this.timeControl);

			// Create the current play time display
			this.currentTimeDisplay = _V_.createElement("span", {
				className: "vjs-current-time-display",
				innerHTML: "00:00"
			});
			this.timeControl.appendChild(this.currentTimeDisplay);
			this.activateElement(this.currentTimeDisplay, "currentTimeDisplay");

			// Add time separator
			this.timeSeparator = _V_.createElement("span", {
				innerHTML: " / "
			});
			this.timeControl.appendChild(this.timeSeparator);

			// Create the total duration display
			this.durationDisplay = _V_.createElement("span", {
				className: "vjs-duration-display",
				innerHTML: "00:00"
			});
			this.timeControl.appendChild(this.durationDisplay);
			this.activateElement(this.durationDisplay, "durationDisplay");

			// Create the volumne control
			this.volumeControl = _V_.createElement("div", {
				className: "vjs-volume-control",
				innerHTML: "<div><span></span><span></span><span></span><span></span><span></span><span></span></div>"
			});
			this.controls.appendChild(this.volumeControl);
			this.activateElement(this.volumeControl, "volumeScrubber");

			this.volumeDisplay = this.volumeControl.children[0];
			this.activateElement(this.volumeDisplay, "volumeDisplay");

			// Crete the fullscreen control
			this.fullscreenControl = _V_.createElement("div", {
				className: "vjs-fullscreen-control",
				innerHTML: "<div><span></span><span></span><span></span><span></span></div>"
			});
			this.controls.appendChild(this.fullscreenControl);
			this.activateElement(this.fullscreenControl, "fullscreenToggle");
		},
		/* Poster Image
		 ================================================================================ */
		buildAndActivatePoster: function() {
			this.updatePosterSource();
			if (this.video.poster) {
				this.poster = document.createElement("img");
				// Add poster to video box
				this.box.appendChild(this.poster);

				// Add poster image data
				this.poster.src = this.video.poster;
				// Add poster styles
				this.poster.className = "vjs-poster";
				this.activateElement(this.poster, "poster");
			} else {
				this.poster = false;
			}
		},
		/* Big Play Button
		 ================================================================================ */
		buildBigPlayButton: function() {
			/* Creating this HTML
				<div class="vjs-big-play-button"><span></span></div>
*/
			this.bigPlayButton = _V_.createElement("div", {
				className: "vjs-big-play-button",
				innerHTML: "<span></span>"
			});
			this.box.appendChild(this.bigPlayButton);
			this.activateElement(this.bigPlayButton, "bigPlayButton");
		},
		/* Spinner (Loading)
		 ================================================================================ */
		buildAndActivateSpinner: function() {
			this.spinner = _V_.createElement("div", {
				className: "vjs-spinner",
				innerHTML: "<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>"
			});
			this.box.appendChild(this.spinner);
			this.activateElement(this.spinner, "spinner");
		},
		/* Styles Check - Check if styles are loaded (move ot _V_)
		 ================================================================================ */
		// Sometimes the CSS styles haven't been applied to the controls yet
		// when we're trying to calculate the height and position them correctly.
		// This causes a flicker where the controls are out of place.
		buildStylesCheckDiv: function() {
			this.stylesCheckDiv = _V_.createElement("div", {
				className: "vjs-styles-check"
			});
			this.stylesCheckDiv.style.position = "absolute";
			this.box.appendChild(this.stylesCheckDiv);
		},
		hideStylesCheckDiv: function() {
			this.stylesCheckDiv.style.display = "none";
		},
		stylesHaveLoaded: function() {
			if (this.stylesCheckDiv.offsetHeight != 5) {
				return false;
			} else {
				return true;
			}
		},
		/* VideoJS Box - Holds all elements
		 ================================================================================ */
		positionAll: function() {
			this.positionBox();
			this.positionControlBars();
			this.positionPoster();
		},
		positionBox: function() {
			// Set width based on fullscreen or not.
			if (this.videoIsFullScreen) {
				this.box.style.width = "";
				this.element.style.height = "";
				if (this.options.controlsBelow) {
					this.box.style.height = "";
					this.element.style.height = (this.box.offsetHeight - this.controls.offsetHeight) + "px";
				}
			} else {
				this.box.style.width = this.width() + "px";
				this.element.style.height = this.height() + "px";
				if (this.options.controlsBelow) {
					this.element.style.height = "";
					// this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px";
				}
			}
		},
		/* Subtitles
		 ================================================================================ */
		getSubtitles: function() {
			var tracks = this.video.getElementsByTagName("TRACK");
			for (var i = 0, j = tracks.length; i < j; i++) {
				if (tracks[i].getAttribute("kind") == "subtitles" && tracks[i].getAttribute("src")) {
					this.subtitlesSource = tracks[i].getAttribute("src");
					this.loadSubtitles();
					this.buildSubtitles();
				}
			}
		},
		loadSubtitles: function() {
			_V_.get(this.subtitlesSource, this.parseSubtitles.context(this));
		},
		parseSubtitles: function(subText) {
			var lines = subText.split("\n"),
				line = "",
				subtitle, time, text;
			this.subtitles = [];
			this.currentSubtitle = false;
			this.lastSubtitleIndex = 0;

			for (var i = 0; i < lines.length; i++) {
					line = _V_.trim(lines[i]); // Trim whitespace and linebreaks
					if (line) { // Loop until a line with content
						// First line - Number
						subtitle = {
							id: line,
							// Subtitle Number
							index: this.subtitles.length // Position in Array
						};

						// Second line - Time
						line = _V_.trim(lines[++i]);
						time = line.split(" --> ");
						subtitle.start = this.parseSubtitleTime(time[0]);
						subtitle.end = this.parseSubtitleTime(time[1]);

						// Additional lines - Subtitle Text
						text = [];
						for (var j = i; j < lines.length; j++) { // Loop until a blank line or end of lines
							line = _V_.trim(lines[++i]);
							if (!line) {
								break;
							}
							text.push(line);
						}
						subtitle.text = text.join('<br/>');

						// Add this subtitle
						this.subtitles.push(subtitle);
					}
				}
		},

		parseSubtitleTime: function(timeText) {
			var parts = timeText.split(':'),
				time = 0;
			// hours => seconds
			time += parseFloat(parts[0]) * 60 * 60;
			// minutes => seconds
			time += parseFloat(parts[1]) * 60;
			// get seconds
			var seconds = parts[2].split(/\.|,/); // Either . or ,
			time += parseFloat(seconds[0]);
			// add miliseconds
			ms = parseFloat(seconds[1]);
			if (ms) {
					time += ms / 1000;
				}
			return time;
		},

		buildSubtitles: function() {
			/* Creating this HTML
			<div class="vjs-subtitles"></div>
*/
			this.subtitlesDisplay = _V_.createElement("div", {
				className: 'vjs-subtitles'
			});
			this.box.appendChild(this.subtitlesDisplay);
			this.activateElement(this.subtitlesDisplay, "subtitlesDisplay");
		},

		/* Player API - Translate functionality from player to video
		 ================================================================================ */
		addVideoListener: function(type, fn) {
			_V_.addListener(this.video, type, fn.rEvtContext(this));
		},

		play: function() {
			this.video.play();
			return this;
		},
		onPlay: function(fn) {
			this.addVideoListener("play", fn);
			return this;
		},

		pause: function() {
			this.video.pause();
			return this;
		},
		onPause: function(fn) {
			this.addVideoListener("pause", fn);
			return this;
		},
		paused: function() {
			return this.video.paused;
		},

		currentTime: function(seconds) {
			if (seconds !== undefined) {
				try {
					this.video.currentTime = seconds;
				}
				catch (e) {
					this.warning(VideoJS.warnings.videoNotReady);
				}
				this.values.currentTime = seconds;
				return this;
			}
			return this.video.currentTime;
		},
		onCurrentTimeUpdate: function(fn) {
			this.currentTimeListeners.push(fn);
		},

		duration: function() {
			return this.video.duration;
		},

		buffered: function() {
			// Storing values allows them be overridden by setBufferedFromProgress
			if (this.values.bufferStart === undefined) {
				this.values.bufferStart = 0;
				this.values.bufferEnd = 0;
			}
			if (this.video.buffered && this.video.buffered.length > 0) {
				var newEnd = this.video.buffered.end(0);
				if (newEnd > this.values.bufferEnd) {
					this.values.bufferEnd = newEnd;
				}
			}
			return [this.values.bufferStart, this.values.bufferEnd];
		},

		volume: function(percentAsDecimal) {
			if (percentAsDecimal !== undefined) {
				// Force value to between 0 and 1
				this.values.volume = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
				this.video.volume = this.values.volume;
				this.setLocalStorage("volume", this.values.volume);
				return this;
			}
			if (this.values.volume) {
				return this.values.volume;
			}
			return this.video.volume;
		},
		onVolumeChange: function(fn) {
			_V_.addListener(this.video, 'volumechange', fn.rEvtContext(this));
		},

		width: function(width) {
			if (width !== undefined) {
				this.video.width = width; // Not using style so it can be overridden on fullscreen.
				this.box.style.width = width + "px";
				this.triggerResizeListeners();
				return this;
			}
			return this.video.offsetWidth;
		},
		height: function(height) {
			if (height !== undefined) {
				this.video.height = height;
				this.box.style.height = height + "px";
				this.triggerResizeListeners();
				return this;
			}
			return this.video.offsetHeight;
		},

		supportsFullScreen: function() {
			if (typeof this.video.webkitEnterFullScreen == 'function') {
				// Seems to be broken in Chromium/Chrome
				if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
					return true;
				}
			}
			return false;
		},

		html5EnterNativeFullScreen: function() {
			try {
				this.video.webkitEnterFullScreen();
			} catch (e) {
				if (e.code == 11) {
					this.warning(VideoJS.warnings.videoNotReady);
				}
			}
			return this;
		},

		// Turn on fullscreen (window) mode
		// Real fullscreen isn't available in browsers quite yet.
		enterFullScreen: function() {
			if (this.supportsFullScreen()) {
				this.html5EnterNativeFullScreen();
			} else {
				this.enterFullWindow();
			}
		},

		exitFullScreen: function() {
			if (this.supportsFullScreen()) {
				// Shouldn't be called
			} else {
				this.exitFullWindow();
			}
		},

		enterFullWindow: function() {
			this.videoIsFullScreen = true;
			// Storing original doc overflow value to return to when fullscreen is off
			this.docOrigOverflow = document.documentElement.style.overflow;
			// Add listener for esc key to exit fullscreen
			_V_.addListener(document, "keydown", this.fullscreenOnEscKey.rEvtContext(this));
			// Add listener for a window resize
			_V_.addListener(window, "resize", this.fullscreenOnWindowResize.rEvtContext(this));
			// Hide any scroll bars
			document.documentElement.style.overflow = 'hidden';
			// Apply fullscreen styles
			_V_.addClass(this.box, "vjs-fullscreen");
			// Resize the box, controller, and poster
			this.positionAll();
		},

		// Turn off fullscreen (window) mode
		exitFullWindow: function() {
			this.videoIsFullScreen = false;
			document.removeEventListener("keydown", this.fullscreenOnEscKey, false);
			window.removeEventListener("resize", this.fullscreenOnWindowResize, false);
			// Unhide scroll bars.
			document.documentElement.style.overflow = this.docOrigOverflow;
			// Remove fullscreen styles
			_V_.removeClass(this.box, "vjs-fullscreen");
			// Resize the box, controller, and poster to original sizes
			this.positionAll();
		},

		onError: function(fn) {
			this.addVideoListener("error", fn);
			return this;
		},
		onEnded: function(fn) {
			this.addVideoListener("ended", fn);
			return this;
		}
	});

	////////////////////////////////////////////////////////////////////////////////
	// Element Behaviors
	// Tell elements how to act or react
	////////////////////////////////////////////////////////////////////////////////
	/* Player Behaviors - How VideoJS reacts to what the video is doing.
	 ================================================================================ */
	VideoJS.player.newBehavior("player", function(player) {
		this.onError(this.playerOnVideoError);
		// Listen for when the video is played
		this.onPlay(this.playerOnVideoPlay);
		this.onPlay(this.trackCurrentTime);
		// Listen for when the video is paused
		this.onPause(this.playerOnVideoPause);
		this.onPause(this.stopTrackingCurrentTime);
		// Listen for when the video ends
		this.onEnded(this.playerOnVideoEnded);
		// Set interval for load progress using buffer watching method
		// this.trackCurrentTime();
		this.trackBuffered();
		// Buffer Full
		this.onBufferedUpdate(this.isBufferFull);
	}, {
		playerOnVideoError: function(event) {
			this.log(event);
			this.log(this.video.error);
		},
		playerOnVideoPlay: function(event) {
			this.hasPlayed = true;
		},
		playerOnVideoPause: function(event) {},
		playerOnVideoEnded: function(event) {
			this.currentTime(0);
			this.pause();
		},

		/* Load Tracking -------------------------------------------------------------- */
		// Buffer watching method for load progress.
		// Used for browsers that don't support the progress event
		trackBuffered: function() {
			this.bufferedInterval = setInterval(this.triggerBufferedListeners.context(this), 500);
		},
		stopTrackingBuffered: function() {
			clearInterval(this.bufferedInterval);
		},
		bufferedListeners: [],
		onBufferedUpdate: function(fn) {
			this.bufferedListeners.push(fn);
		},
		triggerBufferedListeners: function() {
			this.isBufferFull();
			this.each(this.bufferedListeners, function(listener) {
				(listener.context(this))();
			});
		},
		isBufferFull: function() {
			if (this.bufferedPercent() == 1) {
				this.stopTrackingBuffered();
			}
		},

		/* Time Tracking -------------------------------------------------------------- */
		trackCurrentTime: function() {
			if (this.currentTimeInterval) {
				clearInterval(this.currentTimeInterval);
			}
			this.currentTimeInterval = setInterval(this.triggerCurrentTimeListeners.context(this), 100); // 42 = 24 fps
			this.trackingCurrentTime = true;
		},
		// Turn off play progress tracking (when paused or dragging)
		stopTrackingCurrentTime: function() {
			clearInterval(this.currentTimeInterval);
			this.trackingCurrentTime = false;
		},
		currentTimeListeners: [],
		// onCurrentTimeUpdate is in API section now
		triggerCurrentTimeListeners: function(late, newTime) { // FF passes milliseconds late as the first argument
			this.each(this.currentTimeListeners, function(listener) {
				(listener.context(this))(newTime || this.currentTime());
			});
		},

		/* Resize Tracking -------------------------------------------------------------- */
		resizeListeners: [],
		onResize: function(fn) {
			this.resizeListeners.push(fn);
		},
		// Trigger anywhere the video/box size is changed.
		triggerResizeListeners: function() {
			this.each(this.resizeListeners, function(listener) {
				(listener.context(this))();
			});
		}
	});
	/* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
	 ================================================================================ */
	VideoJS.player.newBehavior("mouseOverVideoReporter", function(element) {
		// Listen for the mouse move the video. Used to reveal the controller.
		_V_.addListener(element, "mousemove", this.mouseOverVideoReporterOnMouseMove.context(this));
		// Listen for the mouse moving out of the video. Used to hide the controller.
		_V_.addListener(element, "mouseout", this.mouseOverVideoReporterOnMouseOut.context(this));
	}, {
		mouseOverVideoReporterOnMouseMove: function() {
			this.showControlBars();
			clearInterval(this.mouseMoveTimeout);
			this.mouseMoveTimeout = setTimeout(this.hideControlBars.context(this), 4000);
		},
		mouseOverVideoReporterOnMouseOut: function(event) {
			// Prevent flicker by making sure mouse hasn't left the video
			var parent = event.relatedTarget;
			while (parent && parent !== this.box) {
				parent = parent.parentNode;
			}
			if (parent !== this.box) {
				this.hideControlBars();
			}
		}
	});
	/* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
	 ================================================================================ */
	VideoJS.player.newBehavior("box", function(element) {
		this.positionBox();
		_V_.addClass(element, "vjs-paused");
		this.activateElement(element, "mouseOverVideoReporter");
		this.onPlay(this.boxOnVideoPlay);
		this.onPause(this.boxOnVideoPause);
	}, {
		boxOnVideoPlay: function() {
			_V_.removeClass(this.box, "vjs-paused");
			_V_.addClass(this.box, "vjs-playing");
		},
		boxOnVideoPause: function() {
			_V_.removeClass(this.box, "vjs-playing");
			_V_.addClass(this.box, "vjs-paused");
		}
	});
	/* Poster Image Overlay
	 ================================================================================ */
	VideoJS.player.newBehavior("poster", function(element) {
		this.activateElement(element, "mouseOverVideoReporter");
		this.activateElement(element, "playButton");
		this.onPlay(this.hidePoster);
		this.onEnded(this.showPoster);
		this.onResize(this.positionPoster);
	}, {
		showPoster: function() {
			if (!this.poster) {
				return;
			}
			this.poster.style.display = "block";
			this.positionPoster();
		},
		positionPoster: function() {
			// Only if the poster is visible
			if (!this.poster || this.poster.style.display == 'none') {
				return;
			}
			this.poster.style.height = this.height() + "px"; // Need incase controlsBelow
			this.poster.style.width = this.width() + "px"; // Could probably do 100% of box
		},
		hidePoster: function() {
			if (!this.poster) {
				return;
			}
			this.poster.style.display = "none";
		},
		// Update poster source from attribute or fallback image
		// iPad breaks if you include a poster attribute, so this fixes that
		updatePosterSource: function() {
			if (!this.video.poster) {
				var images = this.video.getElementsByTagName("img");
				if (images.length > 0) {
					this.video.poster = images[0].src;
				}
			}
		}
	});
	/* Control Bar Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("controlBar", function(element) {
		if (!this.controlBars) {
			this.controlBars = [];
			this.onResize(this.positionControlBars);
		}
		this.controlBars.push(element);
		_V_.addListener(element, "mousemove", this.onControlBarsMouseMove.context(this));
		_V_.addListener(element, "mouseout", this.onControlBarsMouseOut.context(this));
	}, {
		showControlBars: function() {
			if (!this.options.controlsAtStart && !this.hasPlayed) {
				return;
			}
			this.each(this.controlBars, function(bar) {
				bar.style.display = "block";
			});
		},
		// Place controller relative to the video's position (now just resizing bars)
		positionControlBars: function() {
			this.updatePlayProgressBars();
			this.updateLoadProgressBars();
		},
		hideControlBars: function() {
			if (this.options.controlsHiding && !this.mouseIsOverControls) {
				this.each(this.controlBars, function(bar) {
					bar.style.display = "none";
				});
			}
		},
		// Block controls from hiding when mouse is over them.
		onControlBarsMouseMove: function() {
			this.mouseIsOverControls = true;
		},
		onControlBarsMouseOut: function(event) {
			this.mouseIsOverControls = false;
		}
	});
	/* PlayToggle, PlayButton, PauseButton Behaviors
	 ================================================================================ */
	// Play Toggle
	VideoJS.player.newBehavior("playToggle", function(element) {
		if (!this.elements.playToggles) {
			this.elements.playToggles = [];
			this.onPlay(this.playTogglesOnPlay);
			this.onPause(this.playTogglesOnPause);
		}
		this.elements.playToggles.push(element);
		_V_.addListener(element, "click", this.onPlayToggleClick.context(this));
	}, {
		onPlayToggleClick: function(event) {
			if (this.paused()) {
				this.play();
			} else {
				this.pause();
			}
		},
		playTogglesOnPlay: function(event) {
			this.each(this.elements.playToggles, function(toggle) {
				_V_.removeClass(toggle, "vjs-paused");
				_V_.addClass(toggle, "vjs-playing");
			});
		},
		playTogglesOnPause: function(event) {
			this.each(this.elements.playToggles, function(toggle) {
				_V_.removeClass(toggle, "vjs-playing");
				_V_.addClass(toggle, "vjs-paused");
			});
		}
	});
	// Play
	VideoJS.player.newBehavior("playButton", function(element) {
		_V_.addListener(element, "click", this.onPlayButtonClick.context(this));
	}, {
		onPlayButtonClick: function(event) {
			this.play();
		}
	});
	// Pause
	VideoJS.player.newBehavior("pauseButton", function(element) {
		_V_.addListener(element, "click", this.onPauseButtonClick.context(this));
	}, {
		onPauseButtonClick: function(event) {
			this.pause();
		}
	});
	/* Play Progress Bar Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("playProgressBar", function(element) {
		if (!this.playProgressBars) {
			this.playProgressBars = [];
			this.onCurrentTimeUpdate(this.updatePlayProgressBars);
		}
		this.playProgressBars.push(element);
	}, {
		// Ajust the play progress bar's width based on the current play time
		updatePlayProgressBars: function(newTime) {
			var progress = (newTime !== undefined) ? newTime / this.duration() : this.currentTime() / this.duration();
			if (isNaN(progress)) {
				progress = 0;
			}
			this.each(this.playProgressBars, function(bar) {
				if (bar.style) {
					bar.style.width = _V_.round(progress * 100, 2) + "%";
				}
			});
		}
	});
	/* Load Progress Bar Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("loadProgressBar", function(element) {
		if (!this.loadProgressBars) {
			this.loadProgressBars = [];
		}
		this.loadProgressBars.push(element);
		this.onBufferedUpdate(this.updateLoadProgressBars);
	}, {
		updateLoadProgressBars: function() {
			this.each(this.loadProgressBars, function(bar) {
				if (bar.style) {
					bar.style.width = _V_.round(this.bufferedPercent() * 100, 2) + "%";
				}
			});
		}
	});

	/* Current Time Display Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("currentTimeDisplay", function(element) {
		if (!this.currentTimeDisplays) {
			this.currentTimeDisplays = [];
			this.onCurrentTimeUpdate(this.updateCurrentTimeDisplays);
		}
		this.currentTimeDisplays.push(element);
	}, {
		// Update the displayed time (00:00)
		updateCurrentTimeDisplays: function(newTime) {
			if (!this.currentTimeDisplays) {
				return;
			}
			// Allows for smooth scrubbing, when player can't keep up.
			var time = (newTime) ? newTime : this.currentTime();
			this.each(this.currentTimeDisplays, function(dis) {
				dis.innerHTML = _V_.formatTime(time);
			});
		}
	});

	/* Duration Display Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("durationDisplay", function(element) {
		if (!this.durationDisplays) {
			this.durationDisplays = [];
			this.onCurrentTimeUpdate(this.updateDurationDisplays);
		}
		this.durationDisplays.push(element);
	}, {
		updateDurationDisplays: function() {
			if (!this.durationDisplays) {
				return;
			}
			this.each(this.durationDisplays, function(dis) {
				if (this.duration()) {
					dis.innerHTML = _V_.formatTime(this.duration());
				}
			});
		}
	});

	/* Current Time Scrubber Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("currentTimeScrubber", function(element) {
		_V_.addListener(element, "mousedown", this.onCurrentTimeScrubberMouseDown.rEvtContext(this));
	}, {
		// Adjust the play position when the user drags on the progress bar
		onCurrentTimeScrubberMouseDown: function(event, scrubber) {
			event.preventDefault();
			this.currentScrubber = scrubber;

			this.stopTrackingCurrentTime(); // Allows for smooth scrubbing
			this.videoWasPlaying = !this.paused();
			this.pause();

			_V_.blockTextSelection();
			this.setCurrentTimeWithScrubber(event);
			_V_.addListener(document, "mousemove", this.onCurrentTimeScrubberMouseMove.rEvtContext(this));
			_V_.addListener(document, "mouseup", this.onCurrentTimeScrubberMouseUp.rEvtContext(this));
		},
		onCurrentTimeScrubberMouseMove: function(event) { // Removeable
			this.setCurrentTimeWithScrubber(event);
		},
		onCurrentTimeScrubberMouseUp: function(event) { // Removeable
			_V_.unblockTextSelection();
			document.removeEventListener("mousemove", this.onCurrentTimeScrubberMouseMove, false);
			document.removeEventListener("mouseup", this.onCurrentTimeScrubberMouseUp, false);
			if (this.videoWasPlaying) {
				this.play();
				this.trackCurrentTime();
			}
		},
		setCurrentTimeWithScrubber: function(event) {
			var newProgress = _V_.getRelativePosition(event.pageX, this.currentScrubber);
			var newTime = newProgress * this.duration();
			this.triggerCurrentTimeListeners(0, newTime); // Allows for smooth scrubbing
			// Don't let video end while scrubbing.
			if (newTime == this.duration()) {
				newTime = newTime - 0.1;
			}
			this.currentTime(newTime);
		}
	});
	/* Volume Display Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("volumeDisplay", function(element) {
		if (!this.volumeDisplays) {
			this.volumeDisplays = [];
			this.onVolumeChange(this.updateVolumeDisplays);
		}
		this.volumeDisplays.push(element);
		this.updateVolumeDisplay(element); // Set the display to the initial volume
	}, {
		// Update the volume control display
		// Unique to these default controls. Uses borders to create the look of bars.
		updateVolumeDisplays: function() {
			if (!this.volumeDisplays) {
				return;
			}
			this.each(this.volumeDisplays, function(dis) {
				this.updateVolumeDisplay(dis);
			});
		},
		updateVolumeDisplay: function(display) {
			var volNum = Math.ceil(this.volume() * 6);
			this.each(display.children, function(child, num) {
				if (num < volNum) {
					_V_.addClass(child, "vjs-volume-level-on");
				} else {
					_V_.removeClass(child, "vjs-volume-level-on");
				}
			});
		}
	});
	/* Volume Scrubber Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("volumeScrubber", function(element) {
		_V_.addListener(element, "mousedown", this.onVolumeScrubberMouseDown.rEvtContext(this));
	}, {
		// Adjust the volume when the user drags on the volume control
		onVolumeScrubberMouseDown: function(event, scrubber) {
			// event.preventDefault();
			_V_.blockTextSelection();
			this.currentScrubber = scrubber;
			this.setVolumeWithScrubber(event);
			_V_.addListener(document, "mousemove", this.onVolumeScrubberMouseMove.rEvtContext(this));
			_V_.addListener(document, "mouseup", this.onVolumeScrubberMouseUp.rEvtContext(this));
		},
		onVolumeScrubberMouseMove: function(event) {
			this.setVolumeWithScrubber(event);
		},
		onVolumeScrubberMouseUp: function(event) {
			this.setVolumeWithScrubber(event);
			_V_.unblockTextSelection();
			document.removeEventListener("mousemove", this.onVolumeScrubberMouseMove, false);
			document.removeEventListener("mouseup", this.onVolumeScrubberMouseUp, false);
		},
		setVolumeWithScrubber: function(event) {
			var newVol = _V_.getRelativePosition(event.pageX, this.currentScrubber);
			this.volume(newVol);
		}
	});
	/* Fullscreen Toggle Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("fullscreenToggle", function(element) {
		_V_.addListener(element, "click", this.onFullscreenToggleClick.context(this));
	}, {
		// When the user clicks on the fullscreen button, update fullscreen setting
		onFullscreenToggleClick: function(event) {
			if (!this.videoIsFullScreen) {
				this.enterFullScreen();
			} else {
				this.exitFullScreen();
			}
		},

		fullscreenOnWindowResize: function(event) { // Removeable
			this.positionControlBars();
		},
		// Create listener for esc key while in full screen mode
		fullscreenOnEscKey: function(event) { // Removeable
			if (event.keyCode == 27) {
				this.exitFullScreen();
			}
		}
	});
	/* Big Play Button Behaviors
	 ================================================================================ */
	VideoJS.player.newBehavior("bigPlayButton", function(element) {
		if (!this.elements.bigPlayButtons) {
			this.elements.bigPlayButtons = [];
			this.onPlay(this.bigPlayButtonsOnPlay);
			this.onEnded(this.bigPlayButtonsOnEnded);
		}
		this.elements.bigPlayButtons.push(element);
		this.activateElement(element, "playButton");
	}, {
		bigPlayButtonsOnPlay: function(event) {
			this.hideBigPlayButtons();
		},
		bigPlayButtonsOnEnded: function(event) {
			this.showBigPlayButtons();
		},
		showBigPlayButtons: function() {
			this.each(this.elements.bigPlayButtons, function(element) {
				element.style.display = "block";
			});
		},
		hideBigPlayButtons: function() {
			this.each(this.elements.bigPlayButtons, function(element) {
				element.style.display = "none";
			});
		}
	});
	/* Spinner
	 ================================================================================ */
	VideoJS.player.newBehavior("spinner", function(element) {
		if (!this.spinners) {
			this.spinners = [];
			_V_.addListener(this.video, "loadeddata", this.spinnersOnVideoLoadedData.context(this));
			_V_.addListener(this.video, "loadstart", this.spinnersOnVideoLoadStart.context(this));
			_V_.addListener(this.video, "seeking", this.spinnersOnVideoSeeking.context(this));
			_V_.addListener(this.video, "seeked", this.spinnersOnVideoSeeked.context(this));
			_V_.addListener(this.video, "canplay", this.spinnersOnVideoCanPlay.context(this));
			_V_.addListener(this.video, "canplaythrough", this.spinnersOnVideoCanPlayThrough.context(this));
			_V_.addListener(this.video, "waiting", this.spinnersOnVideoWaiting.context(this));
			_V_.addListener(this.video, "stalled", this.spinnersOnVideoStalled.context(this));
			_V_.addListener(this.video, "suspend", this.spinnersOnVideoSuspend.context(this));
			_V_.addListener(this.video, "playing", this.spinnersOnVideoPlaying.context(this));
			_V_.addListener(this.video, "timeupdate", this.spinnersOnVideoTimeUpdate.context(this));
		}
		this.spinners.push(element);
	}, {
		showSpinners: function() {
			this.each(this.spinners, function(spinner) {
				spinner.style.display = "block";
			});
			clearInterval(this.spinnerInterval);
			this.spinnerInterval = setInterval(this.rotateSpinners.context(this), 100);
		},
		hideSpinners: function() {
			this.each(this.spinners, function(spinner) {
				spinner.style.display = "none";
			});
			clearInterval(this.spinnerInterval);
		},
		spinnersRotated: 0,
		rotateSpinners: function() {
			this.each(this.spinners, function(spinner) {
				// spinner.style.transform =       'scale(0.5) rotate('+this.spinnersRotated+'deg)';
				spinner.style.WebkitTransform = 'scale(0.5) rotate(' + this.spinnersRotated + 'deg)';
				spinner.style.MozTransform = 'scale(0.5) rotate(' + this.spinnersRotated + 'deg)';
			});
			if (this.spinnersRotated == 360) {
				this.spinnersRotated = 0;
			}
			this.spinnersRotated += 45;
		},
		spinnersOnVideoLoadedData: function(event) {
			this.hideSpinners();
		},
		spinnersOnVideoLoadStart: function(event) {
			this.showSpinners();
		},
		spinnersOnVideoSeeking: function(event) { /* this.showSpinners(); */
		},
		spinnersOnVideoSeeked: function(event) { /* this.hideSpinners(); */
		},
		spinnersOnVideoCanPlay: function(event) { /* this.hideSpinners(); */
		},
		spinnersOnVideoCanPlayThrough: function(event) {
			this.hideSpinners();
		},
		spinnersOnVideoWaiting: function(event) {
			// Safari sometimes triggers waiting inappropriately
			// Like after video has played, any you play again.
			this.showSpinners();
		},
		spinnersOnVideoStalled: function(event) {},
		spinnersOnVideoSuspend: function(event) {},
		spinnersOnVideoPlaying: function(event) {
			this.hideSpinners();
		},
		spinnersOnVideoTimeUpdate: function(event) {
			// Safari sometimes calls waiting and doesn't recover
			if (this.spinner.style.display == "block") {
				this.hideSpinners();
			}
		}
	});
	/* Subtitles
	 ================================================================================ */
	VideoJS.player.newBehavior("subtitlesDisplay", function(element) {
		if (!this.subtitleDisplays) {
			this.subtitleDisplays = [];
			this.onCurrentTimeUpdate(this.subtitleDisplaysOnVideoTimeUpdate);
			this.onEnded(function() {
				this.lastSubtitleIndex = 0;
			}.context(this));
		}
		this.subtitleDisplays.push(element);
	}, {
		subtitleDisplaysOnVideoTimeUpdate: function(time) {
			// Assuming all subtitles are in order by time, and do not overlap
			if (this.subtitles) {
				// If current subtitle should stay showing, don't do anything. Otherwise, find new subtitle.
				if (!this.currentSubtitle || this.currentSubtitle.start >= time || this.currentSubtitle.end < time) {
					var newSubIndex = false,
					// Loop in reverse if lastSubtitle is after current time (optimization)
					// Meaning the user is scrubbing in reverse or rewinding
					reverse = (this.subtitles[this.lastSubtitleIndex].start > time),
					// If reverse, step back 1 becase we know it's not the lastSubtitle
					i = this.lastSubtitleIndex - (reverse) ? 1 : 0;
					while (true) { // Loop until broken
							if (reverse) { // Looping in reverse
								// Stop if no more, or this subtitle ends before the current time (no earlier subtitles should apply)
								if (i < 0 || this.subtitles[i].end < time) {
									break;
								}
								// End is greater than time, so if start is less, show this subtitle
								if (this.subtitles[i].start < time) {
									newSubIndex = i;
									break;
								}
								i--;
							} else { // Looping forward
								// Stop if no more, or this subtitle starts after time (no later subtitles should apply)
								if (i >= this.subtitles.length || this.subtitles[i].start > time) {
									break;
								}
								// Start is less than time, so if end is later, show this subtitle
								if (this.subtitles[i].end > time) {
									newSubIndex = i;
									break;
								}
								i++;
							}
						}

					// Set or clear current subtitle
					if (newSubIndex !== false) {
							this.currentSubtitle = this.subtitles[newSubIndex];
							this.lastSubtitleIndex = newSubIndex;
							this.updateSubtitleDisplays(this.currentSubtitle.text);
						} else if (this.currentSubtitle) {
							this.currentSubtitle = false;
							this.updateSubtitleDisplays("");
						}
				}
			}
		},
		updateSubtitleDisplays: function(val) {
			this.each(this.subtitleDisplays, function(disp) {
				disp.innerHTML = val;
			});
		}
	});

	////////////////////////////////////////////////////////////////////////////////
	// Convenience Functions (mini library)
	// Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
	////////////////////////////////////////////////////////////////////////////////
	VideoJS.extend({

		addClass: function(element, classToAdd) {
			if ((" " + element.className + " ").indexOf(" " + classToAdd + " ") == -1) {
				element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd;
			}
		},
		removeClass: function(element, classToRemove) {
			if (element.className.indexOf(classToRemove) == -1) {
				return;
			}
			var classNames = element.className.split(/\s+/);
			classNames.splice(classNames.lastIndexOf(classToRemove), 1);
			element.className = classNames.join(" ");
		},
		createElement: function(tagName, attributes) {
			return this.merge(document.createElement(tagName), attributes);
		},

		// Attempt to block the ability to select text while dragging controls
		blockTextSelection: function() {
			document.body.focus();
			document.onselectstart = function() {
				return false;
			};
		},
		// Turn off text selection blocking
		unblockTextSelection: function() {
			document.onselectstart = function() {
				return true;
			};
		},

		// Return seconds as MM:SS
		formatTime: function(secs) {
			var seconds = Math.round(secs);
			var minutes = Math.floor(seconds / 60);
			minutes = (minutes >= 10) ? minutes : "0" + minutes;
			seconds = Math.floor(seconds % 60);
			seconds = (seconds >= 10) ? seconds : "0" + seconds;
			return minutes + ":" + seconds;
		},

		// Return the relative horizonal position of an event as a value from 0-1
		getRelativePosition: function(x, relativeElement) {
			return Math.max(0, Math.min(1, (x - this.findPosX(relativeElement)) / relativeElement.offsetWidth));
		},
		// Get an objects position on the page
		findPosX: function(obj) {
			var curleft = obj.offsetLeft;
			while (obj = obj.offsetParent) {
				curleft += obj.offsetLeft;
			}
			return curleft;
		},
		getComputedStyleValue: function(element, style) {
			return window.getComputedStyle(element, null).getPropertyValue(style);
		},

		round: function(num, dec) {
			if (!dec) {
				dec = 0;
			}
			return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
		},

		addListener: function(element, type, handler) {
			if (element.addEventListener) {
				element.addEventListener(type, handler, false);
			} else if (element.attachEvent) {
				element.attachEvent("on" + type, handler);
			}
		},
		removeListener: function(element, type, handler) {
			if (element.removeEventListener) {
				element.removeEventListener(type, handler, false);
			} else if (element.attachEvent) {
				element.detachEvent("on" + type, handler);
			}
		},

		get: function(url, onSuccess) {
			if (typeof XMLHttpRequest == "undefined") {
				XMLHttpRequest = function() {
					try {
						return new ActiveXObject("Msxml2.XMLHTTP.6.0");
					} catch (e) {}
					try {
						return new ActiveXObject("Msxml2.XMLHTTP.3.0");
					} catch (f) {}
					try {
						return new ActiveXObject("Msxml2.XMLHTTP");
					} catch (g) {}
					//Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
					throw new Error("This browser does not support XMLHttpRequest.");
				};
			}
			var request = new XMLHttpRequest();
			request.open("GET", url);
			request.onreadystatechange = function() {
				if (request.readyState == 4 && request.status == 200) {
					onSuccess(request.responseText);
				}
			}.context(this);
			request.send();
		},

		trim: function(string) {
			return string.toString().replace(/^\s+/, "").replace(/\s+$/, "");
		},

		// DOM Ready functionality adapted from jQuery. http://jquery.com/
		bindDOMReady: function() {
			if (document.readyState === "complete") {
				return VideoJS.onDOMReady();
			}
			if (document.addEventListener) {
				document.addEventListener("DOMContentLoaded", VideoJS.DOMContentLoaded, false);
				window.addEventListener("load", VideoJS.onDOMReady, false);
			} else if (document.attachEvent) {
				document.attachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
				window.attachEvent("onload", VideoJS.onDOMReady);
			}
		},

		DOMContentLoaded: function() {
			if (document.addEventListener) {
				document.removeEventListener("DOMContentLoaded", VideoJS.DOMContentLoaded, false);
				VideoJS.onDOMReady();
			} else if (document.attachEvent) {
				if (document.readyState === "complete") {
					document.detachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
					VideoJS.onDOMReady();
				}
			}
		},

		// Functions to be run once the DOM is loaded
		DOMReadyList: [],
		addToDOMReady: function(fn) {
			if (VideoJS.DOMIsReady) {
				fn.call(document);
			} else {
				VideoJS.DOMReadyList.push(fn);
			}
		},

		DOMIsReady: false,
		onDOMReady: function() {
			if (VideoJS.DOMIsReady) {
				return;
			}
			if (!document.body) {
				return setTimeout(VideoJS.onDOMReady, 13);
			}
			VideoJS.DOMIsReady = true;
			if (VideoJS.DOMReadyList) {
				for (var i = 0; i < VideoJS.DOMReadyList.length; i++) {
					VideoJS.DOMReadyList[i].call(document);
				}
				VideoJS.DOMReadyList = null;
			}
		}
	});
	VideoJS.bindDOMReady();

	// Allows for binding context to functions
	// when using in event listeners and timeouts
	Function.prototype.context = function(obj) {
		var method = this,
			temp = function() {
				return method.apply(obj, arguments);
			};
		return temp;
	};

	// Like context, in that it creates a closure
	// But insteaad keep "this" intact, and passes the var as the second argument of the function
	// Need for event listeners where you need to know what called the event
	// Only use with event callbacks
	Function.prototype.evtContext = function(obj) {
		var method = this,
			temp = function() {
				var origContext = this;
				return method.call(obj, arguments[0], origContext);
			};
		return temp;
	};

	// Removeable Event listener with Context
	// Replaces the original function with a version that has context
	// So it can be removed using the original function name.
	// In order to work, a version of the function must already exist in the player/prototype
	Function.prototype.rEvtContext = function(obj, funcParent) {
		if (this.hasContext === true) {
			return this;
		}
		if (!funcParent) {
			funcParent = obj;
		}
		for (var attrname in funcParent) {
			if (funcParent[attrname] == this) {
				funcParent[attrname] = this.evtContext(obj);
				funcParent[attrname].hasContext = true;
				return funcParent[attrname];
			}
		}
		return this.evtContext(obj);
	};

	// jQuery Plugin
	if (window.jQuery) {
		(function($) {
			$.fn.VideoJS = function(options) {
				this.each(function() {
					VideoJS.setup(this, options);
				});
				return this;
			};
			$.fn.player = function() {
				return this[0].player;
			};
		})(jQuery);
	}


	// Expose to global
	window.VideoJS = window._V_ = VideoJS;

	// End self-executing function
})(window);
