/**
 * Library for using the YouTube chromeless player with a playlist.
 * by Matt Albrecht (groboclown@gmail.com).
 * http://groboclown.net/yt_playlist
 * 
 * Released into the Public Domain:
 * http://creativecommons.org/licenses/publicdomain/
 *
 * See the related howto-embed.html file for a description on how to use this.
 *
 * TODO: move to namespace.  This will be done for version 1.0 release.
 */
PlaylistVersion = "0.5.0";
PlaylistVersionAPI = "0.5";

Playlist_FINISHED = "FINISHED";
Playlist_NEXT = "NEXT";
Playlist_LOADING = "LOADING";
Playlist_START_PLAYBACK = "START PLAYBACK";
Playlist_WAITING_FOR_USER = "WAITING FOR USER";
Playlist_ERROR = "ERROR";


// YouTube player states
// Internal-use only
Playlist_YT_Initialized = -1;
Playlist_YT_Ended = 0;
Playlist_YT_Playing = 1;
Playlist_YT_Paused = 2;
Playlist_YT_Buffering = 3;
Playlist_YT_Cued = 5;
Playlist_TN_Ready = -2000; // Playlist.ready() called
Playlist_TN_ReadyPlay = -2001; // Playlist.readyPlay() called
Playlist_TN_LoadNext = -2002; // Playlist.loadNextVideo(false) called
Playlist_TN_LoadPlayNext = -2003; // Playlist.loadNextVideo(true) called
Playlist_TN_VideoAddedAfterCurrent = -2004; // Playlist.add or insert or addAll called, and insert position after current play position
Playlist_TN_Empty = -2005; // Playlist.add or insert or addAll called, and insert position after current play position
Playlist_TN_DoneSeek = -2006; // seeking has completed

Playlist_Seek_FUDGE = 10;
Playlist_DEBUG = true;

function debug(msg) {
    if (Playlist_DEBUG && window['console'] && window.console['log']) {
        console.log(msg);
    } else
    if (Playlist_DEBUG && document.getElementById('console')) {
        document.getElementById('console').innerHTML += "<br></br>" + msg;
    }
}


/**
 * Constructor for the playlist
 *
 * @param YouTubePlayerObject player the youtube video player embedded object
 * @attribute function callback(dictionary) - a callback function that will
 *    receive different signals based on playlist actions.  See the API docs for
 *    complete details on this.
 * @attribute int size (read-only) - the size of the playlist.
 * @attribute boolean autostart - true (default) means that next videos in the
 *    playlist will automatically start.  Does not apply to the ready() state.
 * @attribute Object player the YouTube video player object.
 * @attribute int maxSeekAttempts - the number of attempts at trying to seek
 *    to a specific postion
 * @attribute boolean allowResize - allow videos to resize the player.
 */
function PlaylistObject(player) {
    // this is the only place outside onStateChange we will directly reference
    // the state.
    this.__state = Playlist_SM_ZERO;
    this._lst = [];
    this.size = 0;
    this._pos = -1;
    this.player = player;
    this.playerstate = -2;
    this._timerfuncActive = -1;
    this.callback = null;
    this.autostart = true;
    this._seekAttempts = 0;
    this.maxSeekAttempts = 5;
    this.allowResize = false;
}

/**
 * Adds a video to the playlist.
 *
 * @param Object video an object with the attributes:
 *       String vId - YouTube video ID (required)
 *       Number start - number of seconds into the video to start the playback
 *             (optional - defaults to 0)
 *       Number end - number of seconds into the video to stop the playback
 *             (absolute relative to the start of the video, not the start
 *             parameter).  A value less than the start value means play until
 *             the end of the file (optional - defaults to -1).
 */
PlaylistObject.prototype.add = function (video) {
    this._add(video, true);
};

// Internal implementation of the add function, with an option to check if a
// callback to the client code should be made
PlaylistObject.prototype._add = function (video, doCallback) {
    if (video) {
        if (! video["start"] || video.start < 0) {
            video.start = 0;
        }
        if (! video["end"]) {
            video.end = -1;
        }
        this._lst[this.size++] = video;
        // always appended to the end
        if (doCallback) {
            this._plStateChange(Playlist_TN_VideoAddedAfterCurrent);
        }
    }
};

/**
 * Add all the video objects in the videoArray to the playlist
 *
 * @param Object[] videoArray the list of video objects to add.
 */
PlaylistObject.prototype.addAll = function (videoArray) {
    if (videoArray) {
        for (var v = 0; v < videoArray.length; v++) {
            this._add(videoArray[v], false);
        }
        // always appended to the end
        this._plStateChange(Playlist_TN_VideoAddedAfterCurrent);
    }
};

/**
 * Insert the given video object into the current playlist at the indx position.
 * The current playlist pointer will be updated so that the currently playing
 * video will remain correct.
 */
PlaylistObject.prototype.insert = function (indx, video) {
    if (indx >= this.size) {
        this._lst[this.size++] = video;
        if (this._pos >= 0) {
            // advance the position to keep it pointing to the current video
            this._pos++;
        }
        return;
    }
    
    if (indx < 0) {
        indx = 0;
    }
    var tmp = [];
    var i = 0;
    for (; i < indx; i++) {
        tmp[i] = this._lst[i];
    }
    tmp[i] = video;
    for (; i < this.size; i++) {
        tmp[i + 1] = this._lst[i];
    }
    this._lst = tmp;
    this.size++;
    if (this._pos >= indx) {
        // advance the position so it stays on the current video
        this._pos++;
    } else {
        // inserted after current position
        this._plStateChange(Playlist_TN_VideoAddedAfterCurrent);
    }
};

/**
 * Removes the video at the given index.  The currently playing video position
 * will be correctly adjusted.
 *
 * @param int indx index of the video to remove.  If it is out of range, then
 *      the call will do nothing.
 */
PlaylistObject.prototype.remove = function (indx) {
    if (indx > 0 && indx < this.size) {
        this.size--;
        for (var i = indx; i < this.size; i++) {
            this._lst[i] = this._lst[i + 1];
        }
        this._lst[this.size] = null;
        if (indx <= this._pos) {
            // the video is after the removed index, so push it down.
            // Push down on equals so that the "next" video will be correct
            this._pos--;
        }
    }
};

/**
 * Removes all videos from the playlist.  This does not affect the currently
 * playing video, but when the video ends, the playlist will be finished.
 */
PlaylistObject.prototype.clear = function () {
    this._pos = -1;
    this._lst = [];
};

/**
 * Tell the playlist that all videos have been added, and to load the first
 * video.
 *
 * @param playAfterLoad if true, then the video will start playing without
 *      user actions once the video is loaded
 */
PlaylistObject.prototype.ready = function (playAfterLoaded) {
    if (playAfterLoaded) {
        this._plStateChange(Playlist_TN_Ready);
    } else {
        this._plStateChange(Playlist_TN_ReadyPlay);
    }
};

/**
 * Retrieve the currently loaded video object, as passed into the playlist.
 *
 * @return the current video object, or null if none is active.
 */
PlaylistObject.prototype.getCurrentVideo = function () {
    if (this._pos >= 0 && this._pos < this.size) {
        return this._lst[this._pos];
    }
    return null;
};

/**
 * @return -1 if at the beginning of the playlist, size if at the end of the
 *  playlist, or the actual index.
 */
PlaylistObject.prototype.getCurrentVideoIndex = function () {
    return this._pos;
};

/**
 * @return true if there are more videos, or false if the player does not
 *    have any more videos in the queue
 */
PlaylistObject.prototype.hasMore = function () {
    return this._pos < this.size && this.size > 0;
};

/**
 * Sets the next video to play.  The next call to #loadNextVideo(boolean)
 * will load this video.
 *
 * @param indx index number of the video to setup.  Values less than 0 will
 *     be considered starting the playlist over again, and greater or equal to
 *     the size of the playlist will be considered finished.
 */
PlaylistObject.prototype.setNextVideo = function (indx) {
    if (indx < 0) {
        indx = 0;
    } else
    if (indx >= this.size) {
        indx = this.size;
    }
    this._pos = indx - 1;
};

/**
 * Advances the YouTube player to the next video in the play list
 *
 * @param boolean immediatePlayback true if the player should start playing immediately
 *     after loading; false if the player should queue the video and wait for
 *     the user or a script to trigger loading.
 * @return true if there was another video to load
 */
PlaylistObject.prototype.loadNextVideo = function (immediatePlayback) {
    if (immediatePlayback) {
        this._plStateChange(Playlist_TN_LoadPlayNext);
    } else {
        this._plStateChange(Playlist_TN_LoadNext);
    }
};

/**
 * Returns a copy of the current list of videos in the playlist.  The objects
 * returned in the list will be the live videos, but altering the list will
 * not affect the Playlist's videos.
 *
 * @return an array of video objects.
 */
PlaylistObject.prototype.getVideos = function () {
    var ret = [];
    for (var i = 0; i < this._lst.length; i++) {
        if (this._lst[i]) {
            ret[ret.length] = this._lst[i];
        }
    }
    return ret;
};

/**
 * Discovers the current state of the player.
 *
 * @return an object with a set of states:
 *      ready: true / false
 *      player: "playing", "paused", "buffering", or "not playing"
 *      playlist: "has more", "end of list", "not initialized"
 */
PlaylistObject.prototype.getState = function () {
    var ret = {
        ready: true,
        player: "not playing",
        playlist: "not initialized"
    };
    if (this.playerstate <= Playlist_YT_Initialized) {
        ret.ready = false;
    }
    if (this._pos >= 0) {
        if (this._pos < this._lst.length) {
            ret.playlist = "has more";
        } else {
            ret.playlist = "end of list";
        }
    }
    if (this.playerstate === Playlist_YT_Playing) {
        ret.player = "playing";
    } else
    if (this.playerstate === Playlist_YT_Paused) {
        ret.player = "paused";
    } else
    if (this.playerstate <= Playlist_YT_Ended) {
        ret.player = "not playing";
    } else {
        ret.player = "buffering";
    }
    return ret;
};

/**
 * Must be called when the YouTube player "onStateListener(state)" function is
 * invoked.
 */
PlaylistObject.prototype.onStateChange = function (newstate) {
    this.playerstate = newstate;
    this._plStateChange(newstate);
};

PlaylistObject.prototype._plStateChange = function (newstate) {
    debug("state change from " + this.__state + " w/ transition " + newstate);
    var stateObj = this._STATEMACHINE[this.__state];
    if (! stateObj) {
        this._callback(Playlist_ERROR, { errid: 2, msgid: 3,
            msg: "Internal error: Invalid state " + this.__state });
        this._setErrorState();
    } else { 
        var nextState = stateObj[String(newstate)];
        if (nextState === null) {
            this._callback(Playlist_ERROR, { errid: 1, msgid: 1,
                msg: "Invalid state transition: playlist in state " +
                    stateObj.name + ", with state transition " + newstate});
            this._setErrorState();
        } else
        if (nextState < 0) {
            // forced No-Op
        } else {
            this.__state = nextState;
            debug(" ::: new state " + nextState);
            // call transition method
            var transition = this._STATEMACHINE[nextState];
            if (! transition) {
                this._callback(Playlist_ERROR, { errid: 2, msgid: 3,
                    msg: "Internal error: Invalid state " + transition +
                        " from transition " + newstate + " and playlist state " +
                        stateObj.name});
                this._setErrorState();
            } else {
                transition.method.call(this);
            }
        }
    }
};

// Called when the playlist enters an error condition.  Because we cannot
// assume that the state machine matrix is in a usable condition when invoked,
// we must manually set the state.  Hence, we have an exception to our rule on
// only setting __state inside the _plStateChange and constructor methods.
PlaylistObject.prototype._setErrorState = function () {
    debug("Entering error state (" + 11 + ") from state " + this.__state);
    this.__state = 11;
};


/**
 * Must be called when the interval timer is invoked.
 */
PlaylistObject.prototype.onTimer = function () {
    if (this._timerfuncActive >= 0) {
        if (this.playerstate === Playlist_YT_Playing) {
            this._timerfuncActive = 1;
            
            // check end time
            var vid = this._lst[this._pos];
            var pos = this.player.getCurrentTime();
            if (pos >= vid.end) {
                this._timerfuncActive = -1;
                // artificial end event
                this._plStateChange(Playlist_YT_Ended);
            }
        }
    }
};


//=========================================================================
// STATE MACHINE


PlaylistObject.prototype._statetn_noop = function () {
    // do nothing
};

PlaylistObject.prototype._statetn_load = function () {
    // load the current video
    var video = this.getCurrentVideo();
    
    if (video === null) {
        this._callback(Playlist_ERROR, { errid: 3, msgid: 4,
            msg: "Null video at index " + this.getCurrentVideoIndex() });
        this._setErrorState();
        return;
    }
    
    // Initialize the seek attempts
    this._seekAttempts = 0;
    
    if (video.end > video.start) {
        this._timerStart();
    }
    
    this._callback(Playlist_LOADING, {
        index: this.getCurrentVideoIndex(),
        autostart: this.autostart,
        video: this.getCurrentVideo()
    });
    if (this.allowResize && video['width'] && video['height']) {
        debug("setting size to " + video.width + "x" + video.height);
        this.player.width = parseFloat(video.width);
        this.player.height = parseFloat(video.height);
    }
    if (video['quality'] && video.quality === 'hd') {
        this.player.loadVideoByUrl(
            "http://www.youtube.com/v/" + video.vId +
                "&hl=en&fs=1&rel=0&ap=%2526fmt%3D22",
            video.start);
    } else
    if (video['quality'] && video.quality === 'hq') {
        this.player.loadVideoByUrl(
            "http://www.youtube.com/v/" + video.vId +
                "&hl=en&fs=1&rel=0&ap=%2526fmt%3D18",
            video.start);
    } else {
        this.player.loadVideoById(video.vId, video.start);
    }
};

PlaylistObject.prototype._statetn_seek = function () {
    // Seek to the start position of the video.
    // If there is no start (or its zero), then go ahead
    // and transition with a simulated buffering call
    var video = this.getCurrentVideo();
    // check null?
    
    if (video.start > 0) {
        this.player.seekTo(video.start, true);
    } else {
        this._plStateChange(Playlist_YT_Cued);
    }
};


PlaylistObject.prototype._statetn_queued = function () {
    this._callback(Playlist_WAITING_FOR_USER, {
        index: this.getCurrentVideoIndex(),
        autostart: this.autostart,
        video: this.getCurrentVideo()
    });
    // Wait for user feedback to play, but, since we turn on auto-play
    // and seek, then we must force a pause here.
    if (this.playerstate === Playlist_YT_Playing) {
        this.player.pauseVideo();
    }
};

PlaylistObject.prototype._statetn_play = function () {
    this._callback(Playlist_START_PLAYBACK, {
        index: this.getCurrentVideoIndex(),
        autostart: this.autostart,
        video: this.getCurrentVideo()
    });
    this.player.playVideo();
};

PlaylistObject.prototype._statetn_advance_load = function () {
    this._pos++;
    if (this._pos >= this.size) {
        // end of list
        this._plStateChange(Playlist_TN_Empty);
    } else {
        this._callback(Playlist_NEXT, {
            index: this.getCurrentVideoIndex(),
            autostart: this.autostart,
            video: this.getCurrentVideo()
        });
        this._statetn_load();
    }
};

PlaylistObject.prototype._statetn_checkSeek = function () {
    var video = this.getCurrentVideo();
    // check null?
    
    var currentTime = this.player.getCurrentTime();
    // there's a bit of a fudge factor here.
    if (video.start <= (currentTime + Playlist_Seek_FUDGE)) {
        debug(" :::: No need to seek: start = " + video.start + "; now = " + currentTime);
        this.player.playVideo();
        this._plStateChange(Playlist_TN_DoneSeek);
    } else {
        if (this.playerstate === Playlist_YT_Playing) {
            // check if we've done this too many times.  If so,
            // then report an error that this video doesn't support seeking.
            this._seekAttempts++;
            if (this._seekAttempts > this.maxSeekAttempts) {
                this._callback(Playlist_ERROR, { errid: 4, msgid: 5,
                    msg: "Video does not support seek " + this.__state });
                this._setErrorState();
                return;
            } else {
                debug(" :::: Seeking: start = " + video.start + "; now = " + currentTime);
                this.player.seekTo(video.start);
            }
        }
    }
};

PlaylistObject.prototype._statetn_check = function () {
    if (this.autostart) {
        this._plStateChange(Playlist_TN_LoadPlayNext);
    } else {
        this._plStateChange(Playlist_TN_LoadNext);
    }
};

PlaylistObject.prototype._statetn_stop = function () {
    // what if we're already stopped?
    if (this.playerstate === Playlist_YT_Ended) {
        // simulate another end, just to advance our state
        this._plStateChange(Playlist_YT_Ended);
    } else {
        // generate a real end event
        this.player.stopVideo();
    }
};

PlaylistObject.prototype._statetn_empty = function () {
    this._callback(Playlist_FINISHED);
};


// Usage:
//    on state change called:
//      - find object in this array matching current _state value
//      - find state to transition to (match incoming newstate's corresponding
//          value).
//      -- if no such transition - call error
//      -- if transition, set __state to next state, and call its "method"
// Notes:
//   the only places the state index should be directly referenced are in the
//   constructor (initialize) and in the matrix itself.  All other places must
//   use the state transition.  The one exception is with the error state
//   method, which is an exception explicitly because an error state may be a
//   result of an issue with the matrix state machine.
//
Playlist_SM_IGNORE = -1;
Playlist_SM_ZERO = 0;
Playlist_SM_NotReady_Init = 1;
Playlist_SM_Ready_NotInit = 2;
Playlist_SM_ReadyPlay_NotInit = 3;
Playlist_SM_Load = 4;

PlaylistObject.prototype._STATEMACHINE = [
    { // 0 = Playlist_SM_ZERO
        name: "playlist not ready, player not initialized",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "-1" /*Playlist_YT_Initialized*/: 1,
        "-2000" /*Playlist_TN_Ready*/: 2,
        "-2001" /*Playlist_TN_ReadyPlay*/: 3
    },
    { // 1
        name: "playlist not ready, player initialized",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "-2000" /*Playlist_TN_Ready*/: 4,
        "-2001" /*Playlist_TN_ReadyPlay*/: 5
    },
    { // 2 = Playlist_SM_Ready_NotInit
        name: "playlist ready, player not initialized",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "-1" /*Playlist_YT_Initialized*/: 4
    },
    { // 3 = Playlist_SM_ReadyPlay_NotInit
        name: "playlist ready+play, player not initialized",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "-1" /*Playlist_YT_Initialized*/: 5
    },
    { // 4 = Playlist_SM_Load
        name: "playlist to load but not play a video",
        method: PlaylistObject.prototype._statetn_advance_load,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 6,
        "5" /*Playlist_YT_Cued*/: 6,
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    },
    { // 5
        name: "playlist to load and play a video",
        method: PlaylistObject.prototype._statetn_advance_load,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 18, //8,
        "5" /*Playlist_YT_Cued*/: 18, //8,
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2005" /*Playlist_TN_Empty*/: 16
    },
    { // 6
        name: "playlist loading but not to play a video, player needs to seek",
        method: PlaylistObject.prototype._statetn_seek,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 7,
        "5" /*Playlist_YT_Cued*/: 17,
        "1" /*Playlist_YT_Playing*/: 7,
        "2" /*Playlist_YT_Paused*/: 7,
        "0" /*Playlist_YT_Ended*/: 7,
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    },
    { // 7
        name: "playlist loading but not to play a video, player seeking",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 7,
        "5" /*Playlist_YT_Cued*/: 17,
        "1" /*Playlist_YT_Playing*/: 17,
        "2" /*Playlist_YT_Paused*/: 17,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    },
    { // 8
        name: "playlist loading and will play a video, player needs to seek",
        method: PlaylistObject.prototype._statetn_seek,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 9,
        "5" /*Playlist_YT_Cued*/: 18,
        "1" /*Playlist_YT_Playing*/: 18,
        "2" /*Playlist_YT_Paused*/: 18,
        "0" /*Playlist_YT_Ended*/: 13,
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2006" /*Playlist_TN_DoneSeek*/: 18
    },
    { // 9
        name: "playlist loading and will play a video, player seeking/buffering",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 9,
        "5" /*Playlist_YT_Cued*/: 18,
        "1" /*Playlist_YT_Playing*/: 18,
        "2" /*Playlist_YT_Paused*/: 10,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2006" /*Playlist_TN_DoneSeek*/: 18
    },
    { // 10
        name: "playlist waiting for user to play",
        method: PlaylistObject.prototype._statetn_queued,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 7,
        "5" /*Playlist_YT_Cued*/: 10,
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 10,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    },
    { // 11
        name: "playlist has video needing automated start",
        method: PlaylistObject.prototype._statetn_play,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 9,
        "5" /*Playlist_YT_Cued*/: 11,
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 12,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2006" /*Playlist_TN_DoneSeek*/: 11
    },
    { // 12
        name: "playlist has video playing",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 12,
        // "5" /*Playlist_YT_Cued*/: 11, // error
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 10,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    },
    { // 13
        name: "playlist advancing to next video",
        // note: this will determine if the next state is 14, 15, or 16, depending
        // upon status of autostart and queue, and immediately transition to
        // that one
        method: PlaylistObject.prototype._statetn_check,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE, // does not change current play state
        "3" /*Playlist_YT_Buffering*/: 12,
        "5" /*Playlist_YT_Cued*/: 11,
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 10,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2005" /*Playlist_TN_Empty*/: 16
    },
    { // 14
        name: "playlist advancing to next video, but will not auto-play",
        // note: this must stop the video
        method: PlaylistObject.prototype._statetn_stop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE, // does not change current play state
        "3" /*Playlist_YT_Buffering*/: 14,
        "5" /*Playlist_YT_Cued*/: 14,
        "1" /*Playlist_YT_Playing*/: 14,
        "2" /*Playlist_YT_Paused*/: 14,
        "0" /*Playlist_YT_Ended*/: 4,
        "-2002" /*Playlist_TN_LoadNext*/: 4,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 5,
        "-2005" /*Playlist_TN_Empty*/: 16
    },
    { // 15
        name: "playlist advancing to next video, and will auto-play",
        // note: this must stop the video
        method: PlaylistObject.prototype._statetn_stop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE, // does not change current play state
        "3" /*Playlist_YT_Buffering*/: 5,
        "5" /*Playlist_YT_Cued*/: 5,
        "1" /*Playlist_YT_Playing*/: 5,
        "2" /*Playlist_YT_Paused*/: 5,
        "0" /*Playlist_YT_Ended*/: 5,
        "-2002" /*Playlist_TN_LoadNext*/: 4,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 5,
        "-2005" /*Playlist_TN_Empty*/: 16
    },
    { // 16
        name: "playlist is empty",
        // note: this must stop the video
        method: PlaylistObject.prototype._statetn_empty,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: 13, // Different! Start playing again
        "3" /*Playlist_YT_Buffering*/: 12,
        "5" /*Playlist_YT_Cued*/: 12,
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 12,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2005" /*Playlist_TN_Empty*/: 16
    },
    { // 17
        name: "playlist checking for current position, and will wait for user to play",
        // note: this must stop the video
        method: PlaylistObject.prototype._statetn_checkSeek,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: -1,
        "3" /*Playlist_YT_Buffering*/: 17,
        "5" /*Playlist_YT_Cued*/: 17,
        "1" /*Playlist_YT_Playing*/: 17,
        "2" /*Playlist_YT_Paused*/: 17,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2006" /*Playlist_TN_DoneSeek*/: 10 // false event to say we're done playing
    },
    { // 18
        name: "playlist checking for current position, and will auto-play",
        method: PlaylistObject.prototype._statetn_checkSeek,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: -1,
        "3" /*Playlist_YT_Buffering*/: 18,
        "5" /*Playlist_YT_Cued*/: 18,
        "1" /*Playlist_YT_Playing*/: 18,
        "2" /*Playlist_YT_Paused*/: 18,
        "0" /*Playlist_YT_Ended*/: 18, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15,
        "-2006" /*Playlist_TN_DoneSeek*/: 11 // false event to say we're done playing
    },
    { // 19 - error state
        name: "playlist in an error condition",
        method: PlaylistObject.prototype._statetn_noop,
        "-2004" /*Playlist_TN_VideoAddedAfterCurrent*/: Playlist_SM_IGNORE,
        "3" /*Playlist_YT_Buffering*/: 19,
        "5" /*Playlist_YT_Cued*/: 19,
        "1" /*Playlist_YT_Playing*/: 12,
        "2" /*Playlist_YT_Paused*/: 12,
        "0" /*Playlist_YT_Ended*/: 13, // based on autostart value + queue
        "-2002" /*Playlist_TN_LoadNext*/: 14,
        "-2003" /*Playlist_TN_LoadPlayNext*/: 15
    }
];


PlaylistObject.prototype._timerStart = function () {
    if (this._timerfuncActive < 0) {
        this._timerfuncActive = 0;
    }
};
PlaylistObject.prototype._callback = function (name, obj) {
    if (this.callback) {
        if (! obj) {
            obj = {status: name, playlist: this};
        } else {
            obj.status = name;
            obj.playlist = this;
        }
        this.callback(obj);
    }
};
