/*****
 Common stuff; not sure where to put it


 Todo
 Test with add-ons disabled
 SongAnchorHandle - links to a certain position of a certain song
 _mp3URL
 _anchorOffsetMsec

 When clicked:
 Start loading the song if it hasn't already been loaded
 Stop playback
 Stop the previous anchor actions (if there were any)
 If we've loaded at least this much of the song, set position to this anchor offset, and resume playback
 Otherwise, wait until we've loaded at least this much, then set position to this anchor offset, and resume playback
 ?? Maybe allow lightups

 PlayerPlayHandle - large dimensions, one per page (one per player)

 Albums

 Album / SongCollection / Playlist
 _songs  (in track order)  -- should we let a SongCollection be a special type of song?

 Song
 _songHandle (?)
 _title
 _detailURL
 _mp3URL

 Functionality:
 Play album from start to end
 Play album random
 Play albums random (all tracks on page)
 Play single song
 Identify previous song(s) to be played in sequence
 Identify next song(s) queued to be played
 Identify previous song played in sequence
 Skip to next song in sequence
 Skip to previous song in sequence
 BUG: When changing from playing song A to song B, song A's progress is still shown for a while (if B takes a while to load). There should be a clearer mid-state.
 Song playback, queue persist across page changes
 Volume setting persists across browser sessions (cookie!)
 Replaygain adjustment

 Misc
 SongPlayHandle - smaller dimensions, multiple per page (can exist multiple times for same song)
 <span DF_bindToSongURL="http://noise.dillfrog.com/audio/fawm2009/jumpydrum.mp3"></span>
 var oHandle = oPlayer.createSongPlayHandle("http://absolute/path/to/song.mp3");
 oPlayer.addSongPlayHandle(oHandle);



 *****/

// Portions of this script adapted from http://www.schillmania.com/projects/soundmanager2/demo/mpc/script/mpc.js

function DF_formatTimeFromSeconds(iSeconds) {
    var iMinutes = Math.floor(iSeconds / 60);
    iSeconds = iSeconds % 60;
    if (iSeconds < 10) return iMinutes + ":0" + iSeconds;
    return iMinutes + ":" + iSeconds;
}

function DF_snapToBounds(valueToSnap, minBound, maxBound) {
    if (valueToSnap < minBound) {
        return minBound;
    } else if (valueToSnap > maxBound) {
        return maxBound;
    } else {
        return valueToSnap;
    }
}

var SongPlayHandle = Class.create({
    initialize: function(htmlContainer, songURL, player) {
        // Value: The ID or span of the container
        this._htmlContainer = $(htmlContainer);

        this._width = null;
        this._height = null;
        this._iconSpriteURL = null;
        // horizontally tiled
        this._playState = null;
        this._songURL = songURL;
        this._player = player;
        this.setState('stop');
        // Initialize state + html

        var oPlayHandle = this;

        Event.observe(this._htmlContainer, 'click', function(event) {
            oPlayHandle._player.playSong(oPlayHandle._songURL);
            // quit bubbling
            Event.stop(event);
            return false;
        });

    },
    setState: function(newStateValue) {
        var lastStateName = this._playState;
        // valid states: stopped, playing, paused
        this._playState = newStateValue;
        if (this._htmlContainer) {
            if (lastStateName)
                Element.removeClassName(this._htmlContainer, "FP_trackHandle_state" + lastStateName);
            if (this._playState == 'play') {
                //this._htmlContainer.innerHTML = '[Pause!]';
            } else if (this._playState == 'pause') {
                //this._htmlContainer.innerHTML = '[Play! (paused)]';
            } else if (this._playState == 'stop') {
                //this._htmlContainer.innerHTML = '[Play!]';
            } else {
                this._htmlContainer.innerHTML = 'Unknown state: ' + this._playState;
            }
            Element.addClassName(this._htmlContainer, "FP_trackHandle_state" + this._playState);
        }
    }
    

});




var SongMasterPlayPauseHandle = Class.create({
    initialize: function(htmlContainer, player) {
        // Value: The ID or span of the container
        this._htmlContainer = $(htmlContainer);

        this._playState = null;
        this._player = player;
        this.setState('stop');
        // Initialize state + html

        var oPlayHandle = this;

        Event.observe(this._htmlContainer, 'click', function(event) {
            oPlayHandle._player.togglePlayStatus();
            // quit bubbling
            Event.stop(event);
        });


    },
    setState: function(newStateValue) {
        // valid states: stopped, playing, paused
        if (newStateValue == null) newStateValue = 'stop';
        this._playState = newStateValue;

        if (this._htmlContainer) {
        // TODO Factor out the hard-coded image references here, in favor of CSS classes
            if (this._playState == 'play') {
                this._htmlContainer.style.backgroundImage = 'url(/includes/ajax/frogplayer/FROGmouth_play.gif)';
               // this._htmlContainer.innerHTML = '[Pause (playing)]';
            } else if (this._playState == 'pause') {
                //this._htmlContainer.innerHTML = '[Play (paused)]';
                this._htmlContainer.style.backgroundImage = 'url(/includes/ajax/frogplayer/FROGmouth_pause.gif)';
            } else if (this._playState == 'stop') {
                //this._htmlContainer.innerHTML = '[Play (stopped)]';
                this._htmlContainer.style.backgroundImage = 'url(/includes/ajax/frogplayer/FROGmouth_finished.gif)';
            } else {
                this._htmlContainer.innerHTML = 'Unknown state: ' + this._playState;
            }
        }
    }

});


var SongPositionBar = Class.create({
    initialize: function(constructorArgs) {
        this._loadingProgressDivID = 'frogPlayerFlyContainer';
        // Graphic width, in pixels
        this._loadingProgressGraphicWidth = 228;

        this._playingProgressDivID = 'frogPlayerTongueContainer';
        // Graphic width, in pixels. Note this is shorter than the loading graphic,
        // and that it may differ from the CSS width for this block.
        this._playingProgressGraphicWidth = 204;

        this._isSafari = (navigator.userAgent.toString().toLowerCase().indexOf('safari') + 1);

        // Progress of loading to represent, from 0..1 (start..end)
        this._loadingProgress = 0;

        // Progress of playback to represent, from 0..1 (start..end)
        this._playingProgress = 0;


        this._isDraggingPosition = false;
        if (constructorArgs != null) {
            this._onSlide = constructorArgs['onSlide'];
            //   this._onChange = callbackFunctions['onChange'];
        }

        var oPositionBar = this;

        $(oPositionBar._playingProgressDivID).observe('mousemove', function(event) {
            //$('divLoadingStatus').stopObserving('mousemove', DF_onClickPlayingPosition);
            //        Event.stop(event); // quit bubbling
            if (oPositionBar._isDraggingPosition)
                oPositionBar._onClickPlayingPosition(event);
        });

        Event.observe(document, 'mousedown', function(event) {
            //soundManager._writeDebug(event.element().id);
            if (event.element().id == oPositionBar._playingProgressDivID) {
                //  $('divLoadingStatus').observe('mousemove', DF_onClickPlayingPosition);
                //event.element().setStyle({cursor:"pointer"});
                oPositionBar._isDraggingPosition = true;
                oPositionBar._onClickPlayingPosition(event);
                Event.stop(event);
                // quit bubbling
            }
        });

        Event.observe(document, 'mouseup', function(event) {
            //$('divLoadingStatus').stopObserving('mousemove', DF_onClickPlayingPosition);
            //        Event.stop(event); // quit bubbling
            oPositionBar._isDraggingPosition = false;
        });

    },

    setLoadingProgress: function(fProgress) {
        fProgress = DF_snapToBounds(fProgress, 0, 1);
        this._loadingProgress = fProgress;

        var oProgressDiv = $(this._loadingProgressDivID);
        var iPixelOffset = Math.ceil((1 - fProgress) * -this._loadingProgressGraphicWidth);
        //soundManager._writeDebug('Load Prog is ' + fProgress + '; setting offset to ' + iPixelOffset);

        //document.write(iPixelOffset);
        oProgressDiv.style.backgroundPosition = iPixelOffset + "px 0px";
        oProgressDiv.style.display = '';

        if (this._isSafari) oProgressDiv.style.clip = 'rect(0px ' + parseInt((fProgress) * oProgressDiv.offsetWidth) + 'px auto 0px)'; // because safari appears to suck, refuses not to tile background images..
    },

    setPlayingProgress: function(fProgress) {
        fProgress = DF_snapToBounds(fProgress, 0, 1);
        this._playingProgress = fProgress;

        // soundManager._writeDebug('Play Prog is ' + fProgress);
        // soundManager._writeDebug('div id is ' + this._playingProgressDivID);


        var oProgressDiv = document.getElementById(this._playingProgressDivID);
        var iPixelOffset = Math.ceil((1 - fProgress) * -this._playingProgressGraphicWidth);
        oProgressDiv.style.backgroundPosition = iPixelOffset + "px 0px";


        if (this._isSafari) oProgressDiv.style.clip = 'rect(0px ' + parseInt((fProgress) * oProgressDiv.offsetWidth) + 'px auto 0px)'; // because safari appears to suck, refuses not to tile background images..
    },



    _onClickPlayingPosition: function(event) {
        if (!this._onSlide) return;

        var element = event.element();
        var aElementOffset = Element.viewportOffset(element);
        var relativeX = Math.max(0, Event.pointerX(event) - aElementOffset[0]);
        // The actual width (element.getWidth()) will be wider than the play-position width at times
        // (e.g. when the fly pushes the width out). So we trust our graphic width rather than element.getWidth()
        var fNewPosition = relativeX / this._playingProgressGraphicWidth;
        //  soundManager._writeDebug('Clickydoodles ' + Event.pointerX(event) + ", " + Event.pointerY(event));
        //  soundManager._writeDebug('Clicky X is ' + fNewPosition);

        Event.stop(event);
        // TODO check if we even need to worry about stopping bubbling
        // quit bubbling

        this._onSlide(fNewPosition);


        // val from 0..1
    }

});

//this._trackSkipContainerDivID = 'frogPlayerEyeContainer';



/** FROG PLAYER!
 This is the mastermind, who coordinates the UI and Audio-playback components
 to work with one another.
 **/
var FrogPlayer = Class.create({
    initialize: function() {

        this.preventIE7Flickering();
      
        // this.name = name
        this._songToPlay = null;
        this._autoPlayUponDurationLoaded = null;
        // If this is defined, and we've loaded this many msec of the song, we start playing automatically.

        var oFrogPlayer = this;

        this._songPlayHandles = $A(new Array());
        this._masterPlayHandle = new SongMasterPlayPauseHandle("frogPlayerBodyContainer", oFrogPlayer);
        this._playList = new PlayList();

        // Initialize position bars
        this._songPositionBar = new SongPositionBar(
        {
            onSlide: function(fNewPosition) {
                if (oFrogPlayer._songToPlay != null) {
                    var bWasStopped = (oFrogPlayer._songToPlay.playState == 0);
                    if (bWasStopped) {
                        // Hack to allow setting position if user clicks bar after song has finished playing
                        oFrogPlayer._songToPlay.play();
                        oFrogPlayer._songToPlay.pause();
                    }
                    oFrogPlayer._songToPlay.setPosition(Math.floor(fNewPosition * oFrogPlayer._songToPlay.durationEstimate));
                    //event.element().setStyle({cursor:"pointer"});

                    // fix AppleWebKit rendering
                    if (navigator.appVersion.indexOf('AppleWebKit') > 0) window.scrollBy(0, 0);
                }
            }
        }
                );


        //        this._legacySongPositionBar = new LegacySongPositionBar(
        //                "divLoadingStatus",
        //                "divPlayingProgress",
        //        {
        //            onSlide: function(fNewPosition) {
        //                if (oFrogPlayer._songToPlay != null) {
        //                    var bWasStopped = (oFrogPlayer._songToPlay.playState == 0);
        //                    if (bWasStopped) {
        //                        // Hack to allow setting position if user clicks bar after song has finished playing
        //                        oFrogPlayer._songToPlay.play();
        //                        oFrogPlayer._songToPlay.pause();
        //                    }
        //                    oFrogPlayer._songToPlay.setPosition(Math.floor(fNewPosition * oFrogPlayer._songToPlay.durationEstimate));
        //                    //event.element().setStyle({cursor:"pointer"});
        //
        //                    // fix AppleWebKit rendering
        //                    if (navigator.appVersion.indexOf('AppleWebKit') > 0) window.scrollBy(0, 0);
        //                }
        //            }
        //        }
        //                );

        // Initialize volume bar
        // vertical slider control
        this._volumeSlider = new Control.Slider('divVolumeHandle', 'divVolumeTrack', {
            axis: 'vertical',
            onSlide: function(v) {
                oFrogPlayer.setVolume((1 - v) * 100);
            },
            onChange: function(v) {
                oFrogPlayer.setVolume((1 - v) * 100);
            }


        });
        // Initialize play handles
        this.bindSongPlayHandles();

        this.setPlayStatus(null);
    },

    speak: function() {
        alert(this.name + " says: " + this.sound + "!");
    }
    ,

    loadSongToPlay: function(songURL) {
        var oPlayer = this;

        // Makes sure _songToPlay contains the song at songURL (if it doesn't already)
        if (this._songToPlay != null) {
            // Don't bother prepping it if it's already loaded
            if (this._songToPlay.url == songURL)
                return;


            // Otherwise we're stopping this song!
            this.setPlayStatus("stop");
            this._autoPlayUponDurationLoaded = null;
            // Reset queued anchor

            // mark the current song as stoppped
            this._songToPlay.destruct();
        }


        this._songToPlay = soundManager.createSound({
            id: '_songToPlay',
            url: songURL,
            multiShot:false,
            // onload: [ event handler function object ],
            // other options here
            onfinish:function() {
                //_songToPlay = null; // Before setting status
                oPlayer.setPlayStatus('stop');
                // this.destruct(); // Allow replays; don't destroy just 'cause it's done!
                
                var oNextPlayListItem = oPlayer._playList.seekToNextItem();
                if (oNextPlayListItem != null) {
                    oPlayer.playSong(oNextPlayListItem.getURL())
                }
            },
            onplay:function() {
                oPlayer.setPlayStatus('play');
            },
            onresume:function() {
                oPlayer.setPlayStatus('play');
            },
            onpause:function() {
                oPlayer.setPlayStatus('pause');
            },
            whileloading:function() {
                oPlayer._songPositionBar.setLoadingProgress(this.bytesLoaded / this.bytesTotal);
                oPlayer.considerAutoPlayingAtPosition();
            },
            onload:function() {
                oPlayer.considerAutoPlayingAtPosition();
            },
            whileplaying:function() {
                oPlayer._songPositionBar.setPlayingProgress(this.position / this.durationEstimate);
                oPlayer.updateTimeElapsed();

            }
        });
        
        // Tell the playlist we're on this song
        var oPlayListItem = this._playList.getItemByUrl(songURL);
        if (oPlayListItem != null) {
            this._playList.seekToItem(oPlayListItem, false);
        }
        
        // Load the waveform gif (assuming there is one)
        // FUTURE: Move this out into a UI layer
        var oWaveformContainer = document.getElementById('frogPlayerWaveformContainer');
        if (oWaveformContainer) {
            var gifURL = songURL.replace(/\.mp3$/, "\.fpwaveform.gif");
            oWaveformContainer.style.backgroundImage = 'url(' + gifURL + ')';
        }
    }
    ,

    setVolume: function(iVolume) {
        // iVolume is from 0 to 100
        iVolume = Math.floor(Math.min(Math.max(iVolume, 0), 100));
        // Place in bounds
        soundManager.defaultOptions.volume = iVolume;
        // For songs yet to play
        if (this._songToPlay != null)
            this._songToPlay.setVolume(iVolume); // For the song playing, if applicable
    }
    ,

    considerAutoPlayingAtPosition: function () {
        // Checks if we can start playback at the anchor position previously chosen.
        // If we can, it seeks to that position and resumes playback if necessary
        //
        if (this._autoPlayUponDurationLoaded == null ||
            this._songToPlay == null)
            return;
        soundManager._writeDebug('Considering autoplay with target ' + this._autoPlayUponDurationLoaded);
        soundManager._writeDebug('duration is ' + this._songToPlay.duration);


        if (this._songToPlay.duration >= this._autoPlayUponDurationLoaded) {
            if (this._songToPlay.paused)
                this._songToPlay.resume();
            this._songToPlay.setPosition(this._autoPlayUponDurationLoaded);
            this._autoPlayUponDurationLoaded = null;
            // Reset; stop trying to seek
            return true;
        }
        return false;

    },

    playSongAtPosition: function(songURL, seekToPosition) {
        this.loadSongToPlay(songURL);
        if (!this._songToPlay)
            return;
        this._songToPlay.play();

        this._autoPlayUponDurationLoaded = seekToPosition;
        if (!this.considerAutoPlayingAtPosition())
            this._songToPlay.pause();

    },

    playSong: function(songURL) {
        this.loadSongToPlay(songURL);
        if (this._songToPlay.position == this._songToPlay.durationEstimate)
            this._songToPlay.setPosition(0);

        if (this._songToPlay.paused)
            this._songToPlay.resume();
        else
            this._songToPlay.togglePause();
        // used to be .play();
    }
    ,

    setPlayStatus: function(statusCode) {
        // INTERNAL
        // Updates UI to reflect the passed playback status (play, pause, stop)

        //alert(statusCode);
        if (this._songToPlay) {
            for (var i = 0; i < this._songPlayHandles.length; ++i) {
                var oHandle = this._songPlayHandles[i];
                if (oHandle._songURL == this._songToPlay.url) {
                    oHandle.setState(statusCode);
                    // play, pause, stop?
                }
            }
        }

        this._masterPlayHandle.setState(statusCode);

        //TODO Deprecate (it's just commented out for now...)
        // We show the option that's opposite the status
        /*
        var playButton = document.getElementById('inpPlayPause');
        if (this._songToPlay != null) {
            playButton.value = statusCode == 'play' ? 'Pause' : 'Play';
        } else {
            playButton.value = '<Play Disabled>';
        }
        */
    }
    ,

    togglePlayStatus: function(statusCode) {
        // If no song is already loaded, use the first song on the playlist, if any
        if (this._songToPlay == null) {
            var oPlayListItem = this._playList.getSeekedItem();
            if (oPlayListItem)
                this.playSong(oPlayListItem.getURL());
        } else {
            this.playSong(this._songToPlay.url);
        }
    }
    ,


    updateTimeElapsed: function() {
        if (this._songToPlay == null) {
            $('divTimeElapsed').innerHTML = '-';
            $('divTimeRemaining').innerHTML = '-';
        } else {
            var iSeconds = Math.round(this._songToPlay.position / 1000);
            $('divTimeElapsed').innerHTML = DF_formatTimeFromSeconds(iSeconds);
            $('divTimeRemaining').innerHTML = DF_formatTimeFromSeconds(Math.round(this._songToPlay.durationEstimate / 1000) - iSeconds);
        }
    }
    ,

    bindSongPlayHandles: function () {
        // Iterates over objects in the page, and automagically sets up playback handles for them if applicable
        var oSpans = $$('span');
        for (var i = 0; i < oSpans.length; ++i) {
            var oSpan = oSpans[i];
            var strBindToSongURL = oSpan.readAttribute("DF_bindToSongURL");
            if (strBindToSongURL != null) {
                // Bind it!
                
                // Create the play handle
                var oNewHandle = new SongPlayHandle(oSpan, strBindToSongURL, this);
                this._songPlayHandles.push(oNewHandle);
                
                // Add it to our playlist
                // TODO Remove the assumption that the handle exists only once per song per page
                // (Are we creating duplicate playlistitems here, potentially?)
                // TODO Add the song title to this list
                var oPlayListItem = new PlayListItem("Unknown Title", strBindToSongURL);
                this._playList.appendItem(oPlayListItem);
                
            }
        }
    },
    
    /* Tries to have IE6, 7, etc use the BackgroundImageCache, to
    avoid flickering as our controls' background images change up. */
    preventIE7Flickering: function() {
        try {
            document.execCommand('BackgroundImageCache', false, true);
        } catch(e) {}
    }


})
        ;


var LegacySongPositionBar = Class.create({
    initialize: function(loadingStatusDivID, playingProgressDivID, callbackFunctions) {
        this._loadingProgressDivID = loadingStatusDivID;
        this._playingProgressDivID = playingProgressDivID;
        this._isSafari = (navigator.userAgent.toString().toLowerCase().indexOf('safari') + 1);

        // Progress of loading to represent, from 0..1 (start..end)
        this._loadingProgress = 0;

        // Progress of playback to represent, from 0..1 (start..end)
        this._playingProgress = 0;


        this._isDraggingPosition = false;
        if (callbackFunctions != null) {
            this._onSlide = callbackFunctions['onSlide'];
            //   this._onChange = callbackFunctions['onChange'];
        }

        var oPositionBar = this;

        $('divLoadingStatus').observe('mousemove', function(event) {
            //$('divLoadingStatus').stopObserving('mousemove', DF_onClickPlayingPosition);
            //        Event.stop(event); // quit bubbling
            if (oPositionBar._isDraggingPosition)
                oPositionBar._onClickPlayingPosition(event);
        });

        Event.observe(document, 'mousedown', function(event) {
            //soundManager._writeDebug(event.element().id);
            if (event.element().id == 'divPlayingProgress') {
                //  $('divLoadingStatus').observe('mousemove', DF_onClickPlayingPosition);
                //event.element().setStyle({cursor:"pointer"});
                oPositionBar._isDraggingPosition = true;
                oPositionBar._onClickPlayingPosition(event);
                Event.stop(event);
                // quit bubbling
            }
        });

        Event.observe(document, 'mouseup', function(event) {
            //$('divLoadingStatus').stopObserving('mousemove', DF_onClickPlayingPosition);
            //        Event.stop(event); // quit bubbling
            oPositionBar._isDraggingPosition = false;
        });

    },

    setLoadingProgress: function(fProgress) {
        fProgress = DF_snapToBounds(fProgress, 0, 1);
        this._loadingProgress = fProgress;

        //soundManager._writeDebug('Load Prog is ' + fProgress);
        var oProgressDiv = document.getElementById(this._loadingProgressDivID);
        var iPixelOffset = Math.ceil((1 - fProgress) * -200);
        //document.write(iPixelOffset);
        oProgressDiv.style.backgroundPosition = iPixelOffset + "px 0px";
        oProgressDiv.style.display = '';
        oProgressDiv.style.display = 'block';
        if (this._isSafari) oProgressDiv.style.clip = 'rect(0px ' + parseInt((fProgress) * oProgressDiv.offsetWidth) + 'px auto 0px)'; // because safari appears to suck, refuses not to tile background images..
    },

    setPlayingProgress: function(fProgress) {
        fProgress = DF_snapToBounds(fProgress, 0, 1);

        this._playingProgress = fProgress;

        //soundManager._writeDebug('Play Prog is ' + fProgress);
        //soundManager._writeDebug('div id is ' + this._playingProgressDivID);


        var oProgressDiv = document.getElementById(this._playingProgressDivID);
        var iPixelOffset = Math.ceil((1 - fProgress) * -200) - 200;
        oProgressDiv.style.backgroundPosition = iPixelOffset + "px 0px";


        if (this._isSafari) oProgressDiv.style.clip = 'rect(0px ' + parseInt((fProgress) * oProgressDiv.offsetWidth) + 'px auto 0px)'; // because safari appears to suck, refuses not to tile background images..
    },



    _onClickPlayingPosition: function(event) {
        if (!this._onSlide) return;

        var element = event.element();
        var aElementOffset = Element.viewportOffset(element);
        var relativeX = Math.max(0, Event.pointerX(event) - aElementOffset[0]);
        var fNewPosition = relativeX / element.getWidth();
        // soundManager._writeDebug('Clickydoodles ' + Event.pointerX(event) + ", " + Event.pointerY(event));
        // soundManager._writeDebug('Clicky X is ' + fNewPosition);

        Event.stop(event);
        // TODO check if we even need to worry about stopping bubbling
        // quit bubbling

        this._onSlide(fNewPosition);


        // val from 0..1
    }

});


var PlayListItem = Class.create({
    initialize: function(title, url) {
        this._url = url;
        this._title = title;
    },

    getTitle: function() {
        return this._title;
    },

    getURL: function () {
        return this._url;
    }

});


var PlayList = Class.create({
    initialize: function() {
        // Initialize state + html

        // List of TrackListItem objects
        this._list = new Array();
        this._historyList = new Array();

        this._playIndex = 0; // 0 for first item, 1 for second, etc. Never -1.
        // First element of list
    },

    /* Appends a PlayListItem to the end of the list */
    appendItem: function(oPlaylistItem) {
        this._list.push(oPlaylistItem);
    },
    
    /* Checks or sets whether this playlist is in "playlist-repeat" (vs track-repeat, vs none) mode.
    Returns True if in playlist-repeat mode
    */
    isRepeatingPlaylist: function(newValue) {
        if (newValue != null)
            this._isRepeatingPlaylist = newValue;
        return this._isRepeatingPlaylist;
    },

    /* Moves to the next item in the list to be played, and
     returns that new item. (Similar to "Next Track")
     */
    seekToNextItem: function() {
        // Empty list: return null
        if (this._list.length == 0)
            return null;

        this.addSeekedItemToHistory();

        // Stop playback if we reached the end of the playlist and
        // we're not on repeat
        if (!this.isRepeatingPlaylist() && this.isSeekedToLastItem())
            return null;

        // Move ahead 1 and roll over to the start of the list again.
        this._playIndex = (this._playIndex + 1) % this._list.length;

        return this.getSeekedItem();
    },

    /* Moves to the previous item in the list to be played, and
     returns that new item (Similar to "Previous Track") */
    seekToPreviousItem: function () {
        if (this._list.length == 0)
            return null;

        this.addSeekedItemToHistory();

        // Move back one
        this._playIndex = this._playIndex - 1;

        // Roll over to the end of the list if we're out of bounds
        if (this._playIndex < 0)
            this._playIndex = this._list.length - 1;

        return this.getSeekedItem();
    },

    /* Selects an item back in our play history, if possible.
     Returns the item selected, if one was.
     */
    seekBackItem: function () {
        if (this._historyList.length > 0) {
            var oItem = this._historyList.pop();
            this.seekToItem(oItem, false);
            return oItem;
        } else {
            return null;
        }

    },

    /* Seeks to where that item is in the list, if it's there at all.
    Returns the item it's seeked to, if it was. Else null.
    */
    seekToItem: function(oItem, bAddCurrentToHistory) {
        if (oItem == null)
            return null;
        // See where item is in the list, if at all
        var iItemIndex = this.getItemIndex(oItem);
        if (iItemIndex == -1)
            return null;

        if (bAddCurrentToHistory)
            this.addSeekedItemToHistory();

        this._playIndex = iItemIndex;
        return this.getSeekedItem();
    },

    /* Looks for an item in my list. If found, returns the index of that
     item within the list. Otherwise returns -1 */
    getItemIndex: function(oItemToFind) {
        if (oItemToFind == null)
            return -1;
        return this._list.indexOf(oItemToFind);
    },

    /* Returns True if the current item we're seeked to, is the last item
    in the list (assuming we play 'forward', not history) */    
    isSeekedToLastItem: function() {
        return this._list.length == 0 ||
            this._playIndex == this._list.length - 1;
    },

    /* Looks for an item in my list. If found, returns the index of that
     item within the list. Otherwise returns -1 */
    getItemByUrl: function(strUrlToFind) {
        if (strUrlToFind == null)
            return -1;
        for (var i=0; l=this._list.length; ++i) {
            var oItem = this._list[i];
            if (oItem.getURL() == strUrlToFind)
                return oItem;
        }
        return null;
    },


    /* Moves to a random item in the list and returns that item */
    seekToRandomItem: function () {
        if (this._list.length == 0)
            return null;

        this.addSeekedItemToHistory();

        // TODO Skip to random index

        return this.getSeekedItem();
    },

    /* Returns the currently-selected item in the list [to be played],
     or null if nothing is selected */
    getSeekedItem: function () {
        if (this._list.length == 0)
            return null;
        return this._list[this._playIndex];
    },


    /*
     For internal use only.
     Adds the currently-selected item (if any) to our history list
     */
    addSeekedItemToHistory: function() {
        var oItem = this.getSeekedItem();
        if (oItem != null) {
            this._historyList.push(oItem);
        }
    }

});

// TODO When the playlist isn't on repeat, and you finish the last song on the page, and hit "play" again on the top of the page, it replays the last song. Why not start the playlist over?