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