/*
 * jPlayer Plugin for jQuery JavaScript Library
 * http://www.happyworm.com/jquery/jplayer
 *
 * Copyright (c) 2009 - 2010 Happyworm Ltd
 * Dual licensed under the MIT and GPL licenses.
 *  - http://www.opensource.org/licenses/mit-license.php
 *  - http://www.gnu.org/copyleft/gpl.html
 *
 * Author: Mark J Panaghiston
 * Version: 2.0.0
 * Date: 20th December 2010
 */

(function($, undefined) {

    // Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge
    $.fn.jPlayer = function( options ) {
        var name = "jPlayer";
        var isMethodCall = typeof options === "string",
            args = Array.prototype.slice.call( arguments, 1 ),
            returnValue = this;

        // allow multiple hashes to be passed on init
        options = !isMethodCall && args.length ?
            $.extend.apply( null, [ true, options ].concat(args) ) :
            options;

        // prevent calls to internal methods
        if ( isMethodCall && options.charAt( 0 ) === "_" ) {
            return returnValue;
        }

        if ( isMethodCall ) {
            this.each(function() {
                var instance = $.data( this, name ),
                    methodValue = instance && $.isFunction( instance[options] ) ?
                        instance[ options ].apply( instance, args ) :
                        instance;
                if ( methodValue !== instance && methodValue !== undefined ) {
                    returnValue = methodValue;
                    return false;
                }
            });
        } else {
            this.each(function() {
                var instance = $.data( this, name );
                if ( instance ) {
                    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.
                    instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
                } else {
                    $.data( this, name, new $.jPlayer( options, this ) );
                }
            });
        }

        return returnValue;
    };

    $.jPlayer = function( options, element ) {
        // allow instantiation without initializing for simple inheritance
        if ( arguments.length ) {
            this.element = $(element);
            this.options = $.extend(true, {},
                this.options,
                options
            );
            var self = this;
            this.element.bind( "remove.jPlayer", function() {
                self.destroy();
            });
            this._init();
        }
    };
    // End of: (Adapted from jquery.ui.widget.js (1.8.7))

    $.jPlayer.event = {
        ready: "jPlayer_ready",
        resize: "jPlayer_resize", // Not implemented.
        error: "jPlayer_error", // Event error code in event.jPlayer.error.type. See $.jPlayer.error
        warning: "jPlayer_warning", // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning

        // Other events match HTML5 spec.
        loadstart: "jPlayer_loadstart",
        progress: "jPlayer_progress",
        suspend: "jPlayer_suspend",
        abort: "jPlayer_abort",
        emptied: "jPlayer_emptied",
        stalled: "jPlayer_stalled",
        play: "jPlayer_play",
        pause: "jPlayer_pause",
        loadedmetadata: "jPlayer_loadedmetadata",
        loadeddata: "jPlayer_loadeddata",
        waiting: "jPlayer_waiting",
        playing: "jPlayer_playing",
        canplay: "jPlayer_canplay",
        canplaythrough: "jPlayer_canplaythrough",
        seeking: "jPlayer_seeking",
        seeked: "jPlayer_seeked",
        timeupdate: "jPlayer_timeupdate",
        ended: "jPlayer_ended",
        ratechange: "jPlayer_ratechange",
        durationchange: "jPlayer_durationchange",
        volumechange: "jPlayer_volumechange"
    };

    $.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
        "loadstart",
        // "progress", // jPlayer uses internally before bubbling.
        // "suspend", // jPlayer uses internally before bubbling.
        "abort",
        // "error", // jPlayer uses internally before bubbling.
        "emptied",
        "stalled",
        // "play", // jPlayer uses internally before bubbling.
        // "pause", // jPlayer uses internally before bubbling.
        "loadedmetadata",
        "loadeddata",
        // "waiting", // jPlayer uses internally before bubbling.
        // "playing", // jPlayer uses internally before bubbling.
        // "canplay", // jPlayer fixes the volume (for Chrome) before bubbling.
        "canplaythrough",
        // "seeking", // jPlayer uses internally before bubbling.
        // "seeked", // jPlayer uses internally before bubbling.
        // "timeupdate", // jPlayer uses internally before bubbling.
        // "ended", // jPlayer uses internally before bubbling.
        "ratechange"
        // "durationchange" // jPlayer uses internally before bubbling.
        // "volumechange" // Handled by jPlayer in volume() method, primarily due to the volume fix (for Chrome) in the canplay event. [*] Need to review whether the latest Chrome still needs the fix sometime.
    ];

    $.jPlayer.pause = function() {
        // $.each($.jPlayer.instances, function(i, element) {
        $.each($.jPlayer.prototype.instances, function(i, element) {
            if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
                element.jPlayer("pause");
            }
        });
    };
    
    $.jPlayer.timeFormat = {
        showHour: false,
        showMin: true,
        showSec: true,
        padHour: false,
        padMin: true,
        padSec: true,
        sepHour: ":",
        sepMin: ":",
        sepSec: ""
    };

    $.jPlayer.convertTime = function(sec) {
        var myTime = new Date(sec * 1000);
        var hour = myTime.getUTCHours();
        var min = myTime.getUTCMinutes();
        var sec = myTime.getUTCSeconds();
        var strHour = ($.jPlayer.timeFormat.padHour && hour < 10) ? "0" + hour : hour;
        var strMin = ($.jPlayer.timeFormat.padMin && min < 10) ? "0" + min : min;
        var strSec = ($.jPlayer.timeFormat.padSec && sec < 10) ? "0" + sec : sec;
        return (($.jPlayer.timeFormat.showHour) ? strHour + $.jPlayer.timeFormat.sepHour : "") + (($.jPlayer.timeFormat.showMin) ? strMin + $.jPlayer.timeFormat.sepMin : "") + (($.jPlayer.timeFormat.showSec) ? strSec + $.jPlayer.timeFormat.sepSec : "");
    };

    // Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
    $.jPlayer.uaMatch = function( ua ) {
        var ua = ua.toLowerCase();

        // Useragent RegExp
        var rwebkit = /(webkit)[ \/]([\w.]+)/;
        var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
        var rmsie = /(msie) ([\w.]+)/;
        var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;

        var match = rwebkit.exec( ua ) ||
            ropera.exec( ua ) ||
            rmsie.exec( ua ) ||
            ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
            [];

        return { browser: match[1] || "", version: match[2] || "0" };
    };

    $.jPlayer.browser = {
    };

    var browserMatch = $.jPlayer.uaMatch(navigator.userAgent);
    if ( browserMatch.browser ) {
        $.jPlayer.browser[ browserMatch.browser ] = true;
        $.jPlayer.browser.version = browserMatch.version;
    }

    $.jPlayer.prototype = {
        count: 0, // Static Variable: Change it via prototype.
        version: { // Static Object
            script: "2.0.0",
            needFlash: "2.0.0",
            flash: "unknown"
        },
        options: { // Instanced in $.jPlayer() constructor
            swfPath: "js", // Path to Jplayer.swf. Can be relative, absolute or server root relative.
            solution: "html, flash", // Valid solutions: html, flash. Order defines priority. 1st is highest,
            supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
            preload: 'metadata',  // HTML5 Spec values: none, metadata, auto.
            volume: 0.8, // The volume. Number 0 to 1.
            muted: false,
            backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
            cssSelectorAncestor: "#jp_interface_1",
            cssSelector: {
                videoPlay: ".jp-video-play",
                play: ".jp-play",
                pause: ".jp-pause",
                stop: ".jp-stop",
                seekBar: ".jp-seek-bar",
                playBar: ".jp-play-bar",
                mute: ".jp-mute",
                unmute: ".jp-unmute",
                volumeBar: ".jp-volume-bar",
                volumeBarValue: ".jp-volume-bar-value",
                currentTime: ".jp-current-time",
                duration: ".jp-duration"
            },
            // globalVolume: false, // Not implemented: Set to make volume changes affect all jPlayer instances
            // globalMute: false, // Not implemented: Set to make mute changes affect all jPlayer instances
            idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
            errorAlerts: false,
            warningAlerts: false
        },
        instances: {}, // Static Object
        status: { // Instanced in _init()
            src: "",
            media: {},
            paused: true,
            format: {},
            formatType: "",
            waitForPlay: true, // Same as waitForLoad except in case where preloading.
            waitForLoad: true,
            srcSet: false,
            video: false, // True if playing a video
            seekPercent: 0,
            currentPercentRelative: 0,
            currentPercentAbsolute: 0,
            currentTime: 0,
            duration: 0
        },
        _status: { // Instanced in _init(): These status values are persistent. ie., Are not affected by a status reset.
            volume: undefined, // Set by constructor option/default.
            muted: false, // Set by constructor option/default.
            width: 0, // Read from CSS
            height: 0 // Read from CSS
        },
        internal: { // Instanced in _init()
            ready: false,
            instance: undefined,
            htmlDlyCmdId: undefined
        },
        solution: { // Static Object: Defines the solutions built in jPlayer.
            html: true,
            flash: true
        },
        // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
        format: { // Static Object
            mp3: {
                codec: 'audio/mpeg; codecs="mp3"',
                flashCanPlay: true,
                media: 'audio'
            },
            m4a: { // AAC / MP4
                codec: 'audio/mp4; codecs="mp4a.40.2"',
                flashCanPlay: true,
                media: 'audio'
            },
            oga: { // OGG
                codec: 'audio/ogg; codecs="vorbis"',
                flashCanPlay: false,
                media: 'audio'
            },
            wav: { // PCM
                codec: 'audio/wav; codecs="1"',
                flashCanPlay: false,
                media: 'audio'
            },
            webma: { // WEBM
                codec: 'audio/webm; codecs="vorbis"',
                flashCanPlay: false,
                media: 'audio'
            },
            m4v: { // H.264 / MP4
                codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
                flashCanPlay: true,
                media: 'video'
            },
            ogv: { // OGG
                codec: 'video/ogg; codecs="theora, vorbis"',
                flashCanPlay: false,
                media: 'video'
            },
            webmv: { // WEBM
                codec: 'video/webm; codecs="vorbis, vp8"',
                flashCanPlay: false,
                media: 'video'
            }
        },
        _init: function() {
            var self = this;
            
            this.element.empty();
            
            this.status = $.extend({}, this.status, this._status); // Copy static to unique instance. Adds the status propeties that persist through a reset. NB: Might want to use $.jPlayer.prototype.status instead once options completely implmented and _init() returned to $.fn.jPlayer plugin. 
            this.internal = $.extend({}, this.internal); // Copy static to unique instance.

            this.formats = []; // Array based on supplied string option. Order defines priority.
            this.solutions = []; // Array based on solution string option. Order defines priority.
            this.require = {}; // Which media types are required: video, audio.
            
            this.htmlElement = {}; // DOM elements created by jPlayer
            this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
            this.html.audio = {};
            this.html.video = {};
            this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
            
            this.css = {};
            this.css.cs = {}; // Holds the css selector strings
            this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)

            this.status.volume = this._limitValue(this.options.volume, 0, 1); // Set volume status from constructor option.
            this.status.muted = this.options.muted; // Set muted status from constructor option.
            this.status.width = this.element.css('width'); // Sets from CSS.
            this.status.height = this.element.css('height'); // Sets from CSS.

            this.element.css({'background-color': this.options.backgroundColor});

            // Create the formats array, with prority based on the order of the supplied formats string
            $.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
                var format = value1.replace(/^\s+|\s+$/g, ""); //trim
                if(self.format[format]) { // Check format is valid.
                    var dupFound = false;
                    $.each(self.formats, function(index2, value2) { // Check for duplicates
                        if(format === value2) {
                            dupFound = true;
                            return false;
                        }
                    });
                    if(!dupFound) {
                        self.formats.push(format);
                    }
                }
            });

            // Create the solutions array, with prority based on the order of the solution string
            $.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
                var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
                if(self.solution[solution]) { // Check solution is valid.
                    var dupFound = false;
                    $.each(self.solutions, function(index2, value2) { // Check for duplicates
                        if(solution === value2) {
                            dupFound = true;
                            return false;
                        }
                    });
                    if(!dupFound) {
                        self.solutions.push(solution);
                    }
                }
            });

            this.internal.instance = "jp_" + this.count;
            this.instances[this.internal.instance] = this.element;

            // Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
            if(this.element.attr("id") === "") {
                this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
            }

            this.internal.self = $.extend({}, {
                id: this.element.attr("id"),
                jq: this.element
            });
            this.internal.audio = $.extend({}, {
                id: this.options.idPrefix + "_audio_" + this.count,
                jq: undefined
            });
            this.internal.video = $.extend({}, {
                id: this.options.idPrefix + "_video_" + this.count,
                jq: undefined
            });
            this.internal.flash = $.extend({}, {
                id: this.options.idPrefix + "_flash_" + this.count,
                jq: undefined,
                swf: this.options.swfPath + ((this.options.swfPath !== "" && this.options.swfPath.slice(-1) !== "/") ? "/" : "") + "Jplayer.swf"
            });
            this.internal.poster = $.extend({}, {
                id: this.options.idPrefix + "_poster_" + this.count,
                jq: undefined
            });

            // Register listeners defined in the constructor
            $.each($.jPlayer.event, function(eventName,eventType) {
                if(self.options[eventName] !== undefined) {
                    self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
                    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.
                }
            });
            
            // Create the poster image.
            this.htmlElement.poster = document.createElement('img');
            this.htmlElement.poster.id = this.internal.poster.id;
            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.
                if(!self.status.video || self.status.waitForPlay) {
                    self.internal.poster.jq.show();
                }
            };
            this.element.append(this.htmlElement.poster);
            this.internal.poster.jq = $("#" + this.internal.poster.id);
            this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
            this.internal.poster.jq.hide();
            
            // Determine if we require solutions for audio, video or both media types.
            this.require.audio = false;
            this.require.video = false;
            $.each(this.formats, function(priority, format) {
                self.require[self.format[format].media] = true;
            });
            
            this.html.audio.available = false;
            if(this.require.audio) { // If a supplied format is audio
                this.htmlElement.audio = document.createElement('audio');
                this.htmlElement.audio.id = this.internal.audio.id;
                this.html.audio.available = !!this.htmlElement.audio.canPlayType;
            }
            this.html.video.available = false;
            if(this.require.video) { // If a supplied format is video
                this.htmlElement.video = document.createElement('video');
                this.htmlElement.video.id = this.internal.video.id;
                this.html.video.available = !!this.htmlElement.video.canPlayType;
            }

            this.flash.available = this._checkForFlash(10); // IE9 forced to false due to ExternalInterface problem.

            this.html.canPlay = {};
            this.flash.canPlay = {};
            $.each(this.formats, function(priority, format) {
                self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
                self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
            });
            this.html.desired = false;
            this.flash.desired = false;
            $.each(this.solutions, function(solutionPriority, solution) {
                if(solutionPriority === 0) {
                    self[solution].desired = true;
                } else {
                    var audioCanPlay = false;
                    var videoCanPlay = false;
                    $.each(self.formats, function(formatPriority, format) {
                        if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
                            if(self.format[format].media === 'video') {
                                videoCanPlay = true;
                            } else {
                                audioCanPlay = true;
                            }
                        }
                    });
                    self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
                }
            });
            // This is what jPlayer will support, based on solution and supplied.
            this.html.support = {};
            this.flash.support = {};
            $.each(this.formats, function(priority, format) {
                self.html.support[format] = self.html.canPlay[format] && self.html.desired;
                self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
            });
            // If jPlayer is supporting any format in a solution, then the solution is used.
            this.html.used = false;
            this.flash.used = false;
            $.each(this.solutions, function(solutionPriority, solution) {
                $.each(self.formats, function(formatPriority, format) {
                    if(self[solution].support[format]) {
                        self[solution].used = true;
                        return false;
                    }
                });
            });

            // If neither html nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
            if(!(this.html.used || this.flash.used)) {
                this._error( {
                    type: $.jPlayer.error.NO_SOLUTION, 
                    context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
                    message: $.jPlayer.errorMsg.NO_SOLUTION,
                    hint: $.jPlayer.errorHint.NO_SOLUTION
                });
            }

            // Init solution active state and the event gates to false.
            this.html.active = false;
            this.html.audio.gate = false;
            this.html.video.gate = false;
            this.flash.active = false;
            this.flash.gate = false;

            // Add the flash solution if it is being used.
            if(this.flash.used) {
                var flashVars = 'id=' + escape(this.internal.self.id) + '&vol=' + this.status.volume + '&muted=' + this.status.muted;

                if($.browser.msie && Number($.browser.version) <= 8) {
                    var html_obj = '<object id="' + this.internal.flash.id + '"';
                    html_obj += ' classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"';
                    html_obj += ' codebase="' + document.URL.substring(0,document.URL.indexOf(':')) + '://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab"'; // Fixed IE non secured element warning.
                    html_obj += ' type="application/x-shockwave-flash"';
                    html_obj += ' width="0" height="0">';
                    html_obj += '</object>';

                    var obj_param = [];
                    obj_param[0] = '<param name="movie" value="' + this.internal.flash.swf + '" />';
                    obj_param[1] = '<param name="quality" value="high" />';
                    obj_param[2] = '<param name="FlashVars" value="' + flashVars + '" />';
                    obj_param[3] = '<param name="allowScriptAccess" value="always" />';
                    obj_param[4] = '<param name="bgcolor" value="' + this.options.backgroundColor + '" />';

                    var ie_dom = document.createElement(html_obj);
                    for(var i=0; i < obj_param.length; i++) {
                        ie_dom.appendChild(document.createElement(obj_param[i]));
                    }
                    this.element.append(ie_dom);
                } else {
                    var html_embed = '<embed name="' + this.internal.flash.id + '" id="' + this.internal.flash.id + '" src="' + this.internal.flash.swf + '"';
                    html_embed += ' width="0" height="0" bgcolor="' + this.options.backgroundColor + '"';
                    html_embed += ' quality="high" FlashVars="' + flashVars + '"';
                    html_embed += ' allowScriptAccess="always"';
                    html_embed += ' type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />';
                    this.element.append(html_embed);
                }
                this.internal.flash.jq = $("#" + this.internal.flash.id);
                this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
            }
            
            // Add the HTML solution if being used.
            if(this.html.used) {

                // The HTML Audio handlers
                if(this.html.audio.available) {
                    this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
                    this.element.append(this.htmlElement.audio);
                    this.internal.audio.jq = $("#" + this.internal.audio.id);
                }

                // The HTML Video handlers
                if(this.html.video.available) {
                    this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
                    this.element.append(this.htmlElement.video);
                    this.internal.video.jq = $("#" + this.internal.video.id);
                    this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
                }
            }

            if(this.html.used && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
                window.setTimeout( function() {
                    self.internal.ready = true;
                    self.version.flash = "n/a";
                    self._trigger($.jPlayer.event.ready);
                }, 100);
            }

            // Set up the css selectors for the control and feedback entities.
            $.each(this.options.cssSelector, function(fn, cssSel) {
                self._cssSelector(fn, cssSel);
            });

            this._updateInterface();
            this._updateButtons(false);
            this._updateVolume(this.status.volume);
            this._updateMute(this.status.muted);
            if(this.css.jq.videoPlay.length) {
                this.css.jq.videoPlay.hide();
            }
            $.jPlayer.prototype.count++; // Change static variable via prototype.
        },
        destroy: function() {
            // MJP: The background change remains. Review later.

            // Reset the interface, remove seeking effect and times.
            this._resetStatus();
            this._updateInterface();
            this._seeked();
            if(this.css.jq.currentTime.length) {
                this.css.jq.currentTime.text("");
            }
            if(this.css.jq.duration.length) {
                this.css.jq.duration.text("");
            }

            if(this.status.srcSet) { // Or you get a bogus error event
                this.pause(); // Pauses the media and clears any delayed commands used in the HTML solution.
            }
            $.each(this.css.jq, function(fn, jq) { // Remove any bindings from the interface controls.
                jq.unbind(".jPlayer");
            });
            this.element.removeData("jPlayer"); // Remove jPlayer data
            this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
            this.element.empty(); // Remove the inserted child elements
            
            this.instances[this.internal.instance] = undefined; // Clear the instance on the static instance object
        },
        enable: function() { // Plan to implement
            // options.disabled = false
        },
        disable: function () { // Plan to implement
            // options.disabled = true
        },
        _addHtmlEventListeners: function(mediaElement, entity) {
            var self = this;
            mediaElement.preload = this.options.preload;
            mediaElement.muted = this.options.muted;

            // Create the event listeners
            // Only want the active entity to affect jPlayer and bubble events.
            // Using entity.gate so that object is referenced and gate property always current
            
            mediaElement.addEventListener("progress", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._getHtmlStatus(mediaElement);
                    self._updateInterface();
                    self._trigger($.jPlayer.event.progress);
                }
            }, false);
            mediaElement.addEventListener("timeupdate", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._getHtmlStatus(mediaElement);
                    self._updateInterface();
                    self._trigger($.jPlayer.event.timeupdate);
                }
            }, false);
            mediaElement.addEventListener("durationchange", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self.status.duration = this.duration;
                    self._getHtmlStatus(mediaElement);
                    self._updateInterface();
                    self._trigger($.jPlayer.event.durationchange);
                }
            }, false);
            mediaElement.addEventListener("play", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._updateButtons(true);
                    self._trigger($.jPlayer.event.play);
                }
            }, false);
            mediaElement.addEventListener("playing", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._updateButtons(true);
                    self._seeked();
                    self._trigger($.jPlayer.event.playing);
                }
            }, false);
            mediaElement.addEventListener("pause", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._updateButtons(false);
                    self._trigger($.jPlayer.event.pause);
                }
            }, false);
            mediaElement.addEventListener("waiting", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._seeking();
                    self._trigger($.jPlayer.event.waiting);
                }
            }, false);
            mediaElement.addEventListener("canplay", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    mediaElement.volume = self._volumeFix(self.status.volume);
                    self._trigger($.jPlayer.event.canplay);
                }
            }, false);
            mediaElement.addEventListener("seeking", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._seeking();
                    self._trigger($.jPlayer.event.seeking);
                }
            }, false);
            mediaElement.addEventListener("seeked", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._seeked();
                    self._trigger($.jPlayer.event.seeked);
                }
            }, false);
            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.
                if(entity.gate && !self.status.waitForLoad) {
                    self._seeked();
                    self._trigger($.jPlayer.event.suspend);
                }
            }, false);
            mediaElement.addEventListener("ended", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    // Order of the next few commands are important. Change the time and then pause.
                    // Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
                    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.
                        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.)
                    }
                    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.
                    self._updateButtons(false);
                    self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
                    self._updateInterface();
                    self._trigger($.jPlayer.event.ended);
                }
            }, false);
            mediaElement.addEventListener("error", function() {
                if(entity.gate && !self.status.waitForLoad) {
                    self._updateButtons(false);
                    self._seeked();
                    if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
                        self.status.waitForLoad = true; // Allows the load operation to try again.
                        self.status.waitForPlay = true; // Reset since a play was captured.
                        if(self.status.video) {
                            self.internal.video.jq.css({'width':'0px', 'height':'0px'});
                        }
                        if(self._validString(self.status.media.poster)) {
                            self.internal.poster.jq.show();
                        }
                        if(self.css.jq.videoPlay.length) {
                            self.css.jq.videoPlay.show();
                        }
                        self._error( {
                            type: $.jPlayer.error.URL,
                            context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
                            message: $.jPlayer.errorMsg.URL,
                            hint: $.jPlayer.errorHint.URL
                        });
                    }
                }
            }, false);
            // Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
            $.each($.jPlayer.htmlEvent, function(i, eventType) {
                mediaElement.addEventListener(this, function() {
                    if(entity.gate && !self.status.waitForLoad) {
                        self._trigger($.jPlayer.event[eventType]);
                    }
                }, false);
            });
        },
        _getHtmlStatus: function(media, override) {
            var ct = 0, d = 0, cpa = 0, sp = 0, cpr = 0;
            
            ct = media.currentTime;
            cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
            if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
                sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
                cpr = 100 * media.currentTime / media.seekable.end(media.seekable.length-1);
            } else {
                sp = 100;
                cpr = cpa;
            }
            
            if(override) {
                ct = 0;
                cpr = 0;
                cpa = 0;
            }

            this.status.seekPercent = sp;
            this.status.currentPercentRelative = cpr;
            this.status.currentPercentAbsolute = cpa;
            this.status.currentTime = ct;
        },
        _resetStatus: function() {
            var self = this;
            this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset. ie., The properties of this._status, contained in the current this.status.

        },
        _trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
            var event = $.Event(eventType);
            event.jPlayer = {};
            event.jPlayer.version = $.extend({}, this.version);
            event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
            event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
            event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
            if(error) event.jPlayer.error = $.extend({}, error);
            if(warning) event.jPlayer.warning = $.extend({}, warning);
            this.element.trigger(event);
        },
        jPlayerFlashEvent: function(eventType, status) { // Called from Flash
            if(eventType === $.jPlayer.event.ready && !this.internal.ready) {
                this.internal.ready = true;
                this.version.flash = status.version;
                if(this.version.needFlash !== this.version.flash) {
                    this._error( {
                        type: $.jPlayer.error.VERSION,
                        context: this.version.flash,
                        message: $.jPlayer.errorMsg.VERSION + this.version.flash,
                        hint: $.jPlayer.errorHint.VERSION
                    });
                }
                this._trigger(eventType);
            }
            if(this.flash.gate) {
                switch(eventType) {
                    case $.jPlayer.event.progress:
                        this._getFlashStatus(status);
                        this._updateInterface();
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.timeupdate:
                        this._getFlashStatus(status);
                        this._updateInterface();
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.play:
                        this._seeked();
                        this._updateButtons(true);
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.pause:
                        this._updateButtons(false);
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.ended:
                        this._updateButtons(false);
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.error:
                        this.status.waitForLoad = true; // Allows the load operation to try again.
                        this.status.waitForPlay = true; // Reset since a play was captured.
                        if(this.status.video) {
                            this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
                        }
                        if(this._validString(this.status.media.poster)) {
                            this.internal.poster.jq.show();
                        }
                        if(this.css.jq.videoPlay.length) {
                            this.css.jq.videoPlay.show();
                        }
                        if(this.status.video) { // Set up for another try. Execute before error event.
                            this._flash_setVideo(this.status.media);
                        } else {
                            this._flash_setAudio(this.status.media);
                        }
                        this._error( {
                            type: $.jPlayer.error.URL,
                            context:status.src,
                            message: $.jPlayer.errorMsg.URL,
                            hint: $.jPlayer.errorHint.URL
                        });
                        break;
                    case $.jPlayer.event.seeking:
                        this._seeking();
                        this._trigger(eventType);
                        break;
                    case $.jPlayer.event.seeked:
                        this._seeked();
                        this._trigger(eventType);
                        break;
                    default:
                        this._trigger(eventType);
                }
            }
            return false;
        },
        _getFlashStatus: function(status) {
            this.status.seekPercent = status.seekPercent;
            this.status.currentPercentRelative = status.currentPercentRelative;
            this.status.currentPercentAbsolute = status.currentPercentAbsolute;
            this.status.currentTime = status.currentTime;
            this.status.duration = status.duration;
        },
        _updateButtons: function(playing) {
            this.status.paused = !playing;
            if(this.css.jq.play.length && this.css.jq.pause.length) {
                if(playing) {
                    this.css.jq.play.hide();
                    this.css.jq.pause.show();
                } else {
                    this.css.jq.play.show();
                    this.css.jq.pause.hide();
                }
            }
        },
        _updateInterface: function() {
            if(this.css.jq.seekBar.length) {
                //this.css.jq.seekBar.width(this.status.seekPercent+"%");
            }
            if(this.css.jq.playBar.length) {
                this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
            }
            if(this.css.jq.currentTime.length) {
                this.css.jq.currentTime.text($.jPlayer.convertTime(this.status.currentTime));
            }
            if(this.css.jq.duration.length) {
                this.css.jq.duration.text($.jPlayer.convertTime(this.status.duration));
            }
        },
        _seeking: function() {
            if(this.css.jq.seekBar.length) {
                this.css.jq.seekBar.addClass("jp-seeking-bg");
            }
        },
        _seeked: function() {
            if(this.css.jq.seekBar.length) {
                this.css.jq.seekBar.removeClass("jp-seeking-bg");
            }
        },
        setMedia: function(media) {
        
            /*  media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
             *  media.poster = String: Video poster URL.
             *  media.subtitles = String: * NOT IMPLEMENTED * URL of subtitles SRT file
             *  media.chapters = String: * NOT IMPLEMENTED * URL of chapters SRT file
             *  media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
             */
            
            var self = this;
            
            this._seeked();
            clearTimeout(this.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.

            // Store the current html gates, since we need for clearMedia() conditions.
            var audioGate = this.html.audio.gate;
            var videoGate = this.html.video.gate;

            var supported = false;
            $.each(this.formats, function(formatPriority, format) {
                var isVideo = self.format[format].media === 'video';
                $.each(self.solutions, function(solutionPriority, solution) {
                    if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
                        var isHtml = solution === 'html';
                        
                        if(isVideo) {
                            if(isHtml) {
                                self.html.audio.gate = false;
                                self.html.video.gate = true;
                                self.flash.gate = false;
                            } else {
                                self.html.audio.gate = false;
                                self.html.video.gate = false;
                                self.flash.gate = true;
                            }
                        } else {
                            if(isHtml) {
                                self.html.audio.gate = true;
                                self.html.video.gate = false;
                                self.flash.gate = false;
                            } else {
                                self.html.audio.gate = false;
                                self.html.video.gate = false;
                                self.flash.gate = true;
                            }
                        }

                        // Clear media of the previous solution if:
                        //  - it was Flash
                        //  - changing from HTML to Flash
                        //  - the HTML solution media type (audio or video) remained the same.
                        // Note that, we must be careful with clearMedia() on iPhone, otherwise clearing the video when changing to audio corrupts the built in video player.
                        if(self.flash.active || (self.html.active && self.flash.gate) || (audioGate === self.html.audio.gate && videoGate === self.html.video.gate)) {
                            self.clearMedia();
                        } else if(audioGate !== self.html.audio.gate && videoGate !== self.html.video.gate) { // If switching between html elements
                            self._html_pause();
                            // Hide the video if it was being used.
                            if(self.status.video) {
                                self.internal.video.jq.css({'width':'0px', 'height':'0px'});
                            }
                            self._resetStatus(); // Since clearMedia usually does this. Execute after status.video useage.
                        }

                        if(isVideo) {
                            if(isHtml) {
                                self._html_setVideo(media);
                                self.html.active = true;
                                self.flash.active = false;
                            } else {
                                self._flash_setVideo(media);
                                self.html.active = false;
                                self.flash.active = true;
                            }
                            if(self.css.jq.videoPlay.length) {
                                self.css.jq.videoPlay.show();
                            }
                            self.status.video = true;
                        } else {
                            if(isHtml) {
                                self._html_setAudio(media);
                                self.html.active = true;
                                self.flash.active = false;
                            } else {
                                self._flash_setAudio(media);
                                self.html.active = false;
                                self.flash.active = true;
                            }
                            if(self.css.jq.videoPlay.length) {
                                self.css.jq.videoPlay.hide();
                            }
                            self.status.video = false;
                        }
                        
                        supported = true;
                        return false; // Exit $.each
                    }
                });
                if(supported) {
                    return false; // Exit $.each
                }
            });

            if(supported) {
                // Set poster after the possible clearMedia() command above. IE had issues since the IMG onload event occurred immediately when cached. ie., The clearMedia() hide the poster.
                if(this._validString(media.poster)) {
                    if(this.htmlElement.poster.src !== media.poster) { // Since some browsers do not generate img onload event.
                        this.htmlElement.poster.src = media.poster;
                    } else {
                        this.internal.poster.jq.show();
                    }
                } else {
                    this.internal.poster.jq.hide(); // Hide if not used, since clearMedia() does not always occur above. ie., HTML audio <-> video switching.
                }
                this.status.srcSet = true;
                this.status.media = $.extend({}, media);
                this._updateButtons(false);
                this._updateInterface();
            } else { // jPlayer cannot support any formats provided in this browser
                // Pause here if old media could be playing. Otherwise, playing media being changed to bad media would leave the old media playing.
                if(this.status.srcSet && !this.status.waitForPlay) {
                    this.pause();
                }
                // Reset all the control flags
                this.html.audio.gate = false;
                this.html.video.gate = false;
                this.flash.gate = false;
                this.html.active = false;
                this.flash.active = false;
                // Reset status and interface.
                this._resetStatus();
                this._updateInterface();
                this._updateButtons(false);
                // Hide the any old media
                this.internal.poster.jq.hide();
                if(this.html.used && this.require.video) {
                    this.internal.video.jq.css({'width':'0px', 'height':'0px'});
                }
                if(this.flash.used) {
                    this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
                }
                // Send an error event
                this._error( {
                    type: $.jPlayer.error.NO_SUPPORT,
                    context: "{supplied:'" + this.options.supplied + "'}",
                    message: $.jPlayer.errorMsg.NO_SUPPORT,
                    hint: $.jPlayer.errorHint.NO_SUPPORT
                });
            }
        },
        clearMedia: function() {
            this._resetStatus();
            this._updateButtons(false);

            this.internal.poster.jq.hide();

            clearTimeout(this.internal.htmlDlyCmdId);

            if(this.html.active) {
                this._html_clearMedia();
            } else if(this.flash.active) {
                this._flash_clearMedia();
            }
        },
        load: function() {
            if(this.status.srcSet) {
                if(this.html.active) {
                    this._html_load();
                } else if(this.flash.active) {
                    this._flash_load();
                }
            } else {
                this._urlNotSetError("load");
            }
        },
        play: function(time) {
            time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
            if(this.status.srcSet) {
                if(this.html.active) {
                    this._html_play(time);
                } else if(this.flash.active) {
                    this._flash_play(time);
                }
            } else {
                this._urlNotSetError("play");
            }
        },
        videoPlay: function(e) { // Handles clicks on the play button over the video poster
            this.play();
        },
        pause: function(time) {
            time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
            if(this.status.srcSet) {
                if(this.html.active) {
                    this._html_pause(time);
                } else if(this.flash.active) {
                    this._flash_pause(time);
                }
            } else {
                this._urlNotSetError("pause");
            }
        },
        pauseOthers: function() {
            var self = this;
            $.each(this.instances, function(i, element) {
                if(self.element !== element) { // Do not this instance.
                    if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
                        element.jPlayer("pause");
                    }
                }
            });
        },
        stop: function() {
            if(this.status.srcSet) {
                if(this.html.active) {
                    this._html_pause(0);
                } else if(this.flash.active) {
                    this._flash_pause(0);
                }
            } else {
                this._urlNotSetError("stop");
            }
        },
        playHead: function(p) {
            p = this._limitValue(p, 0, 100);
            if(this.status.srcSet) {
                if(this.html.active) {
                    this._html_playHead(p);
                } else if(this.flash.active) {
                    this._flash_playHead(p);
                }
            } else {
                this._urlNotSetError("playHead");
            }
        },
        mute: function() {
            this.status.muted = true;
            if(this.html.used) {
                this._html_mute(true);
            }
            if(this.flash.used) {
                this._flash_mute(true);
            }
            this._updateMute(true);
            this._updateVolume(0);
            this._trigger($.jPlayer.event.volumechange);
        },
        unmute: function() {
            this.status.muted = false;
            if(this.html.used) {
                this._html_mute(false);
            }
            if(this.flash.used) {
                this._flash_mute(false);
            }
            this._updateMute(false);
            this._updateVolume(this.status.volume);
            this._trigger($.jPlayer.event.volumechange);
        },
        _updateMute: function(mute) {
            if(this.css.jq.mute.length && this.css.jq.unmute.length) {
                if(mute) {
                    this.css.jq.mute.hide();
                    this.css.jq.unmute.show();
                } else {
                    this.css.jq.mute.show();
                    this.css.jq.unmute.hide();
                }
            }
        },
        volume: function(v) {
            v = this._limitValue(v, 0, 1);
            this.status.volume = v;

            if(this.html.used) {
                this._html_volume(v);
            }
            if(this.flash.used) {
                this._flash_volume(v);
            }
            if(!this.status.muted) {
                this._updateVolume(v);
            }
            this._trigger($.jPlayer.event.volumechange);
        },
        volumeBar: function(e) { // Handles clicks on the volumeBar
            if(!this.status.muted && this.css.jq.volumeBar) { // Ignore clicks when muted
                
                var offset = this.css.jq.volumeBar.offset();
                var x = e.pageY - offset.top;

                x = x;
                var w = this.css.jq.volumeBar.height();
                x = w - x;
                var p = 100*x/w;                
                p = p / 100;
                
                this.volume(p);
            }
        },

        
        
        volumeBarValue: function(e) { // Handles clicks on the volumeBarValue
            this.volumeBar(e);
        },
        _updateVolume: function(v) {
            if(this.css.jq.volumeBarValue.length) {
                this.css.jq.volumeBarValue.height((v*100)+"%");
            }
        },
        _volumeFix: function(v) { // Need to review if this is still necessary on latest Chrome
            var rnd = 0.001 * Math.random(); // Fix for Chrome 4: Fix volume being set multiple times before playing bug.
            var fix = (v < 0.5) ? rnd : -rnd; // Fix for Chrome 4: Solves volume change before play bug. (When new vol == old vol Chrome 4 does nothing!)
            return (v + fix); // Fix for Chrome 4: Event solves initial volume not being set correctly.
        },
        _cssSelectorAncestor: function(ancestor, refresh) {
            this.options.cssSelectorAncestor = ancestor;
            if(refresh) {
                $.each(this.options.cssSelector, function(fn, cssSel) {
                    self._cssSelector(fn, cssSel);
                });
            }
        },
        _cssSelector: function(fn, cssSel) {
            var self = this;
            if(typeof cssSel === 'string') {
                if($.jPlayer.prototype.options.cssSelector[fn]) {
                    if(this.css.jq[fn] && this.css.jq[fn].length) {
                        this.css.jq[fn].unbind(".jPlayer");
                    }
                    this.options.cssSelector[fn] = cssSel;
                    this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;

                    if(cssSel) { // Checks for empty string
                        this.css.jq[fn] = $(this.css.cs[fn]);
                    } else {
                        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. 
                    }

                    if(this.css.jq[fn].length) {
                        var handler = function(e) {
                            self[fn](e);
                            $(this).blur();
                            return false;
                        }
                        this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
                    }

                    if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
                        this._warning( {
                            type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
                            context: this.css.cs[fn],
                            message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
                            hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
                        });
                    }
                } else {
                    this._warning( {
                        type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
                        context: fn,
                        message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
                        hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
                    });
                }
            } else {
                this._warning( {
                    type: $.jPlayer.warning.CSS_SELECTOR_STRING,
                    context: cssSel,
                    message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
                    hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
                });
            }
        },
        seekBar: function(e) { // Handles clicks on the seekBar
            if(this.css.jq.seekBar) {
                var offset = this.css.jq.seekBar.offset();
                var x = e.pageX - offset.left;
                var w = this.css.jq.seekBar.width();
                var p = 100*x/w;
                this.playHead(p);
            }
        },
        playBar: function(e) { // Handles clicks on the playBar
            this.seekBar(e);
        },
        currentTime: function(e) { // Handles clicks on the text
            // Added to avoid errors using cssSelector system for the text
        },
        duration: function(e) { // Handles clicks on the text
            // Added to avoid errors using cssSelector system for the text
        },
        // 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.
        option: function(key, value) {
            var options = key;

             // Enables use: options().  Returns a copy of options object
            if ( arguments.length === 0 ) {
                return $.extend( true, {}, this.options );
            }

            if(typeof key === "string") {
                var keys = key.split(".");

                 // Enables use: options("someOption")  Returns a copy of the option. Supports dot notation.
                if(value === undefined) {

                    var opt = $.extend(true, {}, this.options);
                    for(var i = 0; i < keys.length; i++) {
                        if(opt[keys[i]] !== undefined) {
                            opt = opt[keys[i]];
                        } else {
                            this._warning( {
                                type: $.jPlayer.warning.OPTION_KEY,
                                context: key,
                                message: $.jPlayer.warningMsg.OPTION_KEY,
                                hint: $.jPlayer.warningHint.OPTION_KEY
                            });
                            return undefined;
                        }
                    }
                    return opt;
                }

                 // Enables use: options("someOptionObject", someObject}).  Creates: {someOptionObject:someObject}
                 // Enables use: options("someOption", someValue).  Creates: {someOption:someValue}
                 // Enables use: options("someOptionObject.someOption", someValue).  Creates: {someOptionObject:{someOption:someValue}}

                options = {};
                var opt = options;

                for(var i = 0; i < keys.length; i++) {
                    if(i < keys.length - 1) {
                        opt[keys[i]] = {};
                        opt = opt[keys[i]];
                    } else {
                        opt[keys[i]] = value;
                    }
                }
            }

             // Otherwise enables use: options(optionObject).  Uses original object (the key)

            this._setOptions(options);

            return this;
        },
        _setOptions: function(options) {
            var self = this;
            $.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.
                self._setOption(key, value);
            });

            return this;
        },
        _setOption: function(key, value) {
            var self = this;

            // The ability to set options is limited at this time.

            switch(key) {
                case "cssSelectorAncestor" :
                    this.options[key] = value;
                    $.each(self.options.cssSelector, function(fn, cssSel) { // Refresh all associations for new ancestor.
                        self._cssSelector(fn, cssSel);
                    });
                    break;
                case "cssSelector" :
                    $.each(value, function(fn, cssSel) {
                        self._cssSelector(fn, cssSel);
                    });
                    break;
            }

            return this;
        },
        // End of: (Options code adapted from ui.widget.js)

        // The resize() set of functions are not implemented yet.
        // Basically are currently used to allow Flash debugging without too much hassle.
        resize: function(css) {
            // MJP: Want to run some checks on dim {} first.
            if(this.html.active) {
                this._resizeHtml(css);
            }
            if(this.flash.active) {
                this._resizeFlash(css);
            }
            this._trigger($.jPlayer.event.resize);
        },
        _resizePoster: function(css) {
            // Not implemented yet
        },
        _resizeHtml: function(css) {
            // Not implemented yet
        },
        _resizeFlash: function(css) {
            this.internal.flash.jq.css({'width':css.width, 'height':css.height});
        },

        _html_initMedia: function() {
            if(this.status.srcSet  && !this.status.waitForPlay) {
                this.htmlElement.media.pause();
            }
            if(this.options.preload !== 'none') {
                this._html_load();
            }
            this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
        },
        _html_setAudio: function(media) {
            var self = this;
            // Always finds a format due to checks in setMedia()
            $.each(this.formats, function(priority, format) {
                if(self.html.support[format] && media[format]) {
                    self.status.src = media[format];
                    self.status.format[format] = true;
                    self.status.formatType = format;
                    return false;
                }
            });
            this.htmlElement.media = this.htmlElement.audio;
            this._html_initMedia();
        },
        _html_setVideo: function(media) {
            var self = this;
            // Always finds a format due to checks in setMedia()
            $.each(this.formats, function(priority, format) {
                if(self.html.support[format] && media[format]) {
                    self.status.src = media[format];
                    self.status.format[format] = true;
                    self.status.formatType = format;
                    return false;
                }
            });
            this.htmlElement.media = this.htmlElement.video;
            this._html_initMedia();
        },
        _html_clearMedia: function() {
            if(this.htmlElement.media) {
                if(this.htmlElement.media.id === this.internal.video.id) {
                    this.internal.video.jq.css({'width':'0px', 'height':'0px'});
                }
                this.htmlElement.media.pause();
                this.htmlElement.media.src = "";

                if(!($.browser.msie && Number($.browser.version) >= 9)) { // IE9 Bug: media.load() on broken src causes an exception. In try/catch IE9 generates the error event too, but it is delayed and corrupts jPlayer's event masking.
                    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.
                }
            }
        },
        _html_load: function() {
            if(this.status.waitForLoad) {
                this.status.waitForLoad = false;
                this.htmlElement.media.src = this.status.src;
                try {
                    this.htmlElement.media.load(); // IE9 Beta throws an exception here on broken links. Review again later as IE9 Beta matures
                } catch(err) {}
            }
            clearTimeout(this.internal.htmlDlyCmdId);
        },
        _html_play: function(time) {
            var self = this;
            this._html_load(); // Loads if required and clears any delayed commands.

            this.htmlElement.media.play(); // Before currentTime attempt otherwise Firefox 4 Beta never loads.

            if(!isNaN(time)) {
                try {
                    this.htmlElement.media.currentTime = time;
                } catch(err) {
                    this.internal.htmlDlyCmdId = setTimeout(function() {
                        self.play(time);
                    }, 100);
                    return; // Cancel execution and wait for the delayed command.
                }
            }
            this._html_checkWaitForPlay();
        },
        _html_pause: function(time) {
            var self = this;
            
            if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
                this._html_load(); // Loads if required and clears any delayed commands.
            } else {
                clearTimeout(this.internal.htmlDlyCmdId);
            }

            // Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime.
            this.htmlElement.media.pause();

            if(!isNaN(time)) {
                try {
                    this.htmlElement.media.currentTime = time;
                } catch(err) {
                    this.internal.htmlDlyCmdId = setTimeout(function() {
                        self.pause(time);
                    }, 100);
                    return; // Cancel execution and wait for the delayed command.
                }
            }
            if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
                this._html_checkWaitForPlay();
            }
        },
        _html_playHead: function(percent) {
            var self = this;
            this._html_load(); // Loads if required and clears any delayed commands.
            try {
                if((typeof this.htmlElement.media.seekable === "object") && (this.htmlElement.media.seekable.length > 0)) {
                    this.htmlElement.media.currentTime = percent * this.htmlElement.media.seekable.end(this.htmlElement.media.seekable.length-1) / 100;
                } else if(this.htmlElement.media.duration > 0 && !isNaN(this.htmlElement.media.duration)) {
                    this.htmlElement.media.currentTime = percent * this.htmlElement.media.duration / 100;
                } else {
                    throw "e";
                }
            } catch(err) {
                this.internal.htmlDlyCmdId = setTimeout(function() {
                    self.playHead(percent);
                }, 100);
                return; // Cancel execution and wait for the delayed command.
            }
            if(!this.status.waitForLoad) {
                this._html_checkWaitForPlay();
            }
        },
        _html_checkWaitForPlay: function() {
            if(this.status.waitForPlay) {
                this.status.waitForPlay = false;
                if(this.css.jq.videoPlay.length) {
                    this.css.jq.videoPlay.hide();
                }
                if(this.status.video) {
                    this.internal.poster.jq.hide();
                    this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
                }
            }
        },
        _html_volume: function(v) {
            if(this.html.audio.available) {
                this.htmlElement.audio.volume = v;
            }
            if(this.html.video.available) {
                this.htmlElement.video.volume = v;
            }
        },
        _html_mute: function(m) {
            if(this.html.audio.available) {
                this.htmlElement.audio.muted = m;
            }
            if(this.html.video.available) {
                this.htmlElement.video.muted = m;
            }
        },
        _flash_setAudio: function(media) {
            var self = this;
            try {
                // Always finds a format due to checks in setMedia()
                $.each(this.formats, function(priority, format) {
                    if(self.flash.support[format] && media[format]) {
                        switch (format) {
                            case "m4a" :
                                self._getMovie().fl_setAudio_m4a(media[format]);
                                break;
                            case "mp3" :
                                self._getMovie().fl_setAudio_mp3(media[format]);
                                break;
                        }
                        self.status.src = media[format];
                        self.status.format[format] = true;
                        self.status.formatType = format;
                        return false;
                    }
                });

                if(this.options.preload === 'auto') {
                    this._flash_load();
                    this.status.waitForLoad = false;
                }
            } catch(err) { this._flashError(err); }
        },
        _flash_setVideo: function(media) {
            var self = this;
            try {
                // Always finds a format due to checks in setMedia()
                $.each(this.formats, function(priority, format) {
                    if(self.flash.support[format] && media[format]) {
                        switch (format) {
                            case "m4v" :
                                self._getMovie().fl_setVideo_m4v(media[format]);
                                break;
                        }
                        self.status.src = media[format];
                        self.status.format[format] = true;
                        self.status.formatType = format;
                        return false;
                    }
                });

                if(this.options.preload === 'auto') {
                    this._flash_load();
                    this.status.waitForLoad = false;
                }
            } catch(err) { this._flashError(err); }
        },
        _flash_clearMedia: function() {
            this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
            try {
                this._getMovie().fl_clearMedia();
            } catch(err) { this._flashError(err); }
        },
        _flash_load: function() {
            try {
                this._getMovie().fl_load();
            } catch(err) { this._flashError(err); }
            this.status.waitForLoad = false;
        },
        _flash_play: function(time) {
            try {
                this._getMovie().fl_play(time);
            } catch(err) { this._flashError(err); }
            this.status.waitForLoad = false;
            this._flash_checkWaitForPlay();
        },
        _flash_pause: function(time) {
            try {
                this._getMovie().fl_pause(time);
            } catch(err) { this._flashError(err); }
            if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
                this.status.waitForLoad = false;
                this._flash_checkWaitForPlay();
            }
        },
        _flash_playHead: function(p) {
            try {
                this._getMovie().fl_play_head(p)
            } catch(err) { this._flashError(err); }
            if(!this.status.waitForLoad) {
                this._flash_checkWaitForPlay();
            }
        },
        _flash_checkWaitForPlay: function() {
            if(this.status.waitForPlay) {
                this.status.waitForPlay = false;
                if(this.css.jq.videoPlay.length) {
                    this.css.jq.videoPlay.hide();
                }
                if(this.status.video) {
                    this.internal.poster.jq.hide();
                    this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
                }
            }
        },
        _flash_volume: function(v) {
            try {
                this._getMovie().fl_volume(v);
            } catch(err) { this._flashError(err); }
        },
        _flash_mute: function(m) {
            try {
                this._getMovie().fl_mute(m);
            } catch(err) { this._flashError(err); }
        },
        _getMovie: function() {
            return document[this.internal.flash.id];
        },
        _checkForFlash: function (version) {
            // Function checkForFlash adapted from FlashReplace by Robert Nyman
            // http://code.google.com/p/flashreplace/
            var flashIsInstalled = false;
            var flash;
            if(window.ActiveXObject){
                try{
                    flash = new ActiveXObject(("ShockwaveFlash.ShockwaveFlash." + version));
                    flashIsInstalled = true;
                }
                catch(e){
                    // Throws an error if the version isn't available           
                }
            }
            else if(navigator.plugins && navigator.mimeTypes.length > 0){
                flash = navigator.plugins["Shockwave Flash"];
                if(flash){
                    var flashVersion = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1");
                    if(flashVersion >= version){
                        flashIsInstalled = true;
                    }
                }
            }
            if($.browser.msie && Number($.browser.version) >= 9) { // IE9 does not work with external interface. With dynamic Flash insertion like jPlayer uses.
                return false;
            } else {
                return flashIsInstalled;
            }
        },
        _validString: function(url) {
            return (url && typeof url === "string"); // Empty strings return false
        },
        _limitValue: function(value, min, max) {
            return (value < min) ? min : ((value > max) ? max : value);
        },
        _urlNotSetError: function(context) {
            this._error( {
                type: $.jPlayer.error.URL_NOT_SET,
                context: context,
                message: $.jPlayer.errorMsg.URL_NOT_SET,
                hint: $.jPlayer.errorHint.URL_NOT_SET
            });
        },
        _flashError: function(error) {
            this._error( {
                type: $.jPlayer.error.FLASH,
                context: this.internal.flash.swf,
                message: $.jPlayer.errorMsg.FLASH + error.message,
                hint: $.jPlayer.errorHint.FLASH
            });
        },
        _error: function(error) {
            this._trigger($.jPlayer.event.error, error);
            if(this.options.errorAlerts) {
                this._alert("Error!" + (error.message ? "\n\n" + error.message : "") + (error.hint ? "\n\n" + error.hint : "") + "\n\nContext: " + error.context);
            }
        },
        _warning: function(warning) {
            this._trigger($.jPlayer.event.warning, undefined, warning);
            if(this.options.errorAlerts) {
                this._alert("Warning!" + (warning.message ? "\n\n" + warning.message : "") + (warning.hint ? "\n\n" + warning.hint : "") + "\n\nContext: " + warning.context);
            }
        },
        _alert: function(message) {
            alert("jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message);
        }
    };

    $.jPlayer.error = {
        FLASH: "e_flash",
        NO_SOLUTION: "e_no_solution",
        NO_SUPPORT: "e_no_support",
        URL: "e_url",
        URL_NOT_SET: "e_url_not_set",
        VERSION: "e_version"
    };

    $.jPlayer.errorMsg = {
        FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError()
        NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init()
        NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia()
        URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners()
        URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead()
        VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady()
    };

    $.jPlayer.errorHint = {
        FLASH: "Check your swfPath option and that Jplayer.swf is there.",
        NO_SOLUTION: "Review the jPlayer options: support and supplied.",
        NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.",
        URL: "Check media URL is valid.",
        URL_NOT_SET: "Use setMedia() to set the media URL.",
        VERSION: "Update jPlayer files."
    };

    $.jPlayer.warning = {
        CSS_SELECTOR_COUNT: "e_css_selector_count",
        CSS_SELECTOR_METHOD: "e_css_selector_method",
        CSS_SELECTOR_STRING: "e_css_selector_string",
        OPTION_KEY: "e_option_key"
    };

    $.jPlayer.warningMsg = {
        CSS_SELECTOR_COUNT: "The number of methodCssSelectors found did not equal one: ",
        CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",
        CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",
        OPTION_KEY: "The option requested in jPlayer('option') is undefined."
    };

    $.jPlayer.warningHint = {
        CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.",
        CSS_SELECTOR_METHOD: "Check your method name.",
        CSS_SELECTOR_STRING: "Check your css selector is a string.",
        OPTION_KEY: "Check your option name."
    };
})(jQuery);
