1/*
2 * jPlayer Plugin for jQuery JavaScript Library
3 * http://www.jplayer.org
4 *
5 * Copyright (c) 2009 - 2014 Happyworm Ltd
6 * Licensed under the MIT license.
7 * http://opensource.org/licenses/MIT
8 *
9 * Author: Mark J Panaghiston
10 * Version: 2.9.2
11 * Date: 14th December 2014
12 */
13
14/* Support for Zepto 1.0 compiled with optional data module.
15 * For AMD or NODE/CommonJS support, you will need to manually switch the related 2 lines in the code below.
16 * Search terms: "jQuery Switch" and "Zepto Switch"
17 */
18
19(function (root, factory) {
20	if (typeof define === 'function' && define.amd) {
21		// AMD. Register as an anonymous module.
22		define(['jquery'], factory); // jQuery Switch
23		// define(['zepto'], factory); // Zepto Switch
24	} else if (typeof exports === 'object') {
25		// Node/CommonJS
26		factory(require('jquery')); // jQuery Switch
27		//factory(require('zepto')); // Zepto Switch
28	} else {
29		// Browser globals
30		if(root.jQuery) { // Use jQuery if available
31			factory(root.jQuery);
32		} else { // Otherwise, use Zepto
33			factory(root.Zepto);
34		}
35	}
36}(this, function ($, undefined) {
37
38	// Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge - Tweaked $.data(this,XYZ) to $(this).data(XYZ) for Zepto
39	$.fn.jPlayer = function( options ) {
40		var name = "jPlayer";
41		var isMethodCall = typeof options === "string",
42			args = Array.prototype.slice.call( arguments, 1 ),
43			returnValue = this;
44
45		// allow multiple hashes to be passed on init
46		options = !isMethodCall && args.length ?
47			$.extend.apply( null, [ true, options ].concat(args) ) :
48			options;
49
50		// prevent calls to internal methods
51		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
52			return returnValue;
53		}
54
55		if ( isMethodCall ) {
56			this.each(function() {
57				var instance = $(this).data( name ),
58					methodValue = instance && $.isFunction( instance[options] ) ?
59						instance[ options ].apply( instance, args ) :
60						instance;
61				if ( methodValue !== instance && methodValue !== undefined ) {
62					returnValue = methodValue;
63					return false;
64				}
65			});
66		} else {
67			this.each(function() {
68				var instance = $(this).data( name );
69				if ( instance ) {
70					// instance.option( options || {} )._init(); // Orig jquery.ui.widget.js code: Not recommend for jPlayer. ie., Applying new options to an existing instance (via the jPlayer constructor) and performing the _init(). The _init() is what concerns me. It would leave a lot of event handlers acting on jPlayer instance and the interface.
71					instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
72				} else {
73					$(this).data( name, new $.jPlayer( options, this ) );
74				}
75			});
76		}
77
78		return returnValue;
79	};
80
81	$.jPlayer = function( options, element ) {
82		// allow instantiation without initializing for simple inheritance
83		if ( arguments.length ) {
84			this.element = $(element);
85			this.options = $.extend(true, {},
86				this.options,
87				options
88			);
89			var self = this;
90			this.element.bind( "remove.jPlayer", function() {
91				self.destroy();
92			});
93			this._init();
94		}
95	};
96	// End of: (Adapted from jquery.ui.widget.js (1.8.7))
97
98	// Zepto is missing one of the animation methods.
99	if(typeof $.fn.stop !== 'function') {
100		$.fn.stop = function() {};
101	}
102
103	// Emulated HTML5 methods and properties
104	$.jPlayer.emulateMethods = "load play pause";
105	$.jPlayer.emulateStatus = "src readyState networkState currentTime duration paused ended playbackRate";
106	$.jPlayer.emulateOptions = "muted volume";
107
108	// Reserved event names generated by jPlayer that are not part of the HTML5 Media element spec
109	$.jPlayer.reservedEvent = "ready flashreset resize repeat error warning";
110
111	// Events generated by jPlayer
112	$.jPlayer.event = {};
113	$.each(
114		[
115			'ready',
116			'setmedia', // Fires when the media is set
117			'flashreset', // Similar to the ready event if the Flash solution is set to display:none and then shown again or if it's reloaded for another reason by the browser. For example, using CSS position:fixed on Firefox for the full screen feature.
118			'resize', // Occurs when the size changes through a full/restore screen operation or if the size/sizeFull options are changed.
119			'repeat', // Occurs when the repeat status changes. Usually through clicks on the repeat button of the interface.
120			'click', // Occurs when the user clicks on one of the following: poster image, html video, flash video.
121			'error', // Event error code in event.jPlayer.error.type. See $.jPlayer.error
122			'warning', // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning
123
124			// Other events match HTML5 spec.
125			'loadstart',
126			'progress',
127			'suspend',
128			'abort',
129			'emptied',
130			'stalled',
131			'play',
132			'pause',
133			'loadedmetadata',
134			'loadeddata',
135			'waiting',
136			'playing',
137			'canplay',
138			'canplaythrough',
139			'seeking',
140			'seeked',
141			'timeupdate',
142			'ended',
143			'ratechange',
144			'durationchange',
145			'volumechange'
146		],
147		function() {
148			$.jPlayer.event[ this ] = 'jPlayer_' + this;
149		}
150	);
151
152	$.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
153		"loadstart",
154		// "progress", // jPlayer uses internally before bubbling.
155		// "suspend", // jPlayer uses internally before bubbling.
156		"abort",
157		// "error", // jPlayer uses internally before bubbling.
158		"emptied",
159		"stalled",
160		// "play", // jPlayer uses internally before bubbling.
161		// "pause", // jPlayer uses internally before bubbling.
162		"loadedmetadata",
163		// "loadeddata", // jPlayer uses internally before bubbling.
164		// "waiting", // jPlayer uses internally before bubbling.
165		// "playing", // jPlayer uses internally before bubbling.
166		"canplay",
167		"canplaythrough"
168		// "seeking", // jPlayer uses internally before bubbling.
169		// "seeked", // jPlayer uses internally before bubbling.
170		// "timeupdate", // jPlayer uses internally before bubbling.
171		// "ended", // jPlayer uses internally before bubbling.
172		// "ratechange" // jPlayer uses internally before bubbling.
173		// "durationchange" // jPlayer uses internally before bubbling.
174		// "volumechange" // jPlayer uses internally before bubbling.
175	];
176
177	$.jPlayer.pause = function() {
178		$.jPlayer.prototype.destroyRemoved();
179		$.each($.jPlayer.prototype.instances, function(i, element) {
180			if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
181				element.jPlayer("pause");
182			}
183		});
184	};
185
186	// Default for jPlayer option.timeFormat
187	$.jPlayer.timeFormat = {
188		showHour: false,
189		showMin: true,
190		showSec: true,
191		padHour: false,
192		padMin: true,
193		padSec: true,
194		sepHour: ":",
195		sepMin: ":",
196		sepSec: ""
197	};
198	var ConvertTime = function() {
199		this.init();
200	};
201	ConvertTime.prototype = {
202		init: function() {
203			this.options = {
204				timeFormat: $.jPlayer.timeFormat
205			};
206		},
207		time: function(s) { // function used on jPlayer.prototype._convertTime to enable per instance options.
208			s = (s && typeof s === 'number') ? s : 0;
209
210			var myTime = new Date(s * 1000),
211				hour = myTime.getUTCHours(),
212				min = this.options.timeFormat.showHour ? myTime.getUTCMinutes() : myTime.getUTCMinutes() + hour * 60,
213				sec = this.options.timeFormat.showMin ? myTime.getUTCSeconds() : myTime.getUTCSeconds() + min * 60,
214				strHour = (this.options.timeFormat.padHour && hour < 10) ? "0" + hour : hour,
215				strMin = (this.options.timeFormat.padMin && min < 10) ? "0" + min : min,
216				strSec = (this.options.timeFormat.padSec && sec < 10) ? "0" + sec : sec,
217				strTime = "";
218
219			strTime += this.options.timeFormat.showHour ? strHour + this.options.timeFormat.sepHour : "";
220			strTime += this.options.timeFormat.showMin ? strMin + this.options.timeFormat.sepMin : "";
221			strTime += this.options.timeFormat.showSec ? strSec + this.options.timeFormat.sepSec : "";
222
223			return strTime;
224		}
225	};
226	var myConvertTime = new ConvertTime();
227	$.jPlayer.convertTime = function(s) {
228		return myConvertTime.time(s);
229	};
230
231	// Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
232	$.jPlayer.uaBrowser = function( userAgent ) {
233		var ua = userAgent.toLowerCase();
234
235		// Useragent RegExp
236		var rwebkit = /(webkit)[ \/]([\w.]+)/;
237		var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
238		var rmsie = /(msie) ([\w.]+)/;
239		var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
240
241		var match = rwebkit.exec( ua ) ||
242			ropera.exec( ua ) ||
243			rmsie.exec( ua ) ||
244			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
245			[];
246
247		return { browser: match[1] || "", version: match[2] || "0" };
248	};
249
250	// Platform sniffer for detecting mobile devices
251	$.jPlayer.uaPlatform = function( userAgent ) {
252		var ua = userAgent.toLowerCase();
253
254		// Useragent RegExp
255		var rplatform = /(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/;
256		var rtablet = /(ipad|playbook)/;
257		var randroid = /(android)/;
258		var rmobile = /(mobile)/;
259
260		var platform = rplatform.exec( ua ) || [];
261		var tablet = rtablet.exec( ua ) ||
262			!rmobile.exec( ua ) && randroid.exec( ua ) ||
263			[];
264
265		if(platform[1]) {
266			platform[1] = platform[1].replace(/\s/g, "_"); // Change whitespace to underscore. Enables dot notation.
267		}
268
269		return { platform: platform[1] || "", tablet: tablet[1] || "" };
270	};
271
272	$.jPlayer.browser = {
273	};
274	$.jPlayer.platform = {
275	};
276
277	var browserMatch = $.jPlayer.uaBrowser(navigator.userAgent);
278	if ( browserMatch.browser ) {
279		$.jPlayer.browser[ browserMatch.browser ] = true;
280		$.jPlayer.browser.version = browserMatch.version;
281	}
282	var platformMatch = $.jPlayer.uaPlatform(navigator.userAgent);
283	if ( platformMatch.platform ) {
284		$.jPlayer.platform[ platformMatch.platform ] = true;
285		$.jPlayer.platform.mobile = !platformMatch.tablet;
286		$.jPlayer.platform.tablet = !!platformMatch.tablet;
287	}
288
289	// Internet Explorer (IE) Browser Document Mode Sniffer. Based on code at:
290	// http://msdn.microsoft.com/en-us/library/cc288325%28v=vs.85%29.aspx#GetMode
291	$.jPlayer.getDocMode = function() {
292		var docMode;
293		if ($.jPlayer.browser.msie) {
294			if (document.documentMode) { // IE8 or later
295				docMode = document.documentMode;
296			} else { // IE 5-7
297				docMode = 5; // Assume quirks mode unless proven otherwise
298				if (document.compatMode) {
299					if (document.compatMode === "CSS1Compat") {
300						docMode = 7; // standards mode
301					}
302				}
303			}
304		}
305		return docMode;
306	};
307	$.jPlayer.browser.documentMode = $.jPlayer.getDocMode();
308
309	$.jPlayer.nativeFeatures = {
310		init: function() {
311
312			/* Fullscreen function naming influenced by W3C naming.
313			 * No support for: Mozilla Proposal: https://wiki.mozilla.org/Gecko:FullScreenAPI
314			 */
315
316			var d = document,
317				v = d.createElement('video'),
318				spec = {
319					// http://www.w3.org/TR/fullscreen/
320					w3c: [
321						'fullscreenEnabled',
322						'fullscreenElement',
323						'requestFullscreen',
324						'exitFullscreen',
325						'fullscreenchange',
326						'fullscreenerror'
327					],
328					// https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
329					moz: [
330						'mozFullScreenEnabled',
331						'mozFullScreenElement',
332						'mozRequestFullScreen',
333						'mozCancelFullScreen',
334						'mozfullscreenchange',
335						'mozfullscreenerror'
336					],
337					// http://developer.apple.com/library/safari/#documentation/WebKit/Reference/ElementClassRef/Element/Element.html
338					// http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html
339					webkit: [
340						'',
341						'webkitCurrentFullScreenElement',
342						'webkitRequestFullScreen',
343						'webkitCancelFullScreen',
344						'webkitfullscreenchange',
345						''
346					],
347					// http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html
348					// https://developer.apple.com/library/safari/samplecode/HTML5VideoEventFlow/Listings/events_js.html#//apple_ref/doc/uid/DTS40010085-events_js-DontLinkElementID_5
349					// Events: 'webkitbeginfullscreen' and 'webkitendfullscreen'
350					webkitVideo: [
351						'webkitSupportsFullscreen',
352						'webkitDisplayingFullscreen',
353						'webkitEnterFullscreen',
354						'webkitExitFullscreen',
355						'',
356						''
357					],
358					ms: [
359						'',
360						'msFullscreenElement',
361						'msRequestFullscreen',
362						'msExitFullscreen',
363						'MSFullscreenChange',
364						'MSFullscreenError'
365					]
366				},
367				specOrder = [
368					'w3c',
369					'moz',
370					'webkit',
371					'webkitVideo',
372					'ms'
373				],
374				fs, i, il;
375
376			this.fullscreen = fs = {
377				support: {
378					w3c: !!d[spec.w3c[0]],
379					moz: !!d[spec.moz[0]],
380					webkit: typeof d[spec.webkit[3]] === 'function',
381					webkitVideo: typeof v[spec.webkitVideo[2]] === 'function',
382					ms: typeof v[spec.ms[2]] === 'function'
383				},
384				used: {}
385			};
386
387			// Store the name of the spec being used and as a handy boolean.
388			for(i = 0, il = specOrder.length; i < il; i++) {
389				var n = specOrder[i];
390				if(fs.support[n]) {
391					fs.spec = n;
392					fs.used[n] = true;
393					break;
394				}
395			}
396
397			if(fs.spec) {
398				var s = spec[fs.spec];
399				fs.api = {
400					fullscreenEnabled: true,
401					fullscreenElement: function(elem) {
402						elem = elem ? elem : d; // Video element required for webkitVideo
403						return elem[s[1]];
404					},
405					requestFullscreen: function(elem) {
406						return elem[s[2]](); // Chrome and Opera want parameter (Element.ALLOW_KEYBOARD_INPUT) but Safari fails if flag used.
407					},
408					exitFullscreen: function(elem) {
409						elem = elem ? elem : d; // Video element required for webkitVideo
410						return elem[s[3]]();
411					}
412				};
413				fs.event = {
414					fullscreenchange: s[4],
415					fullscreenerror: s[5]
416				};
417			} else {
418				fs.api = {
419					fullscreenEnabled: false,
420					fullscreenElement: function() {
421						return null;
422					},
423					requestFullscreen: function() {},
424					exitFullscreen: function() {}
425				};
426				fs.event = {};
427			}
428		}
429	};
430	$.jPlayer.nativeFeatures.init();
431
432	// The keyboard control system.
433
434	// The current jPlayer instance in focus.
435	$.jPlayer.focus = null;
436
437	// The list of element node names to ignore with key controls.
438	$.jPlayer.keyIgnoreElementNames = "A INPUT TEXTAREA SELECT BUTTON";
439
440	// The function that deals with key presses.
441	var keyBindings = function(event) {
442		var f = $.jPlayer.focus,
443			ignoreKey;
444
445		// A jPlayer instance must be in focus. ie., keyEnabled and the last one played.
446		if(f) {
447			// What generated the key press?
448			$.each( $.jPlayer.keyIgnoreElementNames.split(/\s+/g), function(i, name) {
449				// The strings should already be uppercase.
450				if(event.target.nodeName.toUpperCase() === name.toUpperCase()) {
451					ignoreKey = true;
452					return false; // exit each.
453				}
454			});
455			if(!ignoreKey) {
456				// See if the key pressed matches any of the bindings.
457				$.each(f.options.keyBindings, function(action, binding) {
458					// The binding could be a null when the default has been disabled. ie., 1st clause in if()
459					if(
460						(binding && $.isFunction(binding.fn)) &&
461						((typeof binding.key === 'number' && event.which === binding.key) ||
462						(typeof binding.key === 'string' && event.key === binding.key))
463					) {
464						event.preventDefault(); // Key being used by jPlayer, so prevent default operation.
465						binding.fn(f);
466						return false; // exit each.
467					}
468				});
469			}
470		}
471	};
472
473	$.jPlayer.keys = function(en) {
474		var event = "keydown.jPlayer";
475		// Remove any binding, just in case enabled more than once.
476		$(document.documentElement).unbind(event);
477		if(en) {
478			$(document.documentElement).bind(event, keyBindings);
479		}
480	};
481
482	// Enable the global key control handler ready for any jPlayer instance with the keyEnabled option enabled.
483	$.jPlayer.keys(true);
484
485	$.jPlayer.prototype = {
486		count: 0, // Static Variable: Change it via prototype.
487		version: { // Static Object
488			script: "2.9.2",
489			needFlash: "2.9.0",
490			flash: "unknown"
491		},
492		options: { // Instanced in $.jPlayer() constructor
493			swfPath: "js", // Path to jquery.jplayer.swf. Can be relative, absolute or server root relative.
494			solution: "html, flash", // Valid solutions: html, flash, aurora. Order defines priority. 1st is highest,
495			supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
496			auroraFormats: "wav", // List the aurora.js codecs being loaded externally. Its core supports "wav". Specify format in jPlayer context. EG., The aac.js codec gives the "m4a" format.
497			preload: 'metadata',  // HTML5 Spec values: none, metadata, auto.
498			volume: 0.8, // The volume. Number 0 to 1.
499			muted: false,
500			remainingDuration: false, // When true, the remaining time is shown in the duration GUI element.
501			toggleDuration: false, // When true, clicks on the duration toggle between the duration and remaining display.
502			captureDuration: true, // When true, clicks on the duration are captured and no longer propagate up the DOM.
503			playbackRate: 1,
504			defaultPlaybackRate: 1,
505			minPlaybackRate: 0.5,
506			maxPlaybackRate: 4,
507			wmode: "opaque", // Valid wmode: window, transparent, opaque, direct, gpu.
508			backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
509			cssSelectorAncestor: "#jp_container_1",
510			cssSelector: { // * denotes properties that should only be required when video media type required. _cssSelector() would require changes to enable splitting these into Audio and Video defaults.
511				videoPlay: ".jp-video-play", // *
512				play: ".jp-play",
513				pause: ".jp-pause",
514				stop: ".jp-stop",
515				seekBar: ".jp-seek-bar",
516				playBar: ".jp-play-bar",
517				mute: ".jp-mute",
518				unmute: ".jp-unmute",
519				volumeBar: ".jp-volume-bar",
520				volumeBarValue: ".jp-volume-bar-value",
521				volumeMax: ".jp-volume-max",
522				playbackRateBar: ".jp-playback-rate-bar",
523				playbackRateBarValue: ".jp-playback-rate-bar-value",
524				currentTime: ".jp-current-time",
525				duration: ".jp-duration",
526				title: ".jp-title",
527				fullScreen: ".jp-full-screen", // *
528				restoreScreen: ".jp-restore-screen", // *
529				repeat: ".jp-repeat",
530				repeatOff: ".jp-repeat-off",
531				gui: ".jp-gui", // The interface used with autohide feature.
532				noSolution: ".jp-no-solution" // For error feedback when jPlayer cannot find a solution.
533			},
534			stateClass: { // Classes added to the cssSelectorAncestor to indicate the state.
535				playing: "jp-state-playing",
536				seeking: "jp-state-seeking",
537				muted: "jp-state-muted",
538				looped: "jp-state-looped",
539				fullScreen: "jp-state-full-screen",
540				noVolume: "jp-state-no-volume"
541			},
542			useStateClassSkin: false, // A state class skin relies on the state classes to change the visual appearance. The single control toggles the effect, for example: play then pause, mute then unmute.
543			autoBlur: true, // GUI control handlers will drop focus after clicks.
544			smoothPlayBar: false, // Smooths the play bar transitions, which affects clicks and short media with big changes per second.
545			fullScreen: false, // Native Full Screen
546			fullWindow: false,
547			autohide: {
548				restored: false, // Controls the interface autohide feature.
549				full: true, // Controls the interface autohide feature.
550				fadeIn: 200, // Milliseconds. The period of the fadeIn anim.
551				fadeOut: 600, // Milliseconds. The period of the fadeOut anim.
552				hold: 1000 // Milliseconds. The period of the pause before autohide beings.
553			},
554			loop: false,
555			repeat: function(event) { // The default jPlayer repeat event handler
556				if(event.jPlayer.options.loop) {
557					$(this).unbind(".jPlayerRepeat").bind($.jPlayer.event.ended + ".jPlayer.jPlayerRepeat", function() {
558						$(this).jPlayer("play");
559					});
560				} else {
561					$(this).unbind(".jPlayerRepeat");
562				}
563			},
564			nativeVideoControls: {
565				// Works well on standard browsers.
566				// Phone and tablet browsers can have problems with the controls disappearing.
567			},
568			noFullWindow: {
569				msie: /msie [0-6]\./,
570				ipad: /ipad.*?os [0-4]\./,
571				iphone: /iphone/,
572				ipod: /ipod/,
573				android_pad: /android [0-3]\.(?!.*?mobile)/,
574				android_phone: /(?=.*android)(?!.*chrome)(?=.*mobile)/,
575				blackberry: /blackberry/,
576				windows_ce: /windows ce/,
577				iemobile: /iemobile/,
578				webos: /webos/
579			},
580			noVolume: {
581				ipad: /ipad/,
582				iphone: /iphone/,
583				ipod: /ipod/,
584				android_pad: /android(?!.*?mobile)/,
585				android_phone: /android.*?mobile/,
586				blackberry: /blackberry/,
587				windows_ce: /windows ce/,
588				iemobile: /iemobile/,
589				webos: /webos/,
590				playbook: /playbook/
591			},
592			timeFormat: {
593				// Specific time format for this instance. The supported options are defined in $.jPlayer.timeFormat
594				// For the undefined options we use the default from $.jPlayer.timeFormat
595			},
596			keyEnabled: false, // Enables keyboard controls.
597			audioFullScreen: false, // Enables keyboard controls to enter full screen with audio media.
598			keyBindings: { // The key control object, defining the key codes and the functions to execute.
599				// The parameter, f = $.jPlayer.focus, will be checked truethy before attempting to call any of these functions.
600				// Properties may be added to this object, in key/fn pairs, to enable other key controls. EG, for the playlist add-on.
601				play: {
602					key: 80, // p
603					fn: function(f) {
604						if(f.status.paused) {
605							f.play();
606						} else {
607							f.pause();
608						}
609					}
610				},
611				fullScreen: {
612					key: 70, // f
613					fn: function(f) {
614						if(f.status.video || f.options.audioFullScreen) {
615							f._setOption("fullScreen", !f.options.fullScreen);
616						}
617					}
618				},
619				muted: {
620					key: 77, // m
621					fn: function(f) {
622						f._muted(!f.options.muted);
623					}
624				},
625				volumeUp: {
626					key: 190, // .
627					fn: function(f) {
628						f.volume(f.options.volume + 0.1);
629					}
630				},
631				volumeDown: {
632					key: 188, // ,
633					fn: function(f) {
634						f.volume(f.options.volume - 0.1);
635					}
636				},
637				loop: {
638					key: 76, // l
639					fn: function(f) {
640						f._loop(!f.options.loop);
641					}
642				}
643			},
644			verticalVolume: false, // Calculate volume from the bottom of the volume bar. Default is from the left. Also volume affects either width or height.
645			verticalPlaybackRate: false,
646			globalVolume: false, // Set to make volume and muted changes affect all jPlayer instances with this option enabled
647			idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
648			noConflict: "jQuery",
649			emulateHtml: false, // Emulates the HTML5 Media element on the jPlayer element.
650			consoleAlerts: true, // Alerts are sent to the console.log() instead of alert().
651			errorAlerts: false,
652			warningAlerts: false
653		},
654		optionsAudio: {
655			size: {
656				width: "0px",
657				height: "0px",
658				cssClass: ""
659			},
660			sizeFull: {
661				width: "0px",
662				height: "0px",
663				cssClass: ""
664			}
665		},
666		optionsVideo: {
667			size: {
668				width: "480px",
669				height: "270px",
670				cssClass: "jp-video-270p"
671			},
672			sizeFull: {
673				width: "100%",
674				height: "100%",
675				cssClass: "jp-video-full"
676			}
677		},
678		instances: {}, // Static Object
679		status: { // Instanced in _init()
680			src: "",
681			media: {},
682			paused: true,
683			format: {},
684			formatType: "",
685			waitForPlay: true, // Same as waitForLoad except in case where preloading.
686			waitForLoad: true,
687			srcSet: false,
688			video: false, // True if playing a video
689			seekPercent: 0,
690			currentPercentRelative: 0,
691			currentPercentAbsolute: 0,
692			currentTime: 0,
693			duration: 0,
694			remaining: 0,
695			videoWidth: 0, // Intrinsic width of the video in pixels.
696			videoHeight: 0, // Intrinsic height of the video in pixels.
697			readyState: 0,
698			networkState: 0,
699			playbackRate: 1, // Warning - Now both an option and a status property
700			ended: 0
701
702/*		Persistant status properties created dynamically at _init():
703			width
704			height
705			cssClass
706			nativeVideoControls
707			noFullWindow
708			noVolume
709			playbackRateEnabled // Warning - Technically, we can have both Flash and HTML, so this might not be correct if the Flash is active. That is a niche case.
710*/
711		},
712
713		internal: { // Instanced in _init()
714			ready: false
715			// instance: undefined
716			// domNode: undefined
717			// htmlDlyCmdId: undefined
718			// autohideId: undefined
719			// mouse: undefined
720			// cmdsIgnored
721		},
722		solution: { // Static Object: Defines the solutions built in jPlayer.
723			html: true,
724			aurora: true,
725			flash: true
726		},
727		// 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
728		format: { // Static Object
729			mp3: {
730				codec: 'audio/mpeg',
731				flashCanPlay: true,
732				media: 'audio'
733			},
734			m4a: { // AAC / MP4
735				codec: 'audio/mp4; codecs="mp4a.40.2"',
736				flashCanPlay: true,
737				media: 'audio'
738			},
739			m3u8a: { // AAC / MP4 / Apple HLS
740				codec: 'application/vnd.apple.mpegurl; codecs="mp4a.40.2"',
741				flashCanPlay: false,
742				media: 'audio'
743			},
744			m3ua: { // M3U
745				codec: 'audio/mpegurl',
746				flashCanPlay: false,
747				media: 'audio'
748			},
749			oga: { // OGG
750				codec: 'audio/ogg; codecs="vorbis, opus"',
751				flashCanPlay: false,
752				media: 'audio'
753			},
754			flac: { // FLAC
755				codec: 'audio/x-flac',
756				flashCanPlay: false,
757				media: 'audio'
758			},
759			wav: { // PCM
760				codec: 'audio/wav; codecs="1"',
761				flashCanPlay: false,
762				media: 'audio'
763			},
764			webma: { // WEBM
765				codec: 'audio/webm; codecs="vorbis"',
766				flashCanPlay: false,
767				media: 'audio'
768			},
769			fla: { // FLV / F4A
770				codec: 'audio/x-flv',
771				flashCanPlay: true,
772				media: 'audio'
773			},
774			rtmpa: { // RTMP AUDIO
775				codec: 'audio/rtmp; codecs="rtmp"',
776				flashCanPlay: true,
777				media: 'audio'
778			},
779			m4v: { // H.264 / MP4
780				codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
781				flashCanPlay: true,
782				media: 'video'
783			},
784			m3u8v: { // H.264 / AAC / MP4 / Apple HLS
785				codec: 'application/vnd.apple.mpegurl; codecs="avc1.42E01E, mp4a.40.2"',
786				flashCanPlay: false,
787				media: 'video'
788			},
789			m3uv: { // M3U
790				codec: 'audio/mpegurl',
791				flashCanPlay: false,
792				media: 'video'
793			},
794			ogv: { // OGG
795				codec: 'video/ogg; codecs="theora, vorbis"',
796				flashCanPlay: false,
797				media: 'video'
798			},
799			webmv: { // WEBM
800				codec: 'video/webm; codecs="vorbis, vp8"',
801				flashCanPlay: false,
802				media: 'video'
803			},
804			flv: { // FLV / F4V
805				codec: 'video/x-flv',
806				flashCanPlay: true,
807				media: 'video'
808			},
809			rtmpv: { // RTMP VIDEO
810				codec: 'video/rtmp; codecs="rtmp"',
811				flashCanPlay: true,
812				media: 'video'
813			}
814		},
815		_init: function() {
816			var self = this;
817
818			this.element.empty();
819
820			this.status = $.extend({}, this.status); // Copy static to unique instance.
821			this.internal = $.extend({}, this.internal); // Copy static to unique instance.
822
823			// Initialize the time format
824			this.options.timeFormat = $.extend({}, $.jPlayer.timeFormat, this.options.timeFormat);
825
826			// On iOS, assume commands will be ignored before user initiates them.
827			this.internal.cmdsIgnored = $.jPlayer.platform.ipad || $.jPlayer.platform.iphone || $.jPlayer.platform.ipod;
828
829			this.internal.domNode = this.element.get(0);
830
831			// Add key bindings focus to 1st jPlayer instanced with key control enabled.
832			if(this.options.keyEnabled && !$.jPlayer.focus) {
833				$.jPlayer.focus = this;
834			}
835
836			// A fix for Android where older (2.3) and even some 4.x devices fail to work when changing the *audio* SRC and then playing immediately.
837			this.androidFix = {
838				setMedia: false, // True when media set
839				play: false, // True when a progress event will instruct the media to play
840				pause: false, // True when a progress event will instruct the media to pause at a time.
841				time: NaN // The play(time) parameter
842			};
843			if($.jPlayer.platform.android) {
844				this.options.preload = this.options.preload !== 'auto' ? 'metadata' : 'auto'; // Default to metadata, but allow auto.
845			}
846
847			this.formats = []; // Array based on supplied string option. Order defines priority.
848			this.solutions = []; // Array based on solution string option. Order defines priority.
849			this.require = {}; // Which media types are required: video, audio.
850
851			this.htmlElement = {}; // DOM elements created by jPlayer
852			this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
853			this.html.audio = {};
854			this.html.video = {};
855			this.aurora = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
856			this.aurora.formats = [];
857			this.aurora.properties = [];
858			this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
859
860			this.css = {};
861			this.css.cs = {}; // Holds the css selector strings
862			this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)
863
864			this.ancestorJq = []; // Holds jQuery selector of cssSelectorAncestor. Init would use $() instead of [], but it is only 1.4+
865
866			this.options.volume = this._limitValue(this.options.volume, 0, 1); // Limit volume value's bounds.
867
868			// Create the formats array, with prority based on the order of the supplied formats string
869			$.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
870				var format = value1.replace(/^\s+|\s+$/g, ""); //trim
871				if(self.format[format]) { // Check format is valid.
872					var dupFound = false;
873					$.each(self.formats, function(index2, value2) { // Check for duplicates
874						if(format === value2) {
875							dupFound = true;
876							return false;
877						}
878					});
879					if(!dupFound) {
880						self.formats.push(format);
881					}
882				}
883			});
884
885			// Create the solutions array, with prority based on the order of the solution string
886			$.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
887				var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
888				if(self.solution[solution]) { // Check solution is valid.
889					var dupFound = false;
890					$.each(self.solutions, function(index2, value2) { // Check for duplicates
891						if(solution === value2) {
892							dupFound = true;
893							return false;
894						}
895					});
896					if(!dupFound) {
897						self.solutions.push(solution);
898					}
899				}
900			});
901
902			// Create Aurora.js formats array
903			$.each(this.options.auroraFormats.toLowerCase().split(","), function(index1, value1) {
904				var format = value1.replace(/^\s+|\s+$/g, ""); //trim
905				if(self.format[format]) { // Check format is valid.
906					var dupFound = false;
907					$.each(self.aurora.formats, function(index2, value2) { // Check for duplicates
908						if(format === value2) {
909							dupFound = true;
910							return false;
911						}
912					});
913					if(!dupFound) {
914						self.aurora.formats.push(format);
915					}
916				}
917			});
918
919			this.internal.instance = "jp_" + this.count;
920			this.instances[this.internal.instance] = this.element;
921
922			// Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
923			if(!this.element.attr("id")) {
924				this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
925			}
926
927			this.internal.self = $.extend({}, {
928				id: this.element.attr("id"),
929				jq: this.element
930			});
931			this.internal.audio = $.extend({}, {
932				id: this.options.idPrefix + "_audio_" + this.count,
933				jq: undefined
934			});
935			this.internal.video = $.extend({}, {
936				id: this.options.idPrefix + "_video_" + this.count,
937				jq: undefined
938			});
939			this.internal.flash = $.extend({}, {
940				id: this.options.idPrefix + "_flash_" + this.count,
941				jq: undefined,
942				swf: this.options.swfPath + (this.options.swfPath.toLowerCase().slice(-4) !== ".swf" ? (this.options.swfPath && this.options.swfPath.slice(-1) !== "/" ? "/" : "") + "jquery.jplayer.swf" : "")
943			});
944			this.internal.poster = $.extend({}, {
945				id: this.options.idPrefix + "_poster_" + this.count,
946				jq: undefined
947			});
948
949			// Register listeners defined in the constructor
950			$.each($.jPlayer.event, function(eventName,eventType) {
951				if(self.options[eventName] !== undefined) {
952					self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
953					self.options[eventName] = undefined; // Destroy the handler pointer copy on the options. Reason, events can be added/removed in other ways so this could be obsolete and misleading.
954				}
955			});
956
957			// Determine if we require solutions for audio, video or both media types.
958			this.require.audio = false;
959			this.require.video = false;
960			$.each(this.formats, function(priority, format) {
961				self.require[self.format[format].media] = true;
962			});
963
964			// Now required types are known, finish the options default settings.
965			if(this.require.video) {
966				this.options = $.extend(true, {},
967					this.optionsVideo,
968					this.options
969				);
970			} else {
971				this.options = $.extend(true, {},
972					this.optionsAudio,
973					this.options
974				);
975			}
976			this._setSize(); // update status and jPlayer element size
977
978			// Determine the status for Blocklisted options.
979			this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls);
980			this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow);
981			this.status.noVolume = this._uaBlocklist(this.options.noVolume);
982
983			// Create event handlers if native fullscreen is supported
984			if($.jPlayer.nativeFeatures.fullscreen.api.fullscreenEnabled) {
985				this._fullscreenAddEventListeners();
986			}
987
988			// The native controls are only for video and are disabled when audio is also used.
989			this._restrictNativeVideoControls();
990
991			// Create the poster image.
992			this.htmlElement.poster = document.createElement('img');
993			this.htmlElement.poster.id = this.internal.poster.id;
994			this.htmlElement.poster.onload = function() { // Note that this did not work on Firefox 3.6: poster.addEventListener("onload", function() {}, false); Did not investigate x-browser.
995				if(!self.status.video || self.status.waitForPlay) {
996					self.internal.poster.jq.show();
997				}
998			};
999			this.element.append(this.htmlElement.poster);
1000			this.internal.poster.jq = $("#" + this.internal.poster.id);
1001			this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
1002			this.internal.poster.jq.hide();
1003			this.internal.poster.jq.bind("click.jPlayer", function() {
1004				self._trigger($.jPlayer.event.click);
1005			});
1006
1007			// Generate the required media elements
1008			this.html.audio.available = false;
1009			if(this.require.audio) { // If a supplied format is audio
1010				this.htmlElement.audio = document.createElement('audio');
1011				this.htmlElement.audio.id = this.internal.audio.id;
1012				this.html.audio.available = !!this.htmlElement.audio.canPlayType && this._testCanPlayType(this.htmlElement.audio); // Test is for IE9 on Win Server 2008.
1013			}
1014			this.html.video.available = false;
1015			if(this.require.video) { // If a supplied format is video
1016				this.htmlElement.video = document.createElement('video');
1017				this.htmlElement.video.id = this.internal.video.id;
1018				this.html.video.available = !!this.htmlElement.video.canPlayType && this._testCanPlayType(this.htmlElement.video); // Test is for IE9 on Win Server 2008.
1019			}
1020
1021			this.flash.available = this._checkForFlash(10.1);
1022
1023			this.html.canPlay = {};
1024			this.aurora.canPlay = {};
1025			this.flash.canPlay = {};
1026			$.each(this.formats, function(priority, format) {
1027				self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
1028				self.aurora.canPlay[format] = ($.inArray(format, self.aurora.formats) > -1);
1029				self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
1030			});
1031			this.html.desired = false;
1032			this.aurora.desired = false;
1033			this.flash.desired = false;
1034			$.each(this.solutions, function(solutionPriority, solution) {
1035				if(solutionPriority === 0) {
1036					self[solution].desired = true;
1037				} else {
1038					var audioCanPlay = false;
1039					var videoCanPlay = false;
1040					$.each(self.formats, function(formatPriority, format) {
1041						if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
1042							if(self.format[format].media === 'video') {
1043								videoCanPlay = true;
1044							} else {
1045								audioCanPlay = true;
1046							}
1047						}
1048					});
1049					self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
1050				}
1051			});
1052			// This is what jPlayer will support, based on solution and supplied.
1053			this.html.support = {};
1054			this.aurora.support = {};
1055			this.flash.support = {};
1056			$.each(this.formats, function(priority, format) {
1057				self.html.support[format] = self.html.canPlay[format] && self.html.desired;
1058				self.aurora.support[format] = self.aurora.canPlay[format] && self.aurora.desired;
1059				self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
1060			});
1061			// If jPlayer is supporting any format in a solution, then the solution is used.
1062			this.html.used = false;
1063			this.aurora.used = false;
1064			this.flash.used = false;
1065			$.each(this.solutions, function(solutionPriority, solution) {
1066				$.each(self.formats, function(formatPriority, format) {
1067					if(self[solution].support[format]) {
1068						self[solution].used = true;
1069						return false;
1070					}
1071				});
1072			});
1073
1074			// Init solution active state and the event gates to false.
1075			this._resetActive();
1076			this._resetGate();
1077
1078			// Set up the css selectors for the control and feedback entities.
1079			this._cssSelectorAncestor(this.options.cssSelectorAncestor);
1080
1081			// If neither html nor aurora nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
1082			if(!(this.html.used || this.aurora.used || this.flash.used)) {
1083				this._error( {
1084					type: $.jPlayer.error.NO_SOLUTION,
1085					context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
1086					message: $.jPlayer.errorMsg.NO_SOLUTION,
1087					hint: $.jPlayer.errorHint.NO_SOLUTION
1088				});
1089				if(this.css.jq.noSolution.length) {
1090					this.css.jq.noSolution.show();
1091				}
1092			} else {
1093				if(this.css.jq.noSolution.length) {
1094					this.css.jq.noSolution.hide();
1095				}
1096			}
1097
1098			// Add the flash solution if it is being used.
1099			if(this.flash.used) {
1100				var htmlObj,
1101				flashVars = 'jQuery=' + encodeURI(this.options.noConflict) + '&id=' + encodeURI(this.internal.self.id) + '&vol=' + this.options.volume + '&muted=' + this.options.muted;
1102
1103				// Code influenced by SWFObject 2.2: http://code.google.com/p/swfobject/
1104				// Non IE browsers have an initial Flash size of 1 by 1 otherwise the wmode affected the Flash ready event.
1105
1106				if($.jPlayer.browser.msie && (Number($.jPlayer.browser.version) < 9 || $.jPlayer.browser.documentMode < 9)) {
1107					var objStr = '<object id="' + this.internal.flash.id + '" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="0" height="0" tabindex="-1"></object>';
1108
1109					var paramStr = [
1110						'<param name="movie" value="' + this.internal.flash.swf + '" />',
1111						'<param name="FlashVars" value="' + flashVars + '" />',
1112						'<param name="allowScriptAccess" value="always" />',
1113						'<param name="bgcolor" value="' + this.options.backgroundColor + '" />',
1114						'<param name="wmode" value="' + this.options.wmode + '" />'
1115					];
1116
1117					htmlObj = document.createElement(objStr);
1118					for(var i=0; i < paramStr.length; i++) {
1119						htmlObj.appendChild(document.createElement(paramStr[i]));
1120					}
1121				} else {
1122					var createParam = function(el, n, v) {
1123						var p = document.createElement("param");
1124						p.setAttribute("name", n);
1125						p.setAttribute("value", v);
1126						el.appendChild(p);
1127					};
1128
1129					htmlObj = document.createElement("object");
1130					htmlObj.setAttribute("id", this.internal.flash.id);
1131					htmlObj.setAttribute("name", this.internal.flash.id);
1132					htmlObj.setAttribute("data", this.internal.flash.swf);
1133					htmlObj.setAttribute("type", "application/x-shockwave-flash");
1134					htmlObj.setAttribute("width", "1"); // Non-zero
1135					htmlObj.setAttribute("height", "1"); // Non-zero
1136					htmlObj.setAttribute("tabindex", "-1");
1137					createParam(htmlObj, "flashvars", flashVars);
1138					createParam(htmlObj, "allowscriptaccess", "always");
1139					createParam(htmlObj, "bgcolor", this.options.backgroundColor);
1140					createParam(htmlObj, "wmode", this.options.wmode);
1141				}
1142
1143				this.element.append(htmlObj);
1144				this.internal.flash.jq = $(htmlObj);
1145			}
1146
1147			// Setup playbackRate ability before using _addHtmlEventListeners()
1148			if(this.html.used && !this.flash.used) { // If only HTML
1149				// Using the audio element capabilities for playbackRate. ie., Assuming video element is the same.
1150				this.status.playbackRateEnabled = this._testPlaybackRate('audio');
1151			} else {
1152				this.status.playbackRateEnabled = false;
1153			}
1154
1155			this._updatePlaybackRate();
1156
1157			// Add the HTML solution if being used.
1158			if(this.html.used) {
1159
1160				// The HTML Audio handlers
1161				if(this.html.audio.available) {
1162					this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
1163					this.element.append(this.htmlElement.audio);
1164					this.internal.audio.jq = $("#" + this.internal.audio.id);
1165				}
1166
1167				// The HTML Video handlers
1168				if(this.html.video.available) {
1169					this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
1170					this.element.append(this.htmlElement.video);
1171					this.internal.video.jq = $("#" + this.internal.video.id);
1172					if(this.status.nativeVideoControls) {
1173						this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
1174					} else {
1175						this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
1176					}
1177					this.internal.video.jq.bind("click.jPlayer", function() {
1178						self._trigger($.jPlayer.event.click);
1179					});
1180				}
1181			}
1182
1183			// Add the Aurora.js solution if being used.
1184			if(this.aurora.used) {
1185				// Aurora.js player need to be created for each media, see setMedia function.
1186			}
1187
1188			// Create the bridge that emulates the HTML Media element on the jPlayer DIV
1189			if( this.options.emulateHtml ) {
1190				this._emulateHtmlBridge();
1191			}
1192
1193			if((this.html.used || this.aurora.used) && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
1194				setTimeout( function() {
1195					self.internal.ready = true;
1196					self.version.flash = "n/a";
1197					self._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option.
1198					self._trigger($.jPlayer.event.ready);
1199				}, 100);
1200			}
1201
1202			// Initialize the interface components with the options.
1203			this._updateNativeVideoControls();
1204			// The other controls are now setup in _cssSelectorAncestor()
1205			if(this.css.jq.videoPlay.length) {
1206				this.css.jq.videoPlay.hide();
1207			}
1208
1209			$.jPlayer.prototype.count++; // Change static variable via prototype.
1210		},
1211		destroy: function() {
1212			// MJP: The background change remains. Would need to store the original to restore it correctly.
1213			// MJP: The jPlayer element's size change remains.
1214
1215			// Clear the media to reset the GUI and stop any downloads. Streams on some browsers had persited. (Chrome)
1216			this.clearMedia();
1217			// Remove the size/sizeFull cssClass from the cssSelectorAncestor
1218			this._removeUiClass();
1219			// Remove the times from the GUI
1220			if(this.css.jq.currentTime.length) {
1221				this.css.jq.currentTime.text("");
1222			}
1223			if(this.css.jq.duration.length) {
1224				this.css.jq.duration.text("");
1225			}
1226			// Remove any bindings from the interface controls.
1227			$.each(this.css.jq, function(fn, jq) {
1228				// Check selector is valid before trying to execute method.
1229				if(jq.length) {
1230					jq.unbind(".jPlayer");
1231				}
1232			});
1233			// Remove the click handlers for $.jPlayer.event.click
1234			this.internal.poster.jq.unbind(".jPlayer");
1235			if(this.internal.video.jq) {
1236				this.internal.video.jq.unbind(".jPlayer");
1237			}
1238			// Remove the fullscreen event handlers
1239			this._fullscreenRemoveEventListeners();
1240			// Remove key bindings
1241			if(this === $.jPlayer.focus) {
1242				$.jPlayer.focus = null;
1243			}
1244			// Destroy the HTML bridge.
1245			if(this.options.emulateHtml) {
1246				this._destroyHtmlBridge();
1247			}
1248			this.element.removeData("jPlayer"); // Remove jPlayer data
1249			this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
1250			this.element.empty(); // Remove the inserted child elements
1251
1252			delete this.instances[this.internal.instance]; // Clear the instance on the static instance object
1253		},
1254		destroyRemoved: function() { // Destroy any instances that have gone away.
1255			var self = this;
1256			$.each(this.instances, function(i, element) {
1257				if(self.element !== element) { // Do not destroy this instance.
1258					if(!element.data("jPlayer")) { // Check that element is a real jPlayer.
1259						element.jPlayer("destroy");
1260						delete self.instances[i];
1261					}
1262				}
1263			});
1264		},
1265		enable: function() { // Plan to implement
1266			// options.disabled = false
1267		},
1268		disable: function () { // Plan to implement
1269			// options.disabled = true
1270		},
1271		_testCanPlayType: function(elem) {
1272			// IE9 on Win Server 2008 did not implement canPlayType(), but it has the property.
1273			try {
1274				elem.canPlayType(this.format.mp3.codec); // The type is irrelevant.
1275				return true;
1276			} catch(err) {
1277				return false;
1278			}
1279		},
1280		_testPlaybackRate: function(type) {
1281			// type: String 'audio' or 'video'
1282			var el, rate = 0.5;
1283			type = typeof type === 'string' ? type : 'audio';
1284			el = document.createElement(type);
1285			// Wrapping in a try/catch, just in case older HTML5 browsers throw and error.
1286			try {
1287				if('playbackRate' in el) {
1288					el.playbackRate = rate;
1289					return el.playbackRate === rate;
1290				} else {
1291					return false;
1292				}
1293			} catch(err) {
1294				return false;
1295			}
1296		},
1297		_uaBlocklist: function(list) {
1298			// list : object with properties that are all regular expressions. Property names are irrelevant.
1299			// Returns true if the user agent is matched in list.
1300			var	ua = navigator.userAgent.toLowerCase(),
1301				block = false;
1302
1303			$.each(list, function(p, re) {
1304				if(re && re.test(ua)) {
1305					block = true;
1306					return false; // exit $.each.
1307				}
1308			});
1309			return block;
1310		},
1311		_restrictNativeVideoControls: function() {
1312			// Fallback to noFullWindow when nativeVideoControls is true and audio media is being used. Affects when both media types are used.
1313			if(this.require.audio) {
1314				if(this.status.nativeVideoControls) {
1315					this.status.nativeVideoControls = false;
1316					this.status.noFullWindow = true;
1317				}
1318			}
1319		},
1320		_updateNativeVideoControls: function() {
1321			if(this.html.video.available && this.html.used) {
1322				// Turn the HTML Video controls on/off
1323				this.htmlElement.video.controls = this.status.nativeVideoControls;
1324				// Show/hide the jPlayer GUI.
1325				this._updateAutohide();
1326				// For when option changed. The poster image is not updated, as it is dealt with in setMedia(). Acceptable degradation since seriously doubt these options will change on the fly. Can again review later.
1327				if(this.status.nativeVideoControls && this.require.video) {
1328					this.internal.poster.jq.hide();
1329					this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
1330				} else if(this.status.waitForPlay && this.status.video) {
1331					this.internal.poster.jq.show();
1332					this.internal.video.jq.css({'width': '0px', 'height': '0px'});
1333				}
1334			}
1335		},
1336		_addHtmlEventListeners: function(mediaElement, entity) {
1337			var self = this;
1338			mediaElement.preload = this.options.preload;
1339			mediaElement.muted = this.options.muted;
1340			mediaElement.volume = this.options.volume;
1341
1342			if(this.status.playbackRateEnabled) {
1343				mediaElement.defaultPlaybackRate = this.options.defaultPlaybackRate;
1344				mediaElement.playbackRate = this.options.playbackRate;
1345			}
1346
1347			// Create the event listeners
1348			// Only want the active entity to affect jPlayer and bubble events.
1349			// Using entity.gate so that object is referenced and gate property always current
1350
1351			mediaElement.addEventListener("progress", function() {
1352				if(entity.gate) {
1353					if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command
1354						self.internal.cmdsIgnored = false;
1355					}
1356					self._getHtmlStatus(mediaElement);
1357					self._updateInterface();
1358					self._trigger($.jPlayer.event.progress);
1359				}
1360			}, false);
1361			mediaElement.addEventListener("loadeddata", function() {
1362				if(entity.gate) {
1363					self.androidFix.setMedia = false; // Disable the fix after the first progress event.
1364					if(self.androidFix.play) { // Play Android audio - performing the fix.
1365						self.androidFix.play = false;
1366						self.play(self.androidFix.time);
1367					}
1368					if(self.androidFix.pause) { // Pause Android audio at time - performing the fix.
1369						self.androidFix.pause = false;
1370						self.pause(self.androidFix.time);
1371					}
1372					self._trigger($.jPlayer.event.loadeddata);
1373				}
1374			}, false);
1375			mediaElement.addEventListener("timeupdate", function() {
1376				if(entity.gate) {
1377					self._getHtmlStatus(mediaElement);
1378					self._updateInterface();
1379					self._trigger($.jPlayer.event.timeupdate);
1380				}
1381			}, false);
1382			mediaElement.addEventListener("durationchange", function() {
1383				if(entity.gate) {
1384					self._getHtmlStatus(mediaElement);
1385					self._updateInterface();
1386					self._trigger($.jPlayer.event.durationchange);
1387				}
1388			}, false);
1389			mediaElement.addEventListener("play", function() {
1390				if(entity.gate) {
1391					self._updateButtons(true);
1392					self._html_checkWaitForPlay(); // So the native controls update this variable and puts the hidden interface in the correct state. Affects toggling native controls.
1393					self._trigger($.jPlayer.event.play);
1394				}
1395			}, false);
1396			mediaElement.addEventListener("playing", function() {
1397				if(entity.gate) {
1398					self._updateButtons(true);
1399					self._seeked();
1400					self._trigger($.jPlayer.event.playing);
1401				}
1402			}, false);
1403			mediaElement.addEventListener("pause", function() {
1404				if(entity.gate) {
1405					self._updateButtons(false);
1406					self._trigger($.jPlayer.event.pause);
1407				}
1408			}, false);
1409			mediaElement.addEventListener("waiting", function() {
1410				if(entity.gate) {
1411					self._seeking();
1412					self._trigger($.jPlayer.event.waiting);
1413				}
1414			}, false);
1415			mediaElement.addEventListener("seeking", function() {
1416				if(entity.gate) {
1417					self._seeking();
1418					self._trigger($.jPlayer.event.seeking);
1419				}
1420			}, false);
1421			mediaElement.addEventListener("seeked", function() {
1422				if(entity.gate) {
1423					self._seeked();
1424					self._trigger($.jPlayer.event.seeked);
1425				}
1426			}, false);
1427			mediaElement.addEventListener("volumechange", function() {
1428				if(entity.gate) {
1429					// Read the values back from the element as the Blackberry PlayBook shares the volume with the physical buttons master volume control.
1430					// However, when tested 6th July 2011, those buttons do not generate an event. The physical play/pause button does though.
1431					self.options.volume = mediaElement.volume;
1432					self.options.muted = mediaElement.muted;
1433					self._updateMute();
1434					self._updateVolume();
1435					self._trigger($.jPlayer.event.volumechange);
1436				}
1437			}, false);
1438			mediaElement.addEventListener("ratechange", function() {
1439				if(entity.gate) {
1440					self.options.defaultPlaybackRate = mediaElement.defaultPlaybackRate;
1441					self.options.playbackRate = mediaElement.playbackRate;
1442					self._updatePlaybackRate();
1443					self._trigger($.jPlayer.event.ratechange);
1444				}
1445			}, false);
1446			mediaElement.addEventListener("suspend", function() { // Seems to be the only way of capturing that the iOS4 browser did not actually play the media from the page code. ie., It needs a user gesture.
1447				if(entity.gate) {
1448					self._seeked();
1449					self._trigger($.jPlayer.event.suspend);
1450				}
1451			}, false);
1452			mediaElement.addEventListener("ended", function() {
1453				if(entity.gate) {
1454					// Order of the next few commands are important. Change the time and then pause.
1455					// Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
1456					if(!$.jPlayer.browser.webkit) { // Chrome crashes if you do this in conjunction with a setMedia command in an ended event handler. ie., The playlist demo.
1457						self.htmlElement.media.currentTime = 0; // Safari does not care about this command. ie., It works with or without this line. (Both Safari and Chrome are Webkit.)
1458					}
1459					self.htmlElement.media.pause(); // Pause otherwise a click on the progress bar will play from that point, when it shouldn't, since it stopped playback.
1460					self._updateButtons(false);
1461					self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
1462					self._updateInterface();
1463					self._trigger($.jPlayer.event.ended);
1464				}
1465			}, false);
1466			mediaElement.addEventListener("error", function() {
1467				if(entity.gate) {
1468					self._updateButtons(false);
1469					self._seeked();
1470					if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
1471						clearTimeout(self.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.
1472						self.status.waitForLoad = true; // Allows the load operation to try again.
1473						self.status.waitForPlay = true; // Reset since a play was captured.
1474						if(self.status.video && !self.status.nativeVideoControls) {
1475							self.internal.video.jq.css({'width':'0px', 'height':'0px'});
1476						}
1477						if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) {
1478							self.internal.poster.jq.show();
1479						}
1480						if(self.css.jq.videoPlay.length) {
1481							self.css.jq.videoPlay.show();
1482						}
1483						self._error( {
1484							type: $.jPlayer.error.URL,
1485							context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
1486							message: $.jPlayer.errorMsg.URL,
1487							hint: $.jPlayer.errorHint.URL
1488						});
1489					}
1490				}
1491			}, false);
1492			// Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
1493			$.each($.jPlayer.htmlEvent, function(i, eventType) {
1494				mediaElement.addEventListener(this, function() {
1495					if(entity.gate) {
1496						self._trigger($.jPlayer.event[eventType]);
1497					}
1498				}, false);
1499			});
1500		},
1501		_addAuroraEventListeners : function(player, entity) {
1502			var self = this;
1503			//player.preload = this.options.preload;
1504			//player.muted = this.options.muted;
1505			player.volume = this.options.volume * 100;
1506
1507			// Create the event listeners
1508			// Only want the active entity to affect jPlayer and bubble events.
1509			// Using entity.gate so that object is referenced and gate property always current
1510
1511			player.on("progress", function() {
1512				if(entity.gate) {
1513					if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command
1514						self.internal.cmdsIgnored = false;
1515					}
1516					self._getAuroraStatus(player);
1517					self._updateInterface();
1518					self._trigger($.jPlayer.event.progress);
1519					// Progress with song duration, we estimate timeupdate need to be triggered too.
1520					if (player.duration > 0) {
1521						self._trigger($.jPlayer.event.timeupdate);
1522					}
1523				}
1524			}, false);
1525			player.on("ready", function() {
1526				if(entity.gate) {
1527					self._trigger($.jPlayer.event.loadeddata);
1528				}
1529			}, false);
1530			player.on("duration", function() {
1531				if(entity.gate) {
1532					self._getAuroraStatus(player);
1533					self._updateInterface();
1534					self._trigger($.jPlayer.event.durationchange);
1535				}
1536			}, false);
1537			player.on("end", function() {
1538				if(entity.gate) {
1539					// Order of the next few commands are important. Change the time and then pause.
1540					self._updateButtons(false);
1541					self._getAuroraStatus(player, true);
1542					self._updateInterface();
1543					self._trigger($.jPlayer.event.ended);
1544				}
1545			}, false);
1546			player.on("error", function() {
1547				if(entity.gate) {
1548					self._updateButtons(false);
1549					self._seeked();
1550					if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
1551						self.status.waitForLoad = true; // Allows the load operation to try again.
1552						self.status.waitForPlay = true; // Reset since a play was captured.
1553						if(self.status.video && !self.status.nativeVideoControls) {
1554							self.internal.video.jq.css({'width':'0px', 'height':'0px'});
1555						}
1556						if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) {
1557							self.internal.poster.jq.show();
1558						}
1559						if(self.css.jq.videoPlay.length) {
1560							self.css.jq.videoPlay.show();
1561						}
1562						self._error( {
1563							type: $.jPlayer.error.URL,
1564							context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
1565							message: $.jPlayer.errorMsg.URL,
1566							hint: $.jPlayer.errorHint.URL
1567						});
1568					}
1569				}
1570			}, false);
1571		},
1572		_getHtmlStatus: function(media, override) {
1573			var ct = 0, cpa = 0, sp = 0, cpr = 0;
1574
1575			// Fixes the duration bug in iOS, where the durationchange event occurs when media.duration is not always correct.
1576			// Fixes the initial duration bug in BB OS7, where the media.duration is infinity and displays as NaN:NaN due to Date() using inifity.
1577			if(isFinite(media.duration)) {
1578				this.status.duration = media.duration;
1579			}
1580
1581			ct = media.currentTime;
1582			cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
1583			if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
1584				sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
1585				cpr = (this.status.duration > 0) ? 100 * media.currentTime / media.seekable.end(media.seekable.length-1) : 0; // Duration conditional for iOS duration bug. ie., seekable.end is a NaN in that case.
1586			} else {
1587				sp = 100;
1588				cpr = cpa;
1589			}
1590
1591			if(override) {
1592				ct = 0;
1593				cpr = 0;
1594				cpa = 0;
1595			}
1596
1597			this.status.seekPercent = sp;
1598			this.status.currentPercentRelative = cpr;
1599			this.status.currentPercentAbsolute = cpa;
1600			this.status.currentTime = ct;
1601
1602			this.status.remaining = this.status.duration - this.status.currentTime;
1603
1604			this.status.videoWidth = media.videoWidth;
1605			this.status.videoHeight = media.videoHeight;
1606
1607			this.status.readyState = media.readyState;
1608			this.status.networkState = media.networkState;
1609			this.status.playbackRate = media.playbackRate;
1610			this.status.ended = media.ended;
1611		},
1612		_getAuroraStatus: function(player, override) {
1613			var ct = 0, cpa = 0, sp = 0, cpr = 0;
1614
1615			this.status.duration = player.duration / 1000;
1616
1617			ct = player.currentTime / 1000;
1618			cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
1619			if(player.buffered > 0) {
1620				sp = (this.status.duration > 0) ? (player.buffered * this.status.duration) / this.status.duration : 100;
1621				cpr = (this.status.duration > 0) ? ct / (player.buffered * this.status.duration) : 0;
1622			} else {
1623				sp = 100;
1624				cpr = cpa;
1625			}
1626
1627			if(override) {
1628				ct = 0;
1629				cpr = 0;
1630				cpa = 0;
1631			}
1632
1633			this.status.seekPercent = sp;
1634			this.status.currentPercentRelative = cpr;
1635			this.status.currentPercentAbsolute = cpa;
1636			this.status.currentTime = ct;
1637
1638			this.status.remaining = this.status.duration - this.status.currentTime;
1639
1640			this.status.readyState = 4; // status.readyState;
1641			this.status.networkState = 0; // status.networkState;
1642			this.status.playbackRate = 1; // status.playbackRate;
1643			this.status.ended = false; // status.ended;
1644		},
1645		_resetStatus: function() {
1646			this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset.
1647		},
1648		_trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
1649			var event = $.Event(eventType);
1650			event.jPlayer = {};
1651			event.jPlayer.version = $.extend({}, this.version);
1652			event.jPlayer.options = $.extend(true, {}, this.options); // Deep copy
1653			event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
1654			event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
1655			event.jPlayer.aurora = $.extend(true, {}, this.aurora); // Deep copy
1656			event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
1657			if(error) {
1658				event.jPlayer.error = $.extend({}, error);
1659			}
1660			if(warning) {
1661				event.jPlayer.warning = $.extend({}, warning);
1662			}
1663			this.element.trigger(event);
1664		},
1665		jPlayerFlashEvent: function(eventType, status) { // Called from Flash
1666			if(eventType === $.jPlayer.event.ready) {
1667				if(!this.internal.ready) {
1668					this.internal.ready = true;
1669					this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Once Flash generates the ready event, minimise to zero as it is not affected by wmode anymore.
1670
1671					this.version.flash = status.version;
1672					if(this.version.needFlash !== this.version.flash) {
1673						this._error( {
1674							type: $.jPlayer.error.VERSION,
1675							context: this.version.flash,
1676							message: $.jPlayer.errorMsg.VERSION + this.version.flash,
1677							hint: $.jPlayer.errorHint.VERSION
1678						});
1679					}
1680					this._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option.
1681					this._trigger(eventType);
1682				} else {
1683					// This condition occurs if the Flash is hidden and then shown again.
1684					// Firefox also reloads the Flash if the CSS position changes. position:fixed is used for full screen.
1685
1686					// Only do this if the Flash is the solution being used at the moment. Affects Media players where both solution may be being used.
1687					if(this.flash.gate) {
1688
1689						// Send the current status to the Flash now that it is ready (available) again.
1690						if(this.status.srcSet) {
1691
1692							// Need to read original status before issuing the setMedia command.
1693							var	currentTime = this.status.currentTime,
1694								paused = this.status.paused;
1695
1696							this.setMedia(this.status.media);
1697							this.volumeWorker(this.options.volume);
1698							if(currentTime > 0) {
1699								if(paused) {
1700									this.pause(currentTime);
1701								} else {
1702									this.play(currentTime);
1703								}
1704							}
1705						}
1706						this._trigger($.jPlayer.event.flashreset);
1707					}
1708				}
1709			}
1710			if(this.flash.gate) {
1711				switch(eventType) {
1712					case $.jPlayer.event.progress:
1713						this._getFlashStatus(status);
1714						this._updateInterface();
1715						this._trigger(eventType);
1716						break;
1717					case $.jPlayer.event.timeupdate:
1718						this._getFlashStatus(status);
1719						this._updateInterface();
1720						this._trigger(eventType);
1721						break;
1722					case $.jPlayer.event.play:
1723						this._seeked();
1724						this._updateButtons(true);
1725						this._trigger(eventType);
1726						break;
1727					case $.jPlayer.event.pause:
1728						this._updateButtons(false);
1729						this._trigger(eventType);
1730						break;
1731					case $.jPlayer.event.ended:
1732						this._updateButtons(false);
1733						this._trigger(eventType);
1734						break;
1735					case $.jPlayer.event.click:
1736						this._trigger(eventType); // This could be dealt with by the default
1737						break;
1738					case $.jPlayer.event.error:
1739						this.status.waitForLoad = true; // Allows the load operation to try again.
1740						this.status.waitForPlay = true; // Reset since a play was captured.
1741						if(this.status.video) {
1742							this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
1743						}
1744						if(this._validString(this.status.media.poster)) {
1745							this.internal.poster.jq.show();
1746						}
1747						if(this.css.jq.videoPlay.length && this.status.video) {
1748							this.css.jq.videoPlay.show();
1749						}
1750						if(this.status.video) { // Set up for another try. Execute before error event.
1751							this._flash_setVideo(this.status.media);
1752						} else {
1753							this._flash_setAudio(this.status.media);
1754						}
1755						this._updateButtons(false);
1756						this._error( {
1757							type: $.jPlayer.error.URL,
1758							context:status.src,
1759							message: $.jPlayer.errorMsg.URL,
1760							hint: $.jPlayer.errorHint.URL
1761						});
1762						break;
1763					case $.jPlayer.event.seeking:
1764						this._seeking();
1765						this._trigger(eventType);
1766						break;
1767					case $.jPlayer.event.seeked:
1768						this._seeked();
1769						this._trigger(eventType);
1770						break;
1771					case $.jPlayer.event.ready:
1772						// The ready event is handled outside the switch statement.
1773						// Captured here otherwise 2 ready events would be generated if the ready event handler used setMedia.
1774						break;
1775					default:
1776						this._trigger(eventType);
1777				}
1778			}
1779			return false;
1780		},
1781		_getFlashStatus: function(status) {
1782			this.status.seekPercent = status.seekPercent;
1783			this.status.currentPercentRelative = status.currentPercentRelative;
1784			this.status.currentPercentAbsolute = status.currentPercentAbsolute;
1785			this.status.currentTime = status.currentTime;
1786			this.status.duration = status.duration;
1787			this.status.remaining = status.duration - status.currentTime;
1788
1789			this.status.videoWidth = status.videoWidth;
1790			this.status.videoHeight = status.videoHeight;
1791
1792			// The Flash does not generate this information in this release
1793			this.status.readyState = 4; // status.readyState;
1794			this.status.networkState = 0; // status.networkState;
1795			this.status.playbackRate = 1; // status.playbackRate;
1796			this.status.ended = false; // status.ended;
1797		},
1798		_updateButtons: function(playing) {
1799			if(playing === undefined) {
1800				playing = !this.status.paused;
1801			} else {
1802				this.status.paused = !playing;
1803			}
1804			// Apply the state classes. (For the useStateClassSkin:true option)
1805			if(playing) {
1806				this.addStateClass('playing');
1807			} else {
1808				this.removeStateClass('playing');
1809			}
1810			if(!this.status.noFullWindow && this.options.fullWindow) {
1811				this.addStateClass('fullScreen');
1812			} else {
1813				this.removeStateClass('fullScreen');
1814			}
1815			if(this.options.loop) {
1816				this.addStateClass('looped');
1817			} else {
1818				this.removeStateClass('looped');
1819			}
1820			// Toggle the GUI element pairs. (For the useStateClassSkin:false option)
1821			if(this.css.jq.play.length && this.css.jq.pause.length) {
1822				if(playing) {
1823					this.css.jq.play.hide();
1824					this.css.jq.pause.show();
1825				} else {
1826					this.css.jq.play.show();
1827					this.css.jq.pause.hide();
1828				}
1829			}
1830			if(this.css.jq.restoreScreen.length && this.css.jq.fullScreen.length) {
1831				if(this.status.noFullWindow) {
1832					this.css.jq.fullScreen.hide();
1833					this.css.jq.restoreScreen.hide();
1834				} else if(this.options.fullWindow) {
1835					this.css.jq.fullScreen.hide();
1836					this.css.jq.restoreScreen.show();
1837				} else {
1838					this.css.jq.fullScreen.show();
1839					this.css.jq.restoreScreen.hide();
1840				}
1841			}
1842			if(this.css.jq.repeat.length && this.css.jq.repeatOff.length) {
1843				if(this.options.loop) {
1844					this.css.jq.repeat.hide();
1845					this.css.jq.repeatOff.show();
1846				} else {
1847					this.css.jq.repeat.show();
1848					this.css.jq.repeatOff.hide();
1849				}
1850			}
1851		},
1852		_updateInterface: function() {
1853			if(this.css.jq.seekBar.length) {
1854				this.css.jq.seekBar.width(this.status.seekPercent+"%");
1855			}
1856			if(this.css.jq.playBar.length) {
1857				if(this.options.smoothPlayBar) {
1858					this.css.jq.playBar.stop().animate({
1859						width: this.status.currentPercentAbsolute+"%"
1860					}, 250, "linear");
1861				} else {
1862					this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
1863				}
1864			}
1865			var currentTimeText = '';
1866			if(this.css.jq.currentTime.length) {
1867				currentTimeText = this._convertTime(this.status.currentTime);
1868				if(currentTimeText !== this.css.jq.currentTime.text()) {
1869					this.css.jq.currentTime.text(this._convertTime(this.status.currentTime));
1870				}
1871			}
1872			var durationText = '',
1873				duration = this.status.duration,
1874				remaining = this.status.remaining;
1875			if(this.css.jq.duration.length) {
1876				if(typeof this.status.media.duration === 'string') {
1877					durationText = this.status.media.duration;
1878				} else {
1879					if(typeof this.status.media.duration === 'number') {
1880						duration = this.status.media.duration;
1881						remaining = duration - this.status.currentTime;
1882					}
1883					if(this.options.remainingDuration) {
1884						durationText = (remaining > 0 ? '-' : '') + this._convertTime(remaining);
1885					} else {
1886						durationText = this._convertTime(duration);
1887					}
1888				}
1889				if(durationText !== this.css.jq.duration.text()) {
1890					this.css.jq.duration.text(durationText);
1891				}
1892			}
1893		},
1894		_convertTime: ConvertTime.prototype.time,
1895		_seeking: function() {
1896			if(this.css.jq.seekBar.length) {
1897				this.css.jq.seekBar.addClass("jp-seeking-bg");
1898			}
1899			this.addStateClass('seeking');
1900		},
1901		_seeked: function() {
1902			if(this.css.jq.seekBar.length) {
1903				this.css.jq.seekBar.removeClass("jp-seeking-bg");
1904			}
1905			this.removeStateClass('seeking');
1906		},
1907		_resetGate: function() {
1908			this.html.audio.gate = false;
1909			this.html.video.gate = false;
1910			this.aurora.gate = false;
1911			this.flash.gate = false;
1912		},
1913		_resetActive: function() {
1914			this.html.active = false;
1915			this.aurora.active = false;
1916			this.flash.active = false;
1917		},
1918		_escapeHtml: function(s) {
1919			return s.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;');
1920		},
1921		_qualifyURL: function(url) {
1922			var el = document.createElement('div');
1923			el.innerHTML= '<a href="' + this._escapeHtml(url) + '">x</a>';
1924			return el.firstChild.href;
1925		},
1926		_absoluteMediaUrls: function(media) {
1927			var self = this;
1928			$.each(media, function(type, url) {
1929				if(url && self.format[type] && url.substr(0, 5) !== "data:") {
1930					media[type] = self._qualifyURL(url);
1931				}
1932			});
1933			return media;
1934		},
1935		addStateClass: function(state) {
1936			if(this.ancestorJq.length) {
1937				this.ancestorJq.addClass(this.options.stateClass[state]);
1938			}
1939		},
1940		removeStateClass: function(state) {
1941			if(this.ancestorJq.length) {
1942				this.ancestorJq.removeClass(this.options.stateClass[state]);
1943			}
1944		},
1945		setMedia: function(media) {
1946
1947			/*	media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
1948			 *	media.poster = String: Video poster URL.
1949			 *	media.track = Array: Of objects defining the track element: kind, src, srclang, label, def.
1950			 *	media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
1951			 */
1952
1953			var	self = this,
1954				supported = false,
1955				posterChanged = this.status.media.poster !== media.poster; // Compare before reset. Important for OSX Safari as this.htmlElement.poster.src is absolute, even if original poster URL was relative.
1956
1957			this._resetMedia();
1958			this._resetGate();
1959			this._resetActive();
1960
1961			// Clear the Android Fix.
1962			this.androidFix.setMedia = false;
1963			this.androidFix.play = false;
1964			this.androidFix.pause = false;
1965
1966			// Convert all media URLs to absolute URLs.
1967			media = this._absoluteMediaUrls(media);
1968
1969			$.each(this.formats, function(formatPriority, format) {
1970				var isVideo = self.format[format].media === 'video';
1971				$.each(self.solutions, function(solutionPriority, solution) {
1972					if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
1973						var isHtml = solution === 'html';
1974						var isAurora = solution === 'aurora';
1975
1976						if(isVideo) {
1977							if(isHtml) {
1978								self.html.video.gate = true;
1979								self._html_setVideo(media);
1980								self.html.active = true;
1981							} else {
1982								self.flash.gate = true;
1983								self._flash_setVideo(media);
1984								self.flash.active = true;
1985							}
1986							if(self.css.jq.videoPlay.length) {
1987								self.css.jq.videoPlay.show();
1988							}
1989							self.status.video = true;
1990						} else {
1991							if(isHtml) {
1992								self.html.audio.gate = true;
1993								self._html_setAudio(media);
1994								self.html.active = true;
1995
1996								// Setup the Android Fix - Only for HTML audio.
1997								if($.jPlayer.platform.android) {
1998									self.androidFix.setMedia = true;
1999								}
2000							} else if(isAurora) {
2001								self.aurora.gate = true;
2002								self._aurora_setAudio(media);
2003								self.aurora.active = true;
2004							} else {
2005								self.flash.gate = true;
2006								self._flash_setAudio(media);
2007								self.flash.active = true;
2008							}
2009							if(self.css.jq.videoPlay.length) {
2010								self.css.jq.videoPlay.hide();
2011							}
2012							self.status.video = false;
2013						}
2014
2015						supported = true;
2016						return false; // Exit $.each
2017					}
2018				});
2019				if(supported) {
2020					return false; // Exit $.each
2021				}
2022			});
2023
2024			if(supported) {
2025				if(!(this.status.nativeVideoControls && this.html.video.gate)) {
2026					// Set poster IMG if native video controls are not being used
2027					// Note: With IE the IMG onload event occurs immediately when cached.
2028					// Note: Poster hidden by default in _resetMedia()
2029					if(this._validString(media.poster)) {
2030						if(posterChanged) { // Since some browsers do not generate img onload event.
2031							this.htmlElement.poster.src = media.poster;
2032						} else {
2033							this.internal.poster.jq.show();
2034						}
2035					}
2036				}
2037				if(typeof media.title === 'string') {
2038					if(this.css.jq.title.length) {
2039						this.css.jq.title.html(media.title);
2040					}
2041					if(this.htmlElement.audio) {
2042						this.htmlElement.audio.setAttribute('title', media.title);
2043					}
2044					if(this.htmlElement.video) {
2045						this.htmlElement.video.setAttribute('title', media.title);
2046					}
2047				}
2048				this.status.srcSet = true;
2049				this.status.media = $.extend({}, media);
2050				this._updateButtons(false);
2051				this._updateInterface();
2052				this._trigger($.jPlayer.event.setmedia);
2053			} else { // jPlayer cannot support any formats provided in this browser
2054				// Send an error event
2055				this._error( {
2056					type: $.jPlayer.error.NO_SUPPORT,
2057					context: "{supplied:'" + this.options.supplied + "'}",
2058					message: $.jPlayer.errorMsg.NO_SUPPORT,
2059					hint: $.jPlayer.errorHint.NO_SUPPORT
2060				});
2061			}
2062		},
2063		_resetMedia: function() {
2064			this._resetStatus();
2065			this._updateButtons(false);
2066			this._updateInterface();
2067			this._seeked();
2068			this.internal.poster.jq.hide();
2069
2070			clearTimeout(this.internal.htmlDlyCmdId);
2071
2072			if(this.html.active) {
2073				this._html_resetMedia();
2074			} else if(this.aurora.active) {
2075				this._aurora_resetMedia();
2076			} else if(this.flash.active) {
2077				this._flash_resetMedia();
2078			}
2079		},
2080		clearMedia: function() {
2081			this._resetMedia();
2082
2083			if(this.html.active) {
2084				this._html_clearMedia();
2085			} else if(this.aurora.active) {
2086				this._aurora_clearMedia();
2087			} else if(this.flash.active) {
2088				this._flash_clearMedia();
2089			}
2090
2091			this._resetGate();
2092			this._resetActive();
2093		},
2094		load: function() {
2095			if(this.status.srcSet) {
2096				if(this.html.active) {
2097					this._html_load();
2098				} else if(this.aurora.active) {
2099					this._aurora_load();
2100				} else if(this.flash.active) {
2101					this._flash_load();
2102				}
2103			} else {
2104				this._urlNotSetError("load");
2105			}
2106		},
2107		focus: function() {
2108			if(this.options.keyEnabled) {
2109				$.jPlayer.focus = this;
2110			}
2111		},
2112		play: function(time) {
2113			var guiAction = typeof time === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
2114			if(guiAction && this.options.useStateClassSkin && !this.status.paused) {
2115				this.pause(time); // The time would be the click event, but passing it over so info is not lost.
2116			} else {
2117				time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
2118				if(this.status.srcSet) {
2119					this.focus();
2120					if(this.html.active) {
2121						this._html_play(time);
2122					} else if(this.aurora.active) {
2123						this._aurora_play(time);
2124					} else if(this.flash.active) {
2125						this._flash_play(time);
2126					}
2127				} else {
2128					this._urlNotSetError("play");
2129				}
2130			}
2131		},
2132		videoPlay: function() { // Handles clicks on the play button over the video poster
2133			this.play();
2134		},
2135		pause: function(time) {
2136			time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
2137			if(this.status.srcSet) {
2138				if(this.html.active) {
2139					this._html_pause(time);
2140				} else if(this.aurora.active) {
2141					this._aurora_pause(time);
2142				} else if(this.flash.active) {
2143					this._flash_pause(time);
2144				}
2145			} else {
2146				this._urlNotSetError("pause");
2147			}
2148		},
2149		tellOthers: function(command, conditions) {
2150			var self = this,
2151				hasConditions = typeof conditions === 'function',
2152				args = Array.prototype.slice.call(arguments); // Convert arguments to an Array.
2153
2154			if(typeof command !== 'string') { // Ignore, since no command.
2155				return; // Return undefined to maintain chaining.
2156			}
2157			if(hasConditions) {
2158				args.splice(1, 1); // Remove the conditions from the arguments
2159			}
2160
2161			$.jPlayer.prototype.destroyRemoved();
2162			$.each(this.instances, function() {
2163				// Remember that "this" is the instance's "element" in the $.each() loop.
2164				if(self.element !== this) { // Do not tell my instance.
2165					if(!hasConditions || conditions.call(this.data("jPlayer"), self)) {
2166						this.jPlayer.apply(this, args);
2167					}
2168				}
2169			});
2170		},
2171		pauseOthers: function(time) {
2172			this.tellOthers("pause", function() {
2173				// In the conditions function, the "this" context is the other instance's jPlayer object.
2174				return this.status.srcSet;
2175			}, time);
2176		},
2177		stop: function() {
2178			if(this.status.srcSet) {
2179				if(this.html.active) {
2180					this._html_pause(0);
2181				} else if(this.aurora.active) {
2182					this._aurora_pause(0);
2183				} else if(this.flash.active) {
2184					this._flash_pause(0);
2185				}
2186			} else {
2187				this._urlNotSetError("stop");
2188			}
2189		},
2190		playHead: function(p) {
2191			p = this._limitValue(p, 0, 100);
2192			if(this.status.srcSet) {
2193				if(this.html.active) {
2194					this._html_playHead(p);
2195				} else if(this.aurora.active) {
2196					this._aurora_playHead(p);
2197				} else if(this.flash.active) {
2198					this._flash_playHead(p);
2199				}
2200			} else {
2201				this._urlNotSetError("playHead");
2202			}
2203		},
2204		_muted: function(muted) {
2205			this.mutedWorker(muted);
2206			if(this.options.globalVolume) {
2207				this.tellOthers("mutedWorker", function() {
2208					// Check the other instance has global volume enabled.
2209					return this.options.globalVolume;
2210				}, muted);
2211			}
2212		},
2213		mutedWorker: function(muted) {
2214			this.options.muted = muted;
2215			if(this.html.used) {
2216				this._html_setProperty('muted', muted);
2217			}
2218			if(this.aurora.used) {
2219				this._aurora_mute(muted);
2220			}
2221			if(this.flash.used) {
2222				this._flash_mute(muted);
2223			}
2224
2225			// The HTML solution generates this event from the media element itself.
2226			if(!this.html.video.gate && !this.html.audio.gate) {
2227				this._updateMute(muted);
2228				this._updateVolume(this.options.volume);
2229				this._trigger($.jPlayer.event.volumechange);
2230			}
2231		},
2232		mute: function(mute) { // mute is either: undefined (true), an event object (true) or a boolean (muted).
2233			var guiAction = typeof mute === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
2234			if(guiAction && this.options.useStateClassSkin && this.options.muted) {
2235				this._muted(false);
2236			} else {
2237				mute = mute === undefined ? true : !!mute;
2238				this._muted(mute);
2239			}
2240		},
2241		unmute: function(unmute) { // unmute is either: undefined (true), an event object (true) or a boolean (!muted).
2242			unmute = unmute === undefined ? true : !!unmute;
2243			this._muted(!unmute);
2244		},
2245		_updateMute: function(mute) {
2246			if(mute === undefined) {
2247				mute = this.options.muted;
2248			}
2249			if(mute) {
2250				this.addStateClass('muted');
2251			} else {
2252				this.removeStateClass('muted');
2253			}
2254			if(this.css.jq.mute.length && this.css.jq.unmute.length) {
2255				if(this.status.noVolume) {
2256					this.css.jq.mute.hide();
2257					this.css.jq.unmute.hide();
2258				} else if(mute) {
2259					this.css.jq.mute.hide();
2260					this.css.jq.unmute.show();
2261				} else {
2262					this.css.jq.mute.show();
2263					this.css.jq.unmute.hide();
2264				}
2265			}
2266		},
2267		volume: function(v) {
2268			this.volumeWorker(v);
2269			if(this.options.globalVolume) {
2270				this.tellOthers("volumeWorker", function() {
2271					// Check the other instance has global volume enabled.
2272					return this.options.globalVolume;
2273				}, v);
2274			}
2275		},
2276		volumeWorker: function(v) {
2277			v = this._limitValue(v, 0, 1);
2278			this.options.volume = v;
2279
2280			if(this.html.used) {
2281				this._html_setProperty('volume', v);
2282			}
2283			if(this.aurora.used) {
2284				this._aurora_volume(v);
2285			}
2286			if(this.flash.used) {
2287				this._flash_volume(v);
2288			}
2289
2290			// The HTML solution generates this event from the media element itself.
2291			if(!this.html.video.gate && !this.html.audio.gate) {
2292				this._updateVolume(v);
2293				this._trigger($.jPlayer.event.volumechange);
2294			}
2295		},
2296		volumeBar: function(e) { // Handles clicks on the volumeBar
2297			if(this.css.jq.volumeBar.length) {
2298				// Using $(e.currentTarget) to enable multiple volume bars
2299				var $bar = $(e.currentTarget),
2300					offset = $bar.offset(),
2301					x = e.pageX - offset.left,
2302					w = $bar.width(),
2303					y = $bar.height() - e.pageY + offset.top,
2304					h = $bar.height();
2305				if(this.options.verticalVolume) {
2306					this.volume(y/h);
2307				} else {
2308					this.volume(x/w);
2309				}
2310			}
2311			if(this.options.muted) {
2312				this._muted(false);
2313			}
2314		},
2315		_updateVolume: function(v) {
2316			if(v === undefined) {
2317				v = this.options.volume;
2318			}
2319			v = this.options.muted ? 0 : v;
2320
2321			if(this.status.noVolume) {
2322				this.addStateClass('noVolume');
2323				if(this.css.jq.volumeBar.length) {
2324					this.css.jq.volumeBar.hide();
2325				}
2326				if(this.css.jq.volumeBarValue.length) {
2327					this.css.jq.volumeBarValue.hide();
2328				}
2329				if(this.css.jq.volumeMax.length) {
2330					this.css.jq.volumeMax.hide();
2331				}
2332			} else {
2333				this.removeStateClass('noVolume');
2334				if(this.css.jq.volumeBar.length) {
2335					this.css.jq.volumeBar.show();
2336				}
2337				if(this.css.jq.volumeBarValue.length) {
2338					this.css.jq.volumeBarValue.show();
2339					this.css.jq.volumeBarValue[this.options.verticalVolume ? "height" : "width"]((v*100)+"%");
2340				}
2341				if(this.css.jq.volumeMax.length) {
2342					this.css.jq.volumeMax.show();
2343				}
2344			}
2345		},
2346		volumeMax: function() { // Handles clicks on the volume max
2347			this.volume(1);
2348			if(this.options.muted) {
2349				this._muted(false);
2350			}
2351		},
2352		_cssSelectorAncestor: function(ancestor) {
2353			var self = this;
2354			this.options.cssSelectorAncestor = ancestor;
2355			this._removeUiClass();
2356			this.ancestorJq = ancestor ? $(ancestor) : []; // Would use $() instead of [], but it is only 1.4+
2357			if(ancestor && this.ancestorJq.length !== 1) { // So empty strings do not generate the warning.
2358				this._warning( {
2359					type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
2360					context: ancestor,
2361					message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.ancestorJq.length + " found for cssSelectorAncestor.",
2362					hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
2363				});
2364			}
2365			this._addUiClass();
2366			$.each(this.options.cssSelector, function(fn, cssSel) {
2367				self._cssSelector(fn, cssSel);
2368			});
2369
2370			// Set the GUI to the current state.
2371			this._updateInterface();
2372			this._updateButtons();
2373			this._updateAutohide();
2374			this._updateVolume();
2375			this._updateMute();
2376		},
2377		_cssSelector: function(fn, cssSel) {
2378			var self = this;
2379			if(typeof cssSel === 'string') {
2380				if($.jPlayer.prototype.options.cssSelector[fn]) {
2381					if(this.css.jq[fn] && this.css.jq[fn].length) {
2382						this.css.jq[fn].unbind(".jPlayer");
2383					}
2384					this.options.cssSelector[fn] = cssSel;
2385					this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;
2386
2387					if(cssSel) { // Checks for empty string
2388						this.css.jq[fn] = $(this.css.cs[fn]);
2389					} else {
2390						this.css.jq[fn] = []; // To comply with the css.jq[fn].length check before its use. As of jQuery 1.4 could have used $() for an empty set.
2391					}
2392
2393					if(this.css.jq[fn].length && this[fn]) {
2394						var handler = function(e) {
2395							e.preventDefault();
2396							self[fn](e);
2397							if(self.options.autoBlur) {
2398								$(this).blur();
2399							} else {
2400								$(this).focus(); // Force focus for ARIA.
2401							}
2402						};
2403						this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
2404					}
2405
2406					if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
2407						this._warning( {
2408							type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
2409							context: this.css.cs[fn],
2410							message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
2411							hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
2412						});
2413					}
2414				} else {
2415					this._warning( {
2416						type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
2417						context: fn,
2418						message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
2419						hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
2420					});
2421				}
2422			} else {
2423				this._warning( {
2424					type: $.jPlayer.warning.CSS_SELECTOR_STRING,
2425					context: cssSel,
2426					message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
2427					hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
2428				});
2429			}
2430		},
2431		duration: function(e) {
2432			if(this.options.toggleDuration) {
2433				if(this.options.captureDuration) {
2434					e.stopPropagation();
2435				}
2436				this._setOption("remainingDuration", !this.options.remainingDuration);
2437			}
2438		},
2439		seekBar: function(e) { // Handles clicks on the seekBar
2440			if(this.css.jq.seekBar.length) {
2441				// Using $(e.currentTarget) to enable multiple seek bars
2442				var $bar = $(e.currentTarget),
2443					offset = $bar.offset(),
2444					x = e.pageX - offset.left,
2445					w = $bar.width(),
2446					p = 100 * x / w;
2447				this.playHead(p);
2448			}
2449		},
2450		playbackRate: function(pbr) {
2451			this._setOption("playbackRate", pbr);
2452		},
2453		playbackRateBar: function(e) { // Handles clicks on the playbackRateBar
2454			if(this.css.jq.playbackRateBar.length) {
2455				// Using $(e.currentTarget) to enable multiple playbackRate bars
2456				var $bar = $(e.currentTarget),
2457					offset = $bar.offset(),
2458					x = e.pageX - offset.left,
2459					w = $bar.width(),
2460					y = $bar.height() - e.pageY + offset.top,
2461					h = $bar.height(),
2462					ratio, pbr;
2463				if(this.options.verticalPlaybackRate) {
2464					ratio = y/h;
2465				} else {
2466					ratio = x/w;
2467				}
2468				pbr = ratio * (this.options.maxPlaybackRate - this.options.minPlaybackRate) + this.options.minPlaybackRate;
2469				this.playbackRate(pbr);
2470			}
2471		},
2472		_updatePlaybackRate: function() {
2473			var pbr = this.options.playbackRate,
2474				ratio = (pbr - this.options.minPlaybackRate) / (this.options.maxPlaybackRate - this.options.minPlaybackRate);
2475			if(this.status.playbackRateEnabled) {
2476				if(this.css.jq.playbackRateBar.length) {
2477					this.css.jq.playbackRateBar.show();
2478				}
2479				if(this.css.jq.playbackRateBarValue.length) {
2480					this.css.jq.playbackRateBarValue.show();
2481					this.css.jq.playbackRateBarValue[this.options.verticalPlaybackRate ? "height" : "width"]((ratio*100)+"%");
2482				}
2483			} else {
2484				if(this.css.jq.playbackRateBar.length) {
2485					this.css.jq.playbackRateBar.hide();
2486				}
2487				if(this.css.jq.playbackRateBarValue.length) {
2488					this.css.jq.playbackRateBarValue.hide();
2489				}
2490			}
2491		},
2492		repeat: function(event) { // Handle clicks on the repeat button
2493			var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
2494			if(guiAction && this.options.useStateClassSkin && this.options.loop) {
2495				this._loop(false);
2496			} else {
2497				this._loop(true);
2498			}
2499		},
2500		repeatOff: function() { // Handle clicks on the repeatOff button
2501			this._loop(false);
2502		},
2503		_loop: function(loop) {
2504			if(this.options.loop !== loop) {
2505				this.options.loop = loop;
2506				this._updateButtons();
2507				this._trigger($.jPlayer.event.repeat);
2508			}
2509		},
2510
2511		// Options code adapted from ui.widget.js (1.8.7).  Made changes so the key can use dot notation. To match previous getData solution in jPlayer 1.
2512		option: function(key, value) {
2513			var options = key;
2514
2515			 // Enables use: options().  Returns a copy of options object
2516			if ( arguments.length === 0 ) {
2517				return $.extend( true, {}, this.options );
2518			}
2519
2520			if(typeof key === "string") {
2521				var keys = key.split(".");
2522
2523				 // Enables use: options("someOption")  Returns a copy of the option. Supports dot notation.
2524				if(value === undefined) {
2525
2526					var opt = $.extend(true, {}, this.options);
2527					for(var i = 0; i < keys.length; i++) {
2528						if(opt[keys[i]] !== undefined) {
2529							opt = opt[keys[i]];
2530						} else {
2531							this._warning( {
2532								type: $.jPlayer.warning.OPTION_KEY,
2533								context: key,
2534								message: $.jPlayer.warningMsg.OPTION_KEY,
2535								hint: $.jPlayer.warningHint.OPTION_KEY
2536							});
2537							return undefined;
2538						}
2539					}
2540					return opt;
2541				}
2542
2543				 // Enables use: options("someOptionObject", someObject}).  Creates: {someOptionObject:someObject}
2544				 // Enables use: options("someOption", someValue).  Creates: {someOption:someValue}
2545				 // Enables use: options("someOptionObject.someOption", someValue).  Creates: {someOptionObject:{someOption:someValue}}
2546
2547				options = {};
2548				var opts = options;
2549
2550				for(var j = 0; j < keys.length; j++) {
2551					if(j < keys.length - 1) {
2552						opts[keys[j]] = {};
2553						opts = opts[keys[j]];
2554					} else {
2555						opts[keys[j]] = value;
2556					}
2557				}
2558			}
2559
2560			 // Otherwise enables use: options(optionObject).  Uses original object (the key)
2561
2562			this._setOptions(options);
2563
2564			return this;
2565		},
2566		_setOptions: function(options) {
2567			var self = this;
2568			$.each(options, function(key, value) { // This supports the 2 level depth that the options of jPlayer has. Would review if we ever need more depth.
2569				self._setOption(key, value);
2570			});
2571
2572			return this;
2573		},
2574		_setOption: function(key, value) {
2575			var self = this;
2576
2577			// The ability to set options is limited at this time.
2578
2579			switch(key) {
2580				case "volume" :
2581					this.volume(value);
2582					break;
2583				case "muted" :
2584					this._muted(value);
2585					break;
2586				case "globalVolume" :
2587					this.options[key] = value;
2588					break;
2589				case "cssSelectorAncestor" :
2590					this._cssSelectorAncestor(value); // Set and refresh all associations for the new ancestor.
2591					break;
2592				case "cssSelector" :
2593					$.each(value, function(fn, cssSel) {
2594						self._cssSelector(fn, cssSel); // NB: The option is set inside this function, after further validity checks.
2595					});
2596					break;
2597				case "playbackRate" :
2598					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate);
2599					if(this.html.used) {
2600						this._html_setProperty('playbackRate', value);
2601					}
2602					this._updatePlaybackRate();
2603					break;
2604				case "defaultPlaybackRate" :
2605					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate);
2606					if(this.html.used) {
2607						this._html_setProperty('defaultPlaybackRate', value);
2608					}
2609					this._updatePlaybackRate();
2610					break;
2611				case "minPlaybackRate" :
2612					this.options[key] = value = this._limitValue(value, 0.1, this.options.maxPlaybackRate - 0.1);
2613					this._updatePlaybackRate();
2614					break;
2615				case "maxPlaybackRate" :
2616					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate + 0.1, 16);
2617					this._updatePlaybackRate();
2618					break;
2619				case "fullScreen" :
2620					if(this.options[key] !== value) { // if changed
2621						var wkv = $.jPlayer.nativeFeatures.fullscreen.used.webkitVideo;
2622						if(!wkv || wkv && !this.status.waitForPlay) {
2623							if(!wkv) { // No sensible way to unset option on these devices.
2624								this.options[key] = value;
2625							}
2626							if(value) {
2627								this._requestFullscreen();
2628							} else {
2629								this._exitFullscreen();
2630							}
2631							if(!wkv) {
2632								this._setOption("fullWindow", value);
2633							}
2634						}
2635					}
2636					break;
2637				case "fullWindow" :
2638					if(this.options[key] !== value) { // if changed
2639						this._removeUiClass();
2640						this.options[key] = value;
2641						this._refreshSize();
2642					}
2643					break;
2644				case "size" :
2645					if(!this.options.fullWindow && this.options[key].cssClass !== value.cssClass) {
2646						this._removeUiClass();
2647					}
2648					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2649					this._refreshSize();
2650					break;
2651				case "sizeFull" :
2652					if(this.options.fullWindow && this.options[key].cssClass !== value.cssClass) {
2653						this._removeUiClass();
2654					}
2655					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2656					this._refreshSize();
2657					break;
2658				case "autohide" :
2659					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2660					this._updateAutohide();
2661					break;
2662				case "loop" :
2663					this._loop(value);
2664					break;
2665				case "remainingDuration" :
2666					this.options[key] = value;
2667					this._updateInterface();
2668					break;
2669				case "toggleDuration" :
2670					this.options[key] = value;
2671					break;
2672				case "nativeVideoControls" :
2673					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2674					this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls);
2675					this._restrictNativeVideoControls();
2676					this._updateNativeVideoControls();
2677					break;
2678				case "noFullWindow" :
2679					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2680					this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); // Need to check again as noFullWindow can depend on this flag and the restrict() can override it.
2681					this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow);
2682					this._restrictNativeVideoControls();
2683					this._updateButtons();
2684					break;
2685				case "noVolume" :
2686					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2687					this.status.noVolume = this._uaBlocklist(this.options.noVolume);
2688					this._updateVolume();
2689					this._updateMute();
2690					break;
2691				case "emulateHtml" :
2692					if(this.options[key] !== value) { // To avoid multiple event handlers being created, if true already.
2693						this.options[key] = value;
2694						if(value) {
2695							this._emulateHtmlBridge();
2696						} else {
2697							this._destroyHtmlBridge();
2698						}
2699					}
2700					break;
2701				case "timeFormat" :
2702					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
2703					break;
2704				case "keyEnabled" :
2705					this.options[key] = value;
2706					if(!value && this === $.jPlayer.focus) {
2707						$.jPlayer.focus = null;
2708					}
2709					break;
2710				case "keyBindings" :
2711					this.options[key] = $.extend(true, {}, this.options[key], value); // store a merged DEEP copy of it, incase not all properties changed.
2712					break;
2713				case "audioFullScreen" :
2714					this.options[key] = value;
2715					break;
2716				case "autoBlur" :
2717					this.options[key] = value;
2718					break;
2719			}
2720
2721			return this;
2722		},
2723		// End of: (Options code adapted from ui.widget.js)
2724
2725		_refreshSize: function() {
2726			this._setSize(); // update status and jPlayer element size
2727			this._addUiClass(); // update the ui class
2728			this._updateSize(); // update internal sizes
2729			this._updateButtons();
2730			this._updateAutohide();
2731			this._trigger($.jPlayer.event.resize);
2732		},
2733		_setSize: function() {
2734			// Determine the current size from the options
2735			if(this.options.fullWindow) {
2736				this.status.width = this.options.sizeFull.width;
2737				this.status.height = this.options.sizeFull.height;
2738				this.status.cssClass = this.options.sizeFull.cssClass;
2739			} else {
2740				this.status.width = this.options.size.width;
2741				this.status.height = this.options.size.height;
2742				this.status.cssClass = this.options.size.cssClass;
2743			}
2744
2745			// Set the size of the jPlayer area.
2746			this.element.css({'width': this.status.width, 'height': this.status.height});
2747		},
2748		_addUiClass: function() {
2749			if(this.ancestorJq.length) {
2750				this.ancestorJq.addClass(this.status.cssClass);
2751			}
2752		},
2753		_removeUiClass: function() {
2754			if(this.ancestorJq.length) {
2755				this.ancestorJq.removeClass(this.status.cssClass);
2756			}
2757		},
2758		_updateSize: function() {
2759			// The poster uses show/hide so can simply resize it.
2760			this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
2761
2762			// Video html or flash resized if necessary at this time, or if native video controls being used.
2763			if(!this.status.waitForPlay && this.html.active && this.status.video || this.html.video.available && this.html.used && this.status.nativeVideoControls) {
2764				this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
2765			}
2766			else if(!this.status.waitForPlay && this.flash.active && this.status.video) {
2767				this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
2768			}
2769		},
2770		_updateAutohide: function() {
2771			var	self = this,
2772				event = "mousemove.jPlayer",
2773				namespace = ".jPlayerAutohide",
2774				eventType = event + namespace,
2775				handler = function(event) {
2776					var moved = false,
2777						deltaX, deltaY;
2778					if(typeof self.internal.mouse !== "undefined") {
2779						//get the change from last position to this position
2780						deltaX = self.internal.mouse.x - event.pageX;
2781						deltaY = self.internal.mouse.y - event.pageY;
2782						moved = (Math.floor(deltaX) > 0) || (Math.floor(deltaY)>0);
2783					} else {
2784						moved = true;
2785					}
2786					// store current position for next method execution
2787					self.internal.mouse = {
2788							x : event.pageX,
2789							y : event.pageY
2790					};
2791					// if mouse has been actually moved, do the gui fadeIn/fadeOut
2792					if (moved) {
2793						self.css.jq.gui.fadeIn(self.options.autohide.fadeIn, function() {
2794							clearTimeout(self.internal.autohideId);
2795							self.internal.autohideId = setTimeout( function() {
2796								self.css.jq.gui.fadeOut(self.options.autohide.fadeOut);
2797							}, self.options.autohide.hold);
2798						});
2799					}
2800				};
2801
2802			if(this.css.jq.gui.length) {
2803
2804				// End animations first so that its callback is executed now.
2805				// Otherwise an in progress fadeIn animation still has the callback to fadeOut again.
2806				this.css.jq.gui.stop(true, true);
2807
2808				// Removes the fadeOut operation from the fadeIn callback.
2809				clearTimeout(this.internal.autohideId);
2810				// undefine mouse
2811				delete this.internal.mouse;
2812
2813				this.element.unbind(namespace);
2814				this.css.jq.gui.unbind(namespace);
2815
2816				if(!this.status.nativeVideoControls) {
2817					if(this.options.fullWindow && this.options.autohide.full || !this.options.fullWindow && this.options.autohide.restored) {
2818						this.element.bind(eventType, handler);
2819						this.css.jq.gui.bind(eventType, handler);
2820						this.css.jq.gui.hide();
2821					} else {
2822						this.css.jq.gui.show();
2823					}
2824				} else {
2825					this.css.jq.gui.hide();
2826				}
2827			}
2828		},
2829		fullScreen: function(event) {
2830			var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
2831			if(guiAction && this.options.useStateClassSkin && this.options.fullScreen) {
2832				this._setOption("fullScreen", false);
2833			} else {
2834				this._setOption("fullScreen", true);
2835			}
2836		},
2837		restoreScreen: function() {
2838			this._setOption("fullScreen", false);
2839		},
2840		_fullscreenAddEventListeners: function() {
2841			var self = this,
2842				fs = $.jPlayer.nativeFeatures.fullscreen;
2843
2844			if(fs.api.fullscreenEnabled) {
2845				if(fs.event.fullscreenchange) {
2846					// Create the event handler function and store it for removal.
2847					if(typeof this.internal.fullscreenchangeHandler !== 'function') {
2848						this.internal.fullscreenchangeHandler = function() {
2849							self._fullscreenchange();
2850						};
2851					}
2852					document.addEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false);
2853				}
2854				// No point creating handler for fullscreenerror.
2855				// Either logic avoids fullscreen occurring (w3c/moz), or their is no event on the browser (webkit).
2856			}
2857		},
2858		_fullscreenRemoveEventListeners: function() {
2859			var fs = $.jPlayer.nativeFeatures.fullscreen;
2860			if(this.internal.fullscreenchangeHandler) {
2861				document.removeEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false);
2862			}
2863		},
2864		_fullscreenchange: function() {
2865			// If nothing is fullscreen, then we cannot be in fullscreen mode.
2866			if(this.options.fullScreen && !$.jPlayer.nativeFeatures.fullscreen.api.fullscreenElement()) {
2867				this._setOption("fullScreen", false);
2868			}
2869		},
2870		_requestFullscreen: function() {
2871			// Either the container or the jPlayer div
2872			var e = this.ancestorJq.length ? this.ancestorJq[0] : this.element[0],
2873				fs = $.jPlayer.nativeFeatures.fullscreen;
2874
2875			// This method needs the video element. For iOS and Android.
2876			if(fs.used.webkitVideo) {
2877				e = this.htmlElement.video;
2878			}
2879
2880			if(fs.api.fullscreenEnabled) {
2881				fs.api.requestFullscreen(e);
2882			}
2883		},
2884		_exitFullscreen: function() {
2885
2886			var fs = $.jPlayer.nativeFeatures.fullscreen,
2887				e;
2888
2889			// This method needs the video element. For iOS and Android.
2890			if(fs.used.webkitVideo) {
2891				e = this.htmlElement.video;
2892			}
2893
2894			if(fs.api.fullscreenEnabled) {
2895				fs.api.exitFullscreen(e);
2896			}
2897		},
2898		_html_initMedia: function(media) {
2899			// Remove any existing track elements
2900			var $media = $(this.htmlElement.media).empty();
2901
2902			// Create any track elements given with the media, as an Array of track Objects.
2903			$.each(media.track || [], function(i,v) {
2904				var track = document.createElement('track');
2905				track.setAttribute("kind", v.kind ? v.kind : "");
2906				track.setAttribute("src", v.src ? v.src : "");
2907				track.setAttribute("srclang", v.srclang ? v.srclang : "");
2908				track.setAttribute("label", v.label ? v.label : "");
2909				if(v.def) {
2910					track.setAttribute("default", v.def);
2911				}
2912				$media.append(track);
2913			});
2914
2915			this.htmlElement.media.src = this.status.src;
2916
2917			if(this.options.preload !== 'none') {
2918				this._html_load(); // See function for comments
2919			}
2920			this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
2921		},
2922		_html_setFormat: function(media) {
2923			var self = this;
2924			// Always finds a format due to checks in setMedia()
2925			$.each(this.formats, function(priority, format) {
2926				if(self.html.support[format] && media[format]) {
2927					self.status.src = media[format];
2928					self.status.format[format] = true;
2929					self.status.formatType = format;
2930					return false;
2931				}
2932			});
2933		},
2934		_html_setAudio: function(media) {
2935			this._html_setFormat(media);
2936			this.htmlElement.media = this.htmlElement.audio;
2937			this._html_initMedia(media);
2938		},
2939		_html_setVideo: function(media) {
2940			this._html_setFormat(media);
2941			if(this.status.nativeVideoControls) {
2942				this.htmlElement.video.poster = this._validString(media.poster) ? media.poster : "";
2943			}
2944			this.htmlElement.media = this.htmlElement.video;
2945			this._html_initMedia(media);
2946		},
2947		_html_resetMedia: function() {
2948			if(this.htmlElement.media) {
2949				if(this.htmlElement.media.id === this.internal.video.id && !this.status.nativeVideoControls) {
2950					this.internal.video.jq.css({'width':'0px', 'height':'0px'});
2951				}
2952				this.htmlElement.media.pause();
2953			}
2954		},
2955		_html_clearMedia: function() {
2956			if(this.htmlElement.media) {
2957				this.htmlElement.media.src = "about:blank";
2958				// The following load() is only required for Firefox 3.6 (PowerMacs).
2959				// Recent HTMl5 browsers only require the src change. Due to changes in W3C spec and load() effect.
2960				this.htmlElement.media.load(); // Stops an old, "in progress" download from continuing the download. Triggers the loadstart, error and emptied events, due to the empty src. Also an abort event if a download was in progress.
2961			}
2962		},
2963		_html_load: function() {
2964			// This function remains to allow the early HTML5 browsers to work, such as Firefox 3.6
2965			// A change in the W3C spec for the media.load() command means that this is no longer necessary.
2966			// This command should be removed and actually causes minor undesirable effects on some browsers. Such as loading the whole file and not only the metadata.
2967			if(this.status.waitForLoad) {
2968				this.status.waitForLoad = false;
2969				this.htmlElement.media.load();
2970			}
2971			clearTimeout(this.internal.htmlDlyCmdId);
2972		},
2973		_html_play: function(time) {
2974			var self = this,
2975				media = this.htmlElement.media;
2976
2977			this.androidFix.pause = false; // Cancel the pause fix.
2978
2979			this._html_load(); // Loads if required and clears any delayed commands.
2980
2981			// Setup the Android Fix.
2982			if(this.androidFix.setMedia) {
2983				this.androidFix.play = true;
2984				this.androidFix.time = time;
2985
2986			} else if(!isNaN(time)) {
2987
2988				// Attempt to play it, since iOS has been ignoring commands
2989				if(this.internal.cmdsIgnored) {
2990					media.play();
2991				}
2992
2993				try {
2994					// !media.seekable is for old HTML5 browsers, like Firefox 3.6.
2995					// Checking seekable.length is important for iOS6 to work with setMedia().play(time)
2996					if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) {
2997						media.currentTime = time;
2998						media.play();
2999					} else {
3000						throw 1;
3001					}
3002				} catch(err) {
3003					this.internal.htmlDlyCmdId = setTimeout(function() {
3004						self.play(time);
3005					}, 250);
3006					return; // Cancel execution and wait for the delayed command.
3007				}
3008			} else {
3009				media.play();
3010			}
3011			this._html_checkWaitForPlay();
3012		},
3013		_html_pause: function(time) {
3014			var self = this,
3015				media = this.htmlElement.media;
3016
3017			this.androidFix.play = false; // Cancel the play fix.
3018
3019			if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
3020				this._html_load(); // Loads if required and clears any delayed commands.
3021			} else {
3022				clearTimeout(this.internal.htmlDlyCmdId);
3023			}
3024
3025			// Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime.
3026			media.pause();
3027
3028			// Setup the Android Fix.
3029			if(this.androidFix.setMedia) {
3030				this.androidFix.pause = true;
3031				this.androidFix.time = time;
3032
3033			} else if(!isNaN(time)) {
3034				try {
3035					if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) {
3036						media.currentTime = time;
3037					} else {
3038						throw 1;
3039					}
3040				} catch(err) {
3041					this.internal.htmlDlyCmdId = setTimeout(function() {
3042						self.pause(time);
3043					}, 250);
3044					return; // Cancel execution and wait for the delayed command.
3045				}
3046			}
3047			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
3048				this._html_checkWaitForPlay();
3049			}
3050		},
3051		_html_playHead: function(percent) {
3052			var self = this,
3053				media = this.htmlElement.media;
3054
3055			this._html_load(); // Loads if required and clears any delayed commands.
3056
3057			// This playHead() method needs a refactor to apply the android fix.
3058
3059			try {
3060				if(typeof media.seekable === "object" && media.seekable.length > 0) {
3061					media.currentTime = percent * media.seekable.end(media.seekable.length-1) / 100;
3062				} else if(media.duration > 0 && !isNaN(media.duration)) {
3063					media.currentTime = percent * media.duration / 100;
3064				} else {
3065					throw "e";
3066				}
3067			} catch(err) {
3068				this.internal.htmlDlyCmdId = setTimeout(function() {
3069					self.playHead(percent);
3070				}, 250);
3071				return; // Cancel execution and wait for the delayed command.
3072			}
3073			if(!this.status.waitForLoad) {
3074				this._html_checkWaitForPlay();
3075			}
3076		},
3077		_html_checkWaitForPlay: function() {
3078			if(this.status.waitForPlay) {
3079				this.status.waitForPlay = false;
3080				if(this.css.jq.videoPlay.length) {
3081					this.css.jq.videoPlay.hide();
3082				}
3083				if(this.status.video) {
3084					this.internal.poster.jq.hide();
3085					this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
3086				}
3087			}
3088		},
3089		_html_setProperty: function(property, value) {
3090			if(this.html.audio.available) {
3091				this.htmlElement.audio[property] = value;
3092			}
3093			if(this.html.video.available) {
3094				this.htmlElement.video[property] = value;
3095			}
3096		},
3097		_aurora_setAudio: function(media) {
3098			var self = this;
3099
3100			// Always finds a format due to checks in setMedia()
3101			$.each(this.formats, function(priority, format) {
3102				if(self.aurora.support[format] && media[format]) {
3103					self.status.src = media[format];
3104					self.status.format[format] = true;
3105					self.status.formatType = format;
3106
3107					return false;
3108				}
3109			});
3110
3111			this.aurora.player = new AV.Player.fromURL(this.status.src);
3112			this._addAuroraEventListeners(this.aurora.player, this.aurora);
3113
3114			if(this.options.preload === 'auto') {
3115				this._aurora_load();
3116				this.status.waitForLoad = false;
3117			}
3118		},
3119		_aurora_resetMedia: function() {
3120			if (this.aurora.player) {
3121				this.aurora.player.stop();
3122			}
3123		},
3124		_aurora_clearMedia: function() {
3125			// Nothing to clear.
3126		},
3127		_aurora_load: function() {
3128			if(this.status.waitForLoad) {
3129				this.status.waitForLoad = false;
3130				this.aurora.player.preload();
3131			}
3132		},
3133		_aurora_play: function(time) {
3134			if (!this.status.waitForLoad) {
3135				if (!isNaN(time)) {
3136					this.aurora.player.seek(time);
3137				}
3138			}
3139			if (!this.aurora.player.playing) {
3140				this.aurora.player.play();
3141			}
3142			this.status.waitForLoad = false;
3143			this._aurora_checkWaitForPlay();
3144
3145			// No event from the player, update UI now.
3146			this._updateButtons(true);
3147			this._trigger($.jPlayer.event.play);
3148		},
3149		_aurora_pause: function(time) {
3150			if (!isNaN(time)) {
3151				this.aurora.player.seek(time * 1000);
3152			}
3153			this.aurora.player.pause();
3154
3155			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
3156				this._aurora_checkWaitForPlay();
3157			}
3158
3159			// No event from the player, update UI now.
3160			this._updateButtons(false);
3161			this._trigger($.jPlayer.event.pause);
3162		},
3163		_aurora_playHead: function(percent) {
3164			if(this.aurora.player.duration > 0) {
3165				// The seek() sould be in milliseconds, but the only codec that works with seek (aac.js) uses seconds.
3166				this.aurora.player.seek(percent * this.aurora.player.duration / 100); // Using seconds
3167			}
3168
3169			if(!this.status.waitForLoad) {
3170				this._aurora_checkWaitForPlay();
3171			}
3172		},
3173		_aurora_checkWaitForPlay: function() {
3174			if(this.status.waitForPlay) {
3175				this.status.waitForPlay = false;
3176			}
3177		},
3178		_aurora_volume: function(v) {
3179			this.aurora.player.volume = v * 100;
3180		},
3181		_aurora_mute: function(m) {
3182			if (m) {
3183				this.aurora.properties.lastvolume = this.aurora.player.volume;
3184				this.aurora.player.volume = 0;
3185			} else {
3186				this.aurora.player.volume = this.aurora.properties.lastvolume;
3187			}
3188			this.aurora.properties.muted = m;
3189		},
3190		_flash_setAudio: function(media) {
3191			var self = this;
3192			try {
3193				// Always finds a format due to checks in setMedia()
3194				$.each(this.formats, function(priority, format) {
3195					if(self.flash.support[format] && media[format]) {
3196						switch (format) {
3197							case "m4a" :
3198							case "fla" :
3199								self._getMovie().fl_setAudio_m4a(media[format]);
3200								break;
3201							case "mp3" :
3202								self._getMovie().fl_setAudio_mp3(media[format]);
3203								break;
3204							case "rtmpa":
3205								self._getMovie().fl_setAudio_rtmp(media[format]);
3206								break;
3207						}
3208						self.status.src = media[format];
3209						self.status.format[format] = true;
3210						self.status.formatType = format;
3211						return false;
3212					}
3213				});
3214
3215				if(this.options.preload === 'auto') {
3216					this._flash_load();
3217					this.status.waitForLoad = false;
3218				}
3219			} catch(err) { this._flashError(err); }
3220		},
3221		_flash_setVideo: function(media) {
3222			var self = this;
3223			try {
3224				// Always finds a format due to checks in setMedia()
3225				$.each(this.formats, function(priority, format) {
3226					if(self.flash.support[format] && media[format]) {
3227						switch (format) {
3228							case "m4v" :
3229							case "flv" :
3230								self._getMovie().fl_setVideo_m4v(media[format]);
3231								break;
3232							case "rtmpv":
3233								self._getMovie().fl_setVideo_rtmp(media[format]);
3234								break;
3235						}
3236						self.status.src = media[format];
3237						self.status.format[format] = true;
3238						self.status.formatType = format;
3239						return false;
3240					}
3241				});
3242
3243				if(this.options.preload === 'auto') {
3244					this._flash_load();
3245					this.status.waitForLoad = false;
3246				}
3247			} catch(err) { this._flashError(err); }
3248		},
3249		_flash_resetMedia: function() {
3250			this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
3251			this._flash_pause(NaN);
3252		},
3253		_flash_clearMedia: function() {
3254			try {
3255				this._getMovie().fl_clearMedia();
3256			} catch(err) { this._flashError(err); }
3257		},
3258		_flash_load: function() {
3259			try {
3260				this._getMovie().fl_load();
3261			} catch(err) { this._flashError(err); }
3262			this.status.waitForLoad = false;
3263		},
3264		_flash_play: function(time) {
3265			try {
3266				this._getMovie().fl_play(time);
3267			} catch(err) { this._flashError(err); }
3268			this.status.waitForLoad = false;
3269			this._flash_checkWaitForPlay();
3270		},
3271		_flash_pause: function(time) {
3272			try {
3273				this._getMovie().fl_pause(time);
3274			} catch(err) { this._flashError(err); }
3275			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
3276				this.status.waitForLoad = false;
3277				this._flash_checkWaitForPlay();
3278			}
3279		},
3280		_flash_playHead: function(p) {
3281			try {
3282				this._getMovie().fl_play_head(p);
3283			} catch(err) { this._flashError(err); }
3284			if(!this.status.waitForLoad) {
3285				this._flash_checkWaitForPlay();
3286			}
3287		},
3288		_flash_checkWaitForPlay: function() {
3289			if(this.status.waitForPlay) {
3290				this.status.waitForPlay = false;
3291				if(this.css.jq.videoPlay.length) {
3292					this.css.jq.videoPlay.hide();
3293				}
3294				if(this.status.video) {
3295					this.internal.poster.jq.hide();
3296					this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
3297				}
3298			}
3299		},
3300		_flash_volume: function(v) {
3301			try {
3302				this._getMovie().fl_volume(v);
3303			} catch(err) { this._flashError(err); }
3304		},
3305		_flash_mute: function(m) {
3306			try {
3307				this._getMovie().fl_mute(m);
3308			} catch(err) { this._flashError(err); }
3309		},
3310		_getMovie: function() {
3311			return document[this.internal.flash.id];
3312		},
3313		_getFlashPluginVersion: function() {
3314
3315			// _getFlashPluginVersion() code influenced by:
3316			// - FlashReplace 1.01: http://code.google.com/p/flashreplace/
3317			// - SWFObject 2.2: http://code.google.com/p/swfobject/
3318
3319			var version = 0,
3320				flash;
3321			if(window.ActiveXObject) {
3322				try {
3323					flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
3324					if (flash) { // flash will return null when ActiveX is disabled
3325						var v = flash.GetVariable("$version");
3326						if(v) {
3327							v = v.split(" ")[1].split(",");
3328							version = parseInt(v[0], 10) + "." + parseInt(v[1], 10);
3329						}
3330					}
3331				} catch(e) {}
3332			}
3333			else if(navigator.plugins && navigator.mimeTypes.length > 0) {
3334				flash = navigator.plugins["Shockwave Flash"];
3335				if(flash) {
3336					version = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1");
3337				}
3338			}
3339			return version * 1; // Converts to a number
3340		},
3341		_checkForFlash: function (version) {
3342			var flashOk = false;
3343			if(this._getFlashPluginVersion() >= version) {
3344				flashOk = true;
3345			}
3346			return flashOk;
3347		},
3348		_validString: function(url) {
3349			return (url && typeof url === "string"); // Empty strings return false
3350		},
3351		_limitValue: function(value, min, max) {
3352			return (value < min) ? min : ((value > max) ? max : value);
3353		},
3354		_urlNotSetError: function(context) {
3355			this._error( {
3356				type: $.jPlayer.error.URL_NOT_SET,
3357				context: context,
3358				message: $.jPlayer.errorMsg.URL_NOT_SET,
3359				hint: $.jPlayer.errorHint.URL_NOT_SET
3360			});
3361		},
3362		_flashError: function(error) {
3363			var errorType;
3364			if(!this.internal.ready) {
3365				errorType = "FLASH";
3366			} else {
3367				errorType = "FLASH_DISABLED";
3368			}
3369			this._error( {
3370				type: $.jPlayer.error[errorType],
3371				context: this.internal.flash.swf,
3372				message: $.jPlayer.errorMsg[errorType] + error.message,
3373				hint: $.jPlayer.errorHint[errorType]
3374			});
3375			// Allow the audio player to recover if display:none and then shown again, or with position:fixed on Firefox.
3376			// This really only affects audio in a media player, as an audio player could easily move the jPlayer element away from such issues.
3377			this.internal.flash.jq.css({'width':'1px', 'height':'1px'});
3378		},
3379		_error: function(error) {
3380			this._trigger($.jPlayer.event.error, error);
3381			if(this.options.errorAlerts) {
3382				this._alert("Error!" + (error.message ? "\n" + error.message : "") + (error.hint ? "\n" + error.hint : "") + "\nContext: " + error.context);
3383			}
3384		},
3385		_warning: function(warning) {
3386			this._trigger($.jPlayer.event.warning, undefined, warning);
3387			if(this.options.warningAlerts) {
3388				this._alert("Warning!" + (warning.message ? "\n" + warning.message : "") + (warning.hint ? "\n" + warning.hint : "") + "\nContext: " + warning.context);
3389			}
3390		},
3391		_alert: function(message) {
3392			var msg = "jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message;
3393			if(!this.options.consoleAlerts) {
3394				alert(msg);
3395			} else if(window.console && window.console.log) {
3396				window.console.log(msg);
3397			}
3398		},
3399		_emulateHtmlBridge: function() {
3400			var self = this;
3401
3402			// Emulate methods on jPlayer's DOM element.
3403			$.each( $.jPlayer.emulateMethods.split(/\s+/g), function(i, name) {
3404				self.internal.domNode[name] = function(arg) {
3405					self[name](arg);
3406				};
3407
3408			});
3409
3410			// Bubble jPlayer events to its DOM element.
3411			$.each($.jPlayer.event, function(eventName,eventType) {
3412				var nativeEvent = true;
3413				$.each( $.jPlayer.reservedEvent.split(/\s+/g), function(i, name) {
3414					if(name === eventName) {
3415						nativeEvent = false;
3416						return false;
3417					}
3418				});
3419				if(nativeEvent) {
3420					self.element.bind(eventType + ".jPlayer.jPlayerHtml", function() { // With .jPlayer & .jPlayerHtml namespaces.
3421						self._emulateHtmlUpdate();
3422						var domEvent = document.createEvent("Event");
3423						domEvent.initEvent(eventName, false, true);
3424						self.internal.domNode.dispatchEvent(domEvent);
3425					});
3426				}
3427				// The error event would require a special case
3428			});
3429
3430			// IE9 has a readyState property on all elements. The document should have it, but all (except media) elements inherit it in IE9. This conflicts with Popcorn, which polls the readyState.
3431		},
3432		_emulateHtmlUpdate: function() {
3433			var self = this;
3434
3435			$.each( $.jPlayer.emulateStatus.split(/\s+/g), function(i, name) {
3436				self.internal.domNode[name] = self.status[name];
3437			});
3438			$.each( $.jPlayer.emulateOptions.split(/\s+/g), function(i, name) {
3439				self.internal.domNode[name] = self.options[name];
3440			});
3441		},
3442		_destroyHtmlBridge: function() {
3443			var self = this;
3444
3445			// Bridge event handlers are also removed by destroy() through .jPlayer namespace.
3446			this.element.unbind(".jPlayerHtml"); // Remove all event handlers created by the jPlayer bridge. So you can change the emulateHtml option.
3447
3448			// Remove the methods and properties
3449			var emulated = $.jPlayer.emulateMethods + " " + $.jPlayer.emulateStatus + " " + $.jPlayer.emulateOptions;
3450			$.each( emulated.split(/\s+/g), function(i, name) {
3451				delete self.internal.domNode[name];
3452			});
3453		}
3454	};
3455
3456	$.jPlayer.error = {
3457		FLASH: "e_flash",
3458		FLASH_DISABLED: "e_flash_disabled",
3459		NO_SOLUTION: "e_no_solution",
3460		NO_SUPPORT: "e_no_support",
3461		URL: "e_url",
3462		URL_NOT_SET: "e_url_not_set",
3463		VERSION: "e_version"
3464	};
3465
3466	$.jPlayer.errorMsg = {
3467		FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError()
3468		FLASH_DISABLED: "jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ", // Used in: _flashError()
3469		NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init()
3470		NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia()
3471		URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners()
3472		URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead()
3473		VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady()
3474	};
3475
3476	$.jPlayer.errorHint = {
3477		FLASH: "Check your swfPath option and that Jplayer.swf is there.",
3478		FLASH_DISABLED: "Check that you have not display:none; the jPlayer entity or any ancestor.",
3479		NO_SOLUTION: "Review the jPlayer options: support and supplied.",
3480		NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.",
3481		URL: "Check media URL is valid.",
3482		URL_NOT_SET: "Use setMedia() to set the media URL.",
3483		VERSION: "Update jPlayer files."
3484	};
3485
3486	$.jPlayer.warning = {
3487		CSS_SELECTOR_COUNT: "e_css_selector_count",
3488		CSS_SELECTOR_METHOD: "e_css_selector_method",
3489		CSS_SELECTOR_STRING: "e_css_selector_string",
3490		OPTION_KEY: "e_option_key"
3491	};
3492
3493	$.jPlayer.warningMsg = {
3494		CSS_SELECTOR_COUNT: "The number of css selectors found did not equal one: ",
3495		CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",
3496		CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",
3497		OPTION_KEY: "The option requested in jPlayer('option') is undefined."
3498	};
3499
3500	$.jPlayer.warningHint = {
3501		CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.",
3502		CSS_SELECTOR_METHOD: "Check your method name.",
3503		CSS_SELECTOR_STRING: "Check your css selector is a string.",
3504		OPTION_KEY: "Check your option name."
3505	};
3506}));
3507