/* Copyright 2003-2010 Emergent Music LLC  All rights reserved.
    $Id$
*/

/* To override the default album art for the Javascript, add this code to the start of {% block js %}
    in your template, BEFORE {{ block.super }}:
        <script type="text/javascript">
            FLYFI.defaultArtPath = PATH-TO-FILE;
        </script>
    where PATH-TO-FILE is the file path on the server from the media_root directory to the file.  It
    should be something like 
            FLYFI.defaultArtPath = '/static/' + window.level + '/flyfi/images/MYCONTEST/defaultArt.jpg';
*/
if (typeof(FLYFI.defaultArtPath) == 'undefined') {
    FLYFI.defaultArtPath = '/static/' + window.level + '/flyfi/images/default_albumart.jpg';
}

FLYFI.onSoundFinished_PlayerControls = function(playerControls) {
    playerControls.playNext();
};

FLYFI.PlayerControls = function(controlsSelector, videoPlayerID, playlistDict, changeListOnVotes, onSoundFinishedCallback, newPlayer) { // Class
    // Users controls for a list of track widgets (or maybe just one).  Tracks register themselves with 
    //  their controls by calling .addTrack()
    
    // onSoundFinishedCallback is a callback for when the sound finishes.   Takes the playercontrols object as a parameter.
    // If not specified, uses FLYFI.onSoundFinished_PlayerControls
    
    // Callers can set class 'activetrack' to denote the currently active track 
    //  (use FLYFI.addTrackClassExclusive to ensure only one)
    
    // Class 'playingtrack' is set on the current track when it is playing
    
    var self = this;
    self.playerControlsWidget = $(controlsSelector);
    self.newPlayer = newPlayer || self.playerControlsWidget.parents('.controls_on_page').length > 0;

    self.changeListOnVotes = changeListOnVotes;
    self.isArtistPlayer = false; // set this to True for artist players - used for vetting
    self.onSoundFinishedCallback = onSoundFinishedCallback ? onSoundFinishedCallback : FLYFI.onSoundFinished_PlayerControls;
    self.widgetPlayCallback = null;     // widgets may have need explicit play calls (otherwise just click it)
    
    // This code looks for elements within the controlsSelector by class - use the following lines
    //  of code to see which elements are supported (note that any that are not support will just
    //  be ignored.
    
    // init
    self.titleWidget = self.playerControlsWidget.find('.titletext');
    self.byLabelWidget = self.playerControlsWidget.find('.by');
    self.artistLinkWidget = self.playerControlsWidget.find('.artistlink');
    self.moreByWidget = self.playerControlsWidget.find('.more_by a');
    self.aboutArtistWidget = self.playerControlsWidget.find('.aboutartist a');
    self.replaceTrackWidget = self.playerControlsWidget.find('.newartiststrack a');
    self.artistWidget = self.playerControlsWidget.find('.artist');
    self.albumWidget = self.playerControlsWidget.find('.album');    
    self.artworkWidget = self.playerControlsWidget.find('.albumart');
    self.artWidget = self.playerControlsWidget.find('.artwork a');
    self.artImageWidget = self.playerControlsWidget.find('.artwork img');
    self.descriptionWidget = self.playerControlsWidget.find('.description');
    
    self.playButton = self.playerControlsWidget.find('.play_btn');
    self.playButtonSpan = self.playerControlsWidget.find('.play_btn span'); // in the main player controls
    self.nextButton = self.playerControlsWidget.find('.next_btn');
    self.previousButton = self.playerControlsWidget.find('.back_btn');

    self.videoWidget = self.playerControlsWidget.find('.video');
    if (self.videoWidget.length > 0) {
        self.videoPlayerID = videoPlayerID;
        self.videoPlayer = FLYFI.embedVideoChromeless(videoPlayerID, "100%", "100%", self);
        self.videoWidget.hide();
    }
    self.videoSpinner = self.playerControlsWidget.find('.trackinfo .spinner');
    
    self.noMedia = self.playerControlsWidget.find('.nomedia');

    self.progressBars = self.playerControlsWidget.find('.progress_bars')[0];
    self.loadProgressBar = self.playerControlsWidget.find('.load_progress');
    self.playProgressBar = self.playerControlsWidget.find('.play_progress');
    self.scrubber = self.playerControlsWidget.find('.scrubber');
    self.scrubThumb = self.playerControlsWidget.find('.scrub_thumb');
    
    self.trackcell_menu = self.playerControlsWidget.find('.trackcellmenu');
    
    self.addButton = self.playerControlsWidget.find('.add_btn');
    self.deleteButton = self.playerControlsWidget.find('.delete_btn');
    self.buyButton = self.playerControlsWidget.find('.buy_btn');
    if (self.buyButton && FLYFI.isContest()) {
        self.buyButton.hide(); // no buy buttons for contests - they have website links, but the tracks won't have vendors
    }
    self.websiteButton = self.playerControlsWidget.find('.website_btn');
    self.websiteButtonA = self.websiteButton.find('a');
    self.websiteButton.click(self.onClick_websiteButton);
    
    self.findButton = self.playerControlsWidget.find('.find_btn');
    self.searchButton = self.playerControlsWidget.parents().find('.search_button');
    self.searchInput = self.playerControlsWidget.parents().find('.searchdata');

    self.downloadButton = self.playerControlsWidget.find('.download_btn');
    self.downloadButtonA = self.downloadButton.find('a');

    self.shareMenu = self.playerControlsWidget.find('.sharemenu>a');
    self.emailButton = self.playerControlsWidget.find('.email_btn');
    self.sharePlaylistInFacebookLink = self.playerControlsWidget.parents().find('.sharetofacebook_btn>a');

    self.voteUpButton = self.playerControlsWidget.find('.vote_up_btn');
    self.voteDownButton = self.playerControlsWidget.find('.vote_down_btn');
    self.useThisVideoButton = self.playerControlsWidget.find('.usethis_video_btn');

    self.allVideoButtons = self.playerControlsWidget.find('.video_btn');    
    self.badVideoButton = self.playerControlsWidget.find('.bad_video_btn');
    self.nextVideoButton = self.playerControlsWidget.find('.next_video_btn');
    self.prevVideoButton = self.playerControlsWidget.find('.prev_video_btn');
    
    self.playlistDict = null;       // the playlistDict for the current track - will be null for Artist page, etc.
    self.trackDict = null;          // the currently displaying track
    self.trackDictsWithWidgets = []; // list of trackDicts with widget entries

    self.clearTrackDictsAndWidgets = function() {
        self.trackDictsWithWidgets = []; // list of trackDicts with widget entries
    };
    
    self.addTrackDictAndWidget = function(trackDict, widget) {
        trackDict.widget = widget;
        widget.trackDict = trackDict;
        self.trackDictsWithWidgets.push(trackDict);
    };
    
    self.trackDictForID = function(trackID) {
        // return the trackDict that matches the supplied ID
        var l = self.trackDictsWithWidgets.length;
        for (var i = 0; i < l; ++i) {
            var trackDict = self.trackDictsWithWidgets[i];
            if (trackDict.trackID == trackID) {
                return trackDict;
            }
        }
        return null;
    };
    self.contestEntryDictForID = function(contestEntryID) {
        // return the dict that matches the supplied ID
        var l = self.trackDictsWithWidgets.length;
        for (var i = 0; i < l; ++i) {
            var trackDict = self.trackDictsWithWidgets[i];
            if (trackDict.contestEntryID == contestEntryID) {
                return trackDict;
            }
        }
        return null;
    };

    self.itemDictForWidget = function(widget) {
        // return the itemDict that matches the ID for the supplied widget, setting the widget into the trackDict
        var targetID = FLYFI.trackIDFromWidget(widget);
        var isTrack;
        if (targetID) {
            isTrack = true;
        } else {
            targetID = FLYFI.contestEntryIDFromWidget(widget);
            isTrack = false;
        }
        var l = self.trackDictsWithWidgets.length;
        for (var i = 0; i < l; ++i) {
            if ((isTrack && self.trackDictsWithWidgets[i].trackID == targetID) ||
                    (!isTrack && self.trackDictsWithWidgets[i].contestEntryID == targetID)) {
                self.trackDictsWithWidgets[i].widget = widget;
                widget.trackDict = self.trackDictsWithWidgets[i];
                return self.trackDictsWithWidgets[i];
            }
        }
        return null;
    };
    
    self.nextTrackWidgetInList = function() {
        var currentTrackID = self.trackDict.trackID;
        var l = self.trackDictsWithWidgets.length;
        if (l === 0) { return null; }
        if (l === 1) { return self.trackDictsWithWidgets[0].widget; }
        
        for (var i = 0; i < l - 1; ++i) {
            var trackDict = self.trackDictsWithWidgets[i];
            if (trackDict.trackID == currentTrackID) {
                return self.trackDictsWithWidgets[i+1].widget;
            }
        }
        var trackDict2 = self.trackDictsWithWidgets[l+1];
        if (trackDict2.trackID == currentTrackID) {
            return self.trackDictsWithWidgets[0].widget;
        }
        return null;
    };
    
    self.prevTrackWidgetInList = function() {
        var currentTrackID = self.trackDict.trackID;
        var l = self.trackDictsWithWidgets.length;
        if (l === 0) { return null; }
        if (l === 1) { return self.trackDictsWithWidgets[0].widget; }
        
        for (var i = 1; i < l; ++i) {
            var trackDict = self.trackDictsWithWidgets[i];
            if (trackDict.trackID == currentTrackID) {
                return self.trackDictsWithWidgets[i-1].widget;
            }
        }
        var trackDict2 = self.trackDictsWithWidgets[0];
        if (trackDict2.trackID == currentTrackID) {
            return self.trackDictsWithWidgets[0].widget;
        }
        return null;
    };
    
    self.newVideoPlayer = function() {
        self.videoPlayer = FLYFI.embedVideoChromeless(self.videoPlayerID, "100%", "100%", self);
    };
    
    self.markUnavailable = function(trackID) {
        if (FLYFI.isContest()) {
            return;
        }
        var l = self.trackDictsWithWidgets.length;
        for (var i = 0; i < l; ++i) {
            var trackDict = self.trackDictsWithWidgets[i];
            if (trackDict.trackID == trackID) {
                var widget = trackDict.widget;
                if (!widget.hasClass('trackcell')) {
                    widget = widget.find('.trackcell');
                }
                widget.addClass("unavailable");
            }
        }
    };
    
    self.firstTrackWidget = function() {
        // return the first widget 
        if (self.trackDictsWithWidgets.length === 0) {
            return null;
        }
        return self.trackDictsWithWidgets[0].widget;
    };
    
    self.lastTrackWidget = function() {
        // return the last widget 
        if (self.trackDictsWithWidgets.length === 0) {
            return null;
        }
        return self.trackDictsWithWidgets[self.trackDictsWithWidgets.length - 1].widget;
    };
    
    self.currentWidget = function() {
        // return the widget that is currently selected
        if (self.trackDictsWithWidgets.length === 0 || self.trackDict === null) {
            return null;
        } else {
            var itemDict;
            if (self.trackDict.trackID) {
                itemDict = self.trackDictForID(self.trackDict.trackID);
            } else {
                itemDict = self.contestEntryDictForID(self.trackDict.contestEntryID);
            }
            return itemDict? itemDict.widget : null;
        }
    };
    
    self.targetTrackWidget = function(noDefault, activeOnly) {
        // return the widget that should be the target of commands from these controls
        if (self.trackDictsWithWidgets.length === 0) {
            return null;
        } else {
            // return playingtrack or the activetrack if none playing
            var activeWidget = null;
            var l = self.trackDictsWithWidgets.length;
            var i;
            for (i = 0; i < l; ++i) {
                var widget = self.trackDictsWithWidgets[i].widget;
                if (widget.length > 0 && widget[0].offsetParent) {
                    if (!activeOnly && widget.hasClass('playingtrack')) {
                        return widget;
                    }
                    if (widget.hasClass('activetrack')) {
                        activeWidget = widget;
                        break;
                    }
                }
            }
            if (!activeWidget && !noDefault) { // no activetrack - return the first visible one
                for (i = 0; i < l; i++) {
                    var trackDict = self.trackDictsWithWidgets[0];
                    if (trackDict.widget[0].offsetParent) {
                        activeWidget = trackDict.widget;
                        activeWidget.addClass('activetrack');
                        self.selectTrack(trackDict, false);
                        break;
                    }
                }
            }
            return activeWidget;
        }
    };
    
    self.selectFirstTrack = function() {
        // Select the first track in a library.
        var tracks = self.trackDictsWithWidgets.length;
        for(var i = 0; i < tracks; i++) {
            var currentTrack = self.trackDictsWithWidgets[i];
            if (!currentTrack.widget || !currentTrack.widget.hasClass('voteddown')) {
                self.selectTrack(currentTrack);
                return;
            }
        }
        self.selectTrack(null);
    };
    
    self.setTrackTextDisplay = function (trackDict) {
        if (trackDict) {
            FLYFI.addTrackClassExclusive(trackDict.widget, 'activetrack');

            var contestEntryClass = trackDict.contestEntryType ? 'contestEntry-' + trackDict.contestEntryType : '';
            var contestArtistCategory = trackDict.contestEntryType == 'artist';
            var contestAlbumCategory = trackDict.contestEntryType == 'album';
            var contestTrackCategory = trackDict.contestEntryType == 'track';

            self.playerControlsWidget.removeClass('contestEntry contestEntry-artist contestEntry-album contestEntry-track');
            if (contestEntryClass) {
                self.playerControlsWidget.addClass('contestEntry ' + contestEntryClass);
            }

            if (contestArtistCategory) {
                if (self.artistWidget) { 
                    self.artistWidget.text(trackDict.nominee); 
                }
                if (self.titleWidget) { 
                    self.titleWidget.text(trackDict.title); 
                }
            } else if (contestAlbumCategory) {
                if (self.albumWidget) { 
                    self.albumWidget.text(trackDict.nominee); 
                }
                if (self.artistWidget) { 
					self.artistWidget.text(' by ' + trackDict.artist);                     
                }
                if (self.titleWidget) { 
                    self.titleWidget.text(trackDict.title); 
                }
            } else if (contestTrackCategory) {
                if (self.artistWidget) { 
                    self.artistWidget.text(trackDict.artist); 
                }
                if (self.titleWidget) { 
                    self.titleWidget.text(trackDict.nominee); 
                }
            }else { // 'normal' track
                if (self.titleWidget) { 
                    self.titleWidget.text(trackDict.title); 
                }
                if (self.byLabelWidget) { 
                    self.byLabelWidget.show(); 
                }
                if (self.artistWidget) { 
                    self.artistWidget.text(trackDict.artist); 
                }
            }
            
            if (self.descriptionWidget) {
                self.descriptionWidget.text(trackDict.description); 
				if (trackDict.description) {	
					$("#player_shell_shell").addClass('hasDescription');
					self.playerControlsWidget.addClass('hasDescription');
				} else {
					$("#player_shell_shell").removeClass('hasDescription');
					self.playerControlsWidget.removeClass('hasDescription');
				}
            }

            var artistLink = "/" + window.level + "/music/artist/" + trackDict.artist + "/";
            if (self.artistLinkWidget) { 
                self.artistLinkWidget.attr('href', artistLink); 
            }
            if (self.moreByWidget) { 
                self.moreByWidget.attr('href', artistLink); 
            }
            if (self.aboutArtistWidget) { 
                self.aboutArtistWidget.attr('href', artistLink); 
            }
            
            $ (".instagrooveLinkOne").attr("href", FLYFI.serverURL()+ "widget/artists/" + self.trackDict.artist +"/search/");            
            $ (".instagrooveLink").text(self.trackDict.artist);
            $ (".instagrooveLink").attr("href", FLYFI.serverURL()+ "widget/artists/" + self.trackDict.artist +"/search/");            
            $ (".instagrooveForm").val(self.trackDict.artist);
        }
    };
    
    self.hasMedia = function (trackDict) {
        // free tracks and youTubes obviously have media.  Other tracks have no media in contests but are considered YouTubes elsewhere
        return trackDict && (trackDict.free || (trackDict.videoYouTubeIDs && trackDict.videoYouTubeIDs.length > 0) || !FLYFI.isContest());
    };
    
    self._setArt = function(trackDict) {
        // for non-video tracks
        if (self.hasMedia(trackDict)) {
            self.noMedia.hide(); 
        } else {
            self.noMedia.show(); 
        }
                                
        if (self.artWidget) { 
            if (trackDict) {
                self.artWidget.attr('href', "/" + window.level + "/music/artist/" + trackDict.artist + "/#" + trackDict.album); 
            } else {
                self.artWidget.attr('href', ""); 
            }
        }
        if (self.artImageWidget) {
            var artPath = FLYFI.defaultArtPath;
            if (trackDict && trackDict.artist_art) { 
                artPath = trackDict.artist_art; 
            }
            self.artImageWidget.attr('src', artPath);
            if (trackDict) { 
                self.artImageWidget.attr('alt', trackDict.title + ' by ' + trackDict.artist);   
            }
        }
        if (self.artworkWidget) { self.artworkWidget.show(); }
        
        if (self.videoWidget) { 
            self.videoWidget.hide(); 
            self.allVideoButtons.hide();
        }
        self._setVideoActive(false);
    };
    
    self.setMediaDisplay = function (trackDict) {
        if (trackDict && trackDict.free) { // free
            self._setArt(trackDict);
        } else if (trackDict && (trackDict.videoYouTubeIDs && trackDict.videoYouTubeIDs.length > 0) || !FLYFI.isContest()) { // youTube
            self.noMedia.hide(); 
            if (self.artworkWidget) { 
                self.artworkWidget.hide(); 
            }
            if (self.videoWidget) { 
                self.videoWidget.show();
                self.allVideoButtons.show(); 
            }
            self._setVideoActive(true);
        } else { // no media
            self._setArt(trackDict);
        }

        if (self.hasMedia(trackDict)) {
            self.playButton.removeClass('unavailable');
            self.previousButton.removeClass('unavailable');
            self.nextButton.removeClass('unavailable');
            self.scrubber.removeClass('unavailable');
        } else {
            self.playButton.addClass('unavailable');
            self.previousButton.addClass('unavailable');
            self.nextButton.addClass('unavailable');
            self.scrubber.addClass('unavailable');
        }
    };
    
    self.setDownloadDisplay = function(trackDict) {
        if (self.downloadButton) {
            if (trackDict && trackDict.download_url) {
                var downloadURL = trackDict.download_url;
                if (FLYFI.isLikeIPhone()) {
                    downloadURL = '';
                }
                self.downloadButtonA.attr('href', downloadURL);
                self.downloadButton.removeClass('unavailable');
            } else {
                self.downloadButton.addClass('unavailable');
            }
        }
    };
    
    self.setBuyDisplay = function(trackDict) {
        if (self.buyButton) {
            if (trackDict && (trackDict.free || (FLYFI.isContest() && !trackDict.videoYouTubeIDs))) {
                self.buyButton.addClass('unavailable'); 
            } else {
                self.buyButton.removeClass('unavailable'); 
            }
        }
    };
    
    self.setWebsiteDisplay = function(trackDict) {
        if (trackDict && trackDict.contestEntryURL) {
            self.websiteButtonA.attr('href', trackDict.contestEntryURL)
                               .attr('target', '_flyfilink')
                               .addClass('logclick');
            self.websiteButton.removeClass('unavailable');
        } else {
            self.websiteButtonA.removeAttr('href')
                               .removeAttr('target')
                               .removeClass('logclick');
            self.websiteButton.addClass('unavailable');
        }
    };

    self._setVideoActive = function (active) {
        if (self == FLYFI.playerControls || self.playerControlsWidget.parents('.community_cloud').length > 0) {
            if (active) {
                $("#player_shell_shell").addClass("videoactive");     
            } else {
                $("#player_shell_shell").removeClass("videoactive");
            }
        } else {
            if (active) {
                self.playerControlsWidget.addClass("videoactive");                
            } else {
                self.playerControlsWidget.removeClass("videoactive");                
            }
        }
    };
    
    self.onClick_websiteButton = function(event) {
        if (self.websiteButton.hasClass('unavailable')) {
            event.preventDefault();
        }
    };
    
    self.selectTrack = function(trackDict, doPlay) {
        // make the controls work for the supplied trackDict
        if (!FLYFI.isContestCreation() &&
                self.trackDict && trackDict && FLYFI.sameItem(self.trackDict, trackDict) &&
                (!doPlay || (FLYFI.playingTrack.playing && FLYFI.playingTrack.loadedTrackID == trackDict.trackID)) ) { 
            return; // already set
        } 
        
        self.clearControlWidgets();
        self.trackDict = trackDict;
        
        self.setTrackTextDisplay(self.trackDict);
        self.setMediaDisplay(self.trackDict);
        self.setDownloadDisplay(self.trackDict);
        self.setBuyDisplay(self.trackDict);
        self.setWebsiteDisplay(self.trackDict);
        
        if (FLYFI.isLikeIPhone()) {
            FLYFI.showIPhonePlayer();
        }

        if (FLYFI.isContestCreation()) {
            if (FLYFI.contestEntryErrors && self.trackDict && self.trackDict.contestEntryID == FLYFI.initialContestEntryID) {
                FLYFI.contestCreation_showEntryForm(self.trackDict);
            } else {
                FLYFI.contestCreation_selectTrack(self.trackDict);
            }
            if (self.trackDict) {
                FLYFI.addTrackClassExclusive(self.trackDict.widget, 'activetrack');
            }
        } else if (self.hasMedia(self.trackDict)) {
            if (doPlay) {
                self.play();
            } else {
                if (!self.trackDict.free) {     // free tracks don't need cueing
                    self.videoPlayer.cue(self);
                }
            }
        }
    };
    
    self.clear = function() {
        self.clearControlWidgets();
        self.clearTrackDictsAndWidgets();
    };
    self.clearControlWidgets = function() {
        // reset the controls so they are not associated with any trackDict
        if (FLYFI.playingTrack.playerControls == self) {
            FLYFI.playingTrack.pause();
            FLYFI.playingTrack.clearTrack();
        }
        self.setNotPlaying();
        if (self.titleWidget) { self.titleWidget.text(''); }
        if (self.byLabelWidget) { self.byLabelWidget.hide(); }
        if (self.artistWidget) { self.artistWidget.text(''); }
        if (self.artImageWidget) {
            self.artImageWidget.attr('src', FLYFI.defaultArtPath);
            self.artImageWidget.attr('alt', '');
        }
        if (self.descriptionWidget) { self.descriptionWidget.text(''); }
        if (self.downloadButton) { self.downloadButtonA.attr('href', ''); }
        if (self.videoSpinner) { self.videoSpinner.hide(); }
        if (self.videoPlayer) { self.videoPlayer.clear(); }
        if (self.videoWidget) {
            self.videoWidget.hide();
			self.allVideoButtons.hide();        
		}			
        if (self.artworkWidget) { self.artworkWidget.show(); }
//        if (FLYFI.isContest()) {
//          self.newVideoPlayer();
//        }
        self.newVideoPlayer(); // Something has broken player - for now we have to live with no continuous play on a background page

        if (self.loadProgressBar) { 
            self.loadProgressBar.css('width', 0);
        }
        if (self.playProgressBar) { 
            self.playProgressBar.css('width', 0);
        }
        if (self.scrubThumb) {
            var thumbWidth = Math.floor(self.scrubThumb.width() / 2);    // ensure there is room to show the thumb
            self.scrubThumb.css('left', '-' + thumbWidth + 'px');
        }
    };   
    
    self.setPlaying = function() {
        // different play buttons operate different ways - play/pause for main player, active for TrackCells
        if (self.playButton) { self.playButton.addClass('active'); }
        if (self.playButtonSpan) { self.playButtonSpan.removeClass('play').addClass('pause'); }
        var widget = self.trackDict.widget; // self.targetTrackWidget();
        FLYFI.addTrackClassExclusive(widget, 'playingtrack');
    };
    self.setNotPlaying = function() {
        if (self.playButton) { self.playButton.removeClass('active'); }
        if (self.playButtonSpan) { self.playButtonSpan.removeClass('pause').addClass('play'); }
        var widget = self.targetTrackWidget(true);
        if (widget) {
            widget.removeClass('playingtrack');
        }
    };
    self.isPlaying = function() {
        return ((self.playButton && self.playButton.hasClass('active')) || (self.playButtonSpan && self.playButtonSpan.hasClass('pause')));
    };

    self.onPlay = function(event) {
        if (event) { // may be called from the code
            event.preventDefault();
        }

        if (self.isPlaying()) {
            self.pause();
        } else {
            self.play();
        }
    };
    
    self.play = function(event) {
        if (!self.trackDict) {
            return;
        }
        var widget = self.trackDict.widget; //self.targetTrackWidget();
        if ((FLYFI.playingTrack.loadedTrackID()==FLYFI.trackIDFromWidget(widget)) && FLYFI.playingTrack.playing) {
            return;
        }
        
        var scrubber = self.scrubber;
        scrubber.show();
        $('.scrubber').not(scrubber).not('.nohide').hide();

        if (FLYFI.isContest() && !(self.trackDict && (self.trackDict.free || self.trackDict.videoYouTubeIDs && self.trackDict.videoYouTubeIDs.length > 0))) {
            return; // don't play empty entries
        } else {
            FLYFI.playingTrack.play(self);
            self.setPlaying();
        }
    };
    
    self.pause = function(event) {
        if (FLYFI.playingTrack.trackDict && self.trackDict && self.trackDict.trackID == FLYFI.playingTrack.trackDict.trackID) {
            FLYFI.playingTrack.pause();
        }
        self.setNotPlaying();
    };
    
    self.onPlayByClickOnYouTube = function() {
        // user clicked on a YouTube track to start it playing - adjust controls to match
        if (self.isPlaying()) { return; } // already set
        
        var scrubber = self.scrubber;
        $('.scrubber').not(scrubber).not('.nohide').hide();
        scrubber.show();

        FLYFI.playingTrack.onPlayByClickOnYouTube(self);
        self.setPlaying();
    };

    self.onVoteUp = function(event) {
        if (event) { // may be called from the code
            event.preventDefault();
            if ($(event.target).parents('.youtube_controls').length > 0) { // Event came from "use this video" button.
                $("#player_shell_shell").removeClass("show_youtube_controls");
            }
        }
        if (self.changeListOnVotes && self.playlistDict && self.playlistDict.writable && !self.trackDict.widget.hasClass('approved')) {
            var prevWidget = self.previousTrackWidget(true);
            if (prevWidget && !prevWidget.hasClass('approved')) {
                var nextWidget = self.nextTrackWidget();
                if (nextWidget) {
                    FLYFI.addTrackClassExclusive(nextWidget, 'activetrack');
                }
            }
        }
        self._onVoteUp(true);
    };
    self._onVoteUp = function(showDialog, noSelect, trackDict) {
        // implementation of onVoteUp, but allows caller to decide if we should show the dialog.
        if (!trackDict) {
            trackDict = self.trackDict;
        }
        
        if (typeof(FLYFI.can_vote) != 'undefined' && !FLYFI.can_vote) {
            alert(FLYFI.cant_vote_warning ? FLYFI.cant_vote_warning : 'Voting is not currently enabled.');
            return;
        }
        
        if (self.playlistDict && self.playlistDict.in_contest) {
            FLYFI.captcha.voteUp(trackDict, function() {self.onServer_VoteUp(trackDict, noSelect);});
        } else {
            self.doVoteUp(showDialog, noSelect, trackDict);
        }
    };
    self._onVoteUpTrackFinished = function(showDialog, noSelect, trackDict) {
        // same as _onVoteUp, but does not vote in a contest
        if (!trackDict) {
            trackDict = self.trackDict;
        }
        
        if (!self.playlistDict || !self.playlistDict.in_contest) {
            self.doVoteUp(showDialog, noSelect, trackDict);
        }
    };
    self.doVoteUp = function(showDialog, noSelect, trackDict) {
        // implementation of _onVoteUp, but allows caller to decide if we should show the dialog.
        var onServer_VoteUp_fn = function(trackDict) { self.onServer_VoteUp(trackDict, noSelect); };
        if (self.changeListOnVotes && self.playlistDict && (self.playlistDict.writable || self.playlistDict.isshared)) {
            // only libraries are writeable
            FLYFI.voteUp_changableList(trackDict, self.playlistDict.libraryID, showDialog, onServer_VoteUp_fn);
        } else {
            FLYFI.voteUp(trackDict, self.playlistDict ? self.playlistDict.libraryID : null, showDialog, onServer_VoteUp_fn, self.isArtistPlayer);
        }
        
    };
    self.onServer_VoteUp = function(trackDict, noSelect) {
        // called after the server does the vote up
        var pcTrackDict;
        if (trackDict.trackID) {
            pcTrackDict = self.trackDictForID(trackDict.trackID);
        } else {
            pcTrackDict = self.contestEntryDictForID(trackDict.contestEntryID);
        }
        var widget = pcTrackDict ? pcTrackDict.widget : null;
        widget.removeClass('voteddown').addClass('votedup');

        if (!widget || widget.hasClass('approved')) {
            return true;     // don't change anything if voting up again - including don't get more recs
        }
        
        var trackList = widget.parent();
        var lastApproved = trackList.find('.approved:last');

        if (self.changeListOnVotes && self.playlistDict && (self.playlistDict.writable || self.playlistDict.isshared)) {
            if (!noSelect) {
            //    self.selectNext();
            }
            widget.removeClass('isrec').addClass('approved');
            if (lastApproved.length === 0) {
                if (!widget.is(':first-child')) {
                    widget.remove();
                    trackList.prepend(widget);
                }
            } else {
                if (widget != lastApproved.next()) {
                    widget.remove();
                    lastApproved.after(widget);
                }
            }

            FLYFI.addTrackClassExclusive(widget, 'last');
            widget.show(); // Make sure tracks added while recs were hidden are displayed.
            FLYFI.approveAndMovePlayerTrackDict(FLYFI.trackIDFromWidget(widget), self.playlistDict.tracks); // make same changes in the data
        }
        if (self.playlistDict && self.playlistDict.voteCallback) {
            return self.playlistDict.voteCallback(true, widget.trackDict); // True = vote was an upvote
        }
    };
    
    self.onVoteDown = function(event) {
        if (event) { // may be called from the code
            event.preventDefault();
        }
        var widget = self.currentWidget();
        var wasPlaying = widget.hasClass('playingtrack');
        // Need to submit downvote before going to next track - may need to pass video id.
        var voteDownFn = (self.isArtistPlayer) ? function(trackDict, libraryID, callback) {FLYFI.voteDown(trackDict, libraryID, callback, true);} : FLYFI.voteDown;
        if (self.changeListOnVotes && self.playlistDict && (self.playlistDict.writable || self.playlistDict.isshared)) {
            voteDownFn = FLYFI.voteDown_changableList;
        }
        if (self.playlistDict && self.playlistDict.libraryID) {
            voteDownFn(widget.trackDict, self.playlistDict.libraryID, function() { self.onServer_VoteDown(widget); });
        }
        if (wasPlaying) {
            self.playNext();
        } else {
            self.selectTrack(self.nextTrackWidget().trackDict);
        }
    };
    self.onServer_VoteDown = function(widget) {
        // called after the server does the vote down
        widget.removeClass('votedup').addClass('voteddown');
                 
        if (self.changeListOnVotes && self.playlistDict && (self.playlistDict.writable && !self.playlistDict.isshared)) { // For shared lists down votes do not remove tracks
            widget.removeClass('approved');
            widget.remove();
            FLYFI.removePlayerTrackDict(FLYFI.trackIDFromWidget(widget), self.playlistDict.tracks); // make same changes in the data
        }
        
        if (self.playlistDict && self.playlistDict.voteCallback) {
            return self.playlistDict.voteCallback(false, widget.trackDict); // False = vote was not an upvote 
        }
    };
    
    self.onBadVideo = function(event) {
        if (event) { event.preventDefault(); }
        if (!confirm("Are you sure you want to ban this video?")) {
            return;
        }
        self.onNextVideo(event, FLYFI.BADVIDEO_USER_VOTE);
    };
    
    self.onNextVideo = function(event, badVideo, previous) {
        if (event) { // may be called from the code
            event.preventDefault();
        }
        if (FLYFI.playingTrack.playerControls == self) {
            FLYFI.playingTrack.playNextVideo(badVideo, previous);
        } else {
            self.onPlay();
        }
    };
    
    self.onPrevVideo = function(event) {
        self.onNextVideo(event, false, true);
    };

    self.onServer_AddTrackToBookmarks = function(playlistDict) {
        FLYFI._doAddKeeper(playlistDict.libraryID, true);
        alert("The track was added to your 'Bookmarks' library.");
    };
    self.onBookmark = function(event) {
        event.preventDefault();
        if (self.trackDict) {
            FLYFI.addDialog_trackDict = self.trackDict;
            FLYFI.ensureBookMarkLibrary(self.onServer_AddTrackToBookmarks);
        }
    };
    
    self.onKeep = function(event) {
        if (FLYFI.isLikeIPhone()) {
            event.preventDefault();
            self.onBookmark(event);
            return;
        }
        
        if (self.trackDict.download_url) {
            // allow default behavior, which starts the download
            setTimeout("FLYFI.server_ReportDownload(" + self.trackDict.trackID + (self.trackDict.library_id ? "," + self.trackDict.library_id : "") + ")", 250);
        } else {
            event.preventDefault();
        }
    };
    
    self.onPlayIPhone = function(event) {
        // the iPhone cannot play the track inline - instead, we download the file to an IFRAME and Quicktime does the rest
        $('#qtframe').attr('src', self.trackDict.download_url);
    };

    self.onShare = function(event) {
        event.preventDefault();
        FLYFI.onClick_shareTrack_link(event);
    };
    
    self.onEmail = function(event) {
        event.preventDefault();
        var libraryID = (self.playlistDict && self.playlistDict.libraryID) ? self.playlistDict.libraryID : 0;// NYI - what if contest category?
        FLYFI.emailTrack(self.trackDict, libraryID, false);
    };
    
    self.onAdd = function(event) {
        event.preventDefault();
        FLYFI.addTrack(self.trackDict, '#addTrack_dialog', '#addtrack_link');
    };
    
    self.onDelete = function(event) {
        event.preventDefault();
        FLYFI.onClick_delete_playerTrackCell(self.trackDict.widget, self);
    };
    
    self.onBuy = function(event) {
        event.preventDefault();
        var libraryID = (self.playlistDict && self.playlistDict.libraryID) ? self.playlistDict.libraryID : 0;// NYI - what if contest category?
        FLYFI.buyTrack(self.trackDict, libraryID);
    };
    
    self.onFind = function(event) {
        event.preventDefault();
        FLYFI.findTrack();
    };
    
    self.onSearch = function(event) {
        if (event) { event.preventDefault(); }
        var input = self.searchInput.val();
        if (input == '') {
            return;
        }
        $("#player_shell_shell").addClass("showsearch");
        $("#search_tile").addClass("showsearch");   
        $("#about_two").addClass("showsearch");           
        FLYFI.search_clear();
        FLYFI.search_result_rows = 6;
        FLYFI.search_result_columns = 1;
        FLYFI.search_criteria = input;
        FLYFI.search_trackOffset = 0;
        FLYFI.search_trackList = [];
        FLYFI.search_allTracks = [];
        FLYFI.search_trackDicts = [];
        FLYFI.search_addWidgetCallback = self.searchAddWidgetCallback;
        FLYFI.search_getMoreTracks();
    };
    self.onSearchInputKeyup = function(event) {
        if (event.which == 13) {
            event.preventDefault();
            self.onSearch();
        }
    };
    
    self.searchAddWidgetCallback = function(trackDict, parent) {
        return FLYFI.SearchAdd_WidgetCallback(self, trackDict, parent);
    };
    self.onSearchAdd = function(trackDict) {
        var list = self.playerControlsWidget.parents().find('.tracklist');
        list.append(new FLYFI.PlayerTrackCell(trackDict, true, self).html());
        self._onVoteUp(false, true, trackDict);
    };
    
    self.onMouseDown = function(event) {
        event.preventDefault();
        event.stopPropagation();
        if(!FLYFI.playingTrack.startScrubbing(event, self.progressBars)) {
            self.play();   // could not scrub, so start playing at the beginning.
        }
    };

    self.onMouseMove = function(event) {
        event.preventDefault();
        event.stopPropagation();
        if (!FLYFI.playingTrack.scrubbing) {
            return;
        }
        FLYFI.playingTrack.scrub(event, self.progressBars);
    };

    self.onMouseUp = function(event) {
        FLYFI.onMouseUp_scrubber(event, self.progressBars);
    };

    self.onSoundPlay = function() {
        self.setPlaying();
    };
    
    self.onSoundFinished = function() {
        self.setNotPlaying();

        if (self.onSoundFinishedCallback) {
            self.onSoundFinishedCallback(self);
        }
    };
    
    self.onSoundStopped = function(event) {
        self.setNotPlaying();
    };
    
    self.onReplaceTrack = function(event) {
        event.preventDefault();
        self.pause();
        self.videoSpinner.show();
        if (self.playlistDict && self.playlistDict.libraryID) { // NYI - can this be called with contest category?
            FLYFI.replaceTrackInLibrary(self.trackDict.trackID, self.playlistDict.libraryID, self.onServer_ReplacedTrack);
        }
    };
    self.onServer_ReplacedTrack = function(newTrackDict) {
        if (!newTrackDict) {
            FLYFI.showFadingAlert('We could not find a replacement track at the moment.');
            return;
        }
        // Replace the current trackDict the the supplied one.
        var currentTrackID = self.trackDict.trackID;
        var trackDicts = self.trackDictsWithWidgets;
        var l = trackDicts.length;
        for (var i = 0; i < l; ++i) {
            if (trackDicts[i].trackID == currentTrackID) {
                // Add new widget after the old one. NOTE: This also adds the new trackDict to end of the array
                self.trackDict.widget.after(new FLYFI.PlayerTrackCell(newTrackDict, true, self).html());
                self.trackDict.widget.remove(); // Remove old widget
                trackDicts.splice(l, 1); // Remove new trackDict from end of array
                trackDicts[i] = newTrackDict; // Replace old track dict with new one
                self.selectTrack(newTrackDict, true);
                return;
            }
        }
    };
    
    self.onRefreshRecs = function(event) {
        event.preventDefault();
        event.stopPropagation();
        if (self.playlistDict && self.playlistDict.libraryID) { // must be a library to get recs
            FLYFI.refreshRecsForLibrary(self.playlistDict.libraryID, self.onServer_RefreshedRecs);
        }
    };
    self.onServer_RefreshedRecs = function(refreshed) {
        if (!refreshed || !self.playlistDict) {
            return;
        }
        FLYFI.removeUnapprovedPlayerTrackDicts(self.playlistDict.tracks, true);
        if (self.playlistDict.showHideRecsCallback) {
            self.playlistDict.showHideRecsCallback(self.playlistDict);
        } else {
            FLYFI.ensureEnoughRecs(self.playlistDict.libraryID);
        }
        if (self.trackDict && !self.trackDict.approved) {
            self.selectFirstTrack();
        }
    };
    
    self.nextTrackWidget = function() {
        // return the next track object
        // If there is an active track which is not the current track then it is the next one,
        // otherwise find the one after the current one.
        var widget = self.currentWidget();
        var activeWidget = self.targetTrackWidget(true, true);
        if (!widget && !activeWidget) {
            return self.firstTrackWidget();
        }
        if (activeWidget && (activeWidget != widget)) {
            return activeWidget;
        }
        
        var nextWidget;
        if (widget.is(':visible')) {
            // first check in the DOM 
            nextWidget = widget.nextAll(':not(.rec_divider,.voteddown):first');
            if (nextWidget.length === 0) {
                nextWidget = widget.siblings(':not(.rec_divider,.voteddown):first');
            }
            if (nextWidget.length === 0 || nextWidget == widget) {
                return null; // All tracks are unsuitable
            }        
        } else {
            // no longer there - just use the first visible track
            var l = self.trackDictsWithWidgets.length;
            for (var i = 0; i < l; ++i ) {
                var w = self.trackDictsWithWidgets[i].widget;
                if ($(w).is(':visible:not(.rec_divider,.voteddown)')) {
                    nextWidget = w;
                    break;
                }
            }
        }
        
        // if the DOM can't help, use the list
        if (!nextWidget) {
            nextWidget = self.nextTrackWidgetInList();
        }
        if (!nextWidget) {
            return null;
        }
        var trackDict = self.itemDictForWidget(nextWidget);
        return trackDict ? trackDict.widget : null;
    };

    self.previousTrackWidget = function(noWrap) {
        // return the track object before the current track
        var widget = self.currentWidget();

        var prevWidget;
        if (widget.is(':visible')) {
            // first check in the DOM 
            prevWidget = widget.prevAll(':not(.rec_divider,.voteddown):first');
            if (prevWidget.length === 0) {
                prevWidget = widget.siblings(':not(.rec_divider,.voteddown):last');
            }
            if (prevWidget.length === 0 || prevWidget == widget) {
                return null; // All tracks are unsuitable
            }  
        } else {
            // no longer there - just use the first visible track
            var l = self.trackDictsWithWidgets.length;
            for (var i = 0; i < l; ++i ) {
                var w = self.trackDictsWithWidgets[i].widget;
                if ($(w).is(':visible:not(.rec_divider,.voteddown)')) {
                    prevWidget = w;
                    break;
                }
            }
        }
        
        // if the DOM can't help, use the list
        if (!prevWidget) {
            prevWidget = self.prevTrackWidgetInList();
        }
        if (!prevWidget) {
            return null;
        }
        var trackDict = self.itemDictForWidget(prevWidget);
        return trackDict ? trackDict.widget : null;
    };

    self.selectNext = function() {
        var widget = self.nextTrackWidget();
        if (widget && widget.length > 0) {
            if (self == FLYFI.playerControls) {
                // In the brown player we just want to activate the selected track
                FLYFI.addTrackClassExclusive(widget, 'activetrack');
            } else {
                self.selectTrack(widget.trackDict);
            }
        }
    };
    
    self.playNext = function() {  
        var widget = self.nextTrackWidget();
        self.pause();
        if (widget && widget.length > 0) {
            self._playWidget(widget);
        }
    }; 
    
    self.playPrevious = function() {  
        var widget = self.previousTrackWidget();
        self.pause();
        if (widget && widget.length > 0) {
            self._playWidget(widget);
        }
    }; 
    
    self._playWidget = function(widget) {
        if (self.widgetPlayCallback) {
            self.widgetPlayCallback(widget);
        } else {
            widget.click();
        }
    };
    
    self.onClick_PlayerTrackList = function(event) {
        // use Event Delegation to reduce the number of handlers. 
        // See http://www.sitepoint.com/blogs/2008/07/23/javascript-event-delegation-is-easier-than-you-think/

        if ($(event.target).parents('.download_link').length > 0) { // on download, just download - don't play or change selection
            FLYFI.onKeep_playerTrackCell(event);
            return; // don't event.preventDefault() since the href does the download
        }
        
        event.preventDefault();
        var target = $(event.target);
        if (target.hasClass('rec_divider') || target.parents('.rec_divider').length !== 0) {
            var divider = target.hasClass('rec_divider') ? target : target.parents('ul');
            if (self.playlistDict) {
	            if (self.playlistDict.hide_recs) {
	                self.playlistDict.hide_recs = false;
	                divider.siblings('.isrec').show();
	                divider.addClass('show_recs');
	            } else {
	                if (target.hasClass('refresh_recs') || target.parents('.refresh_recs').length !== 0) {
	                    self.onRefreshRecs(event);
	                    return;
	                }
	                self.playlistDict.hide_recs = true;
	                divider.siblings('.isrec').hide();
	                divider.removeClass('show_recs');
	                if (self.trackDict && ! self.trackDict.approved) {
	                    self.selectFirstTrack();
	                }
	            }
	            FLYFI.showOrHideRecs(self.playlistDict);
            }
            return;
        }
        if (target.parents('.delete').length === 0) { // no play when delete was clicked
            self.playWidget_TrackList(target);
        } else {
            FLYFI.onClick_delete_playerTrackCell(target, self);
        }
    };

    self.playWidget_TrackList = function(target) {
        var widget = null;
        if (target.hasClass('trackcell')) {
            widget = target;
        } else {
            widget = target.parents('.trackcell').eq(0);
        }

        var trackDict = self.itemDictForWidget(widget);
        
        // WE MAY ADD THE FOLLOWING WIDGETS BACK INTO THE PLAYER OR COMBINE WITH THE OTHER CLICK HANDLER
        //if (target.hasClass('add_btn') || target.parents('.add_btn').length > 0) {
        //    playerControls.selectTrack(trackDict, false);
        //    playerControls.addButton.click(); 
        //} else if (target.hasClass('download_btn') || target.parents('.download_btn').length > 0) {
        //    playerControls.selectTrack(trackDict, false);
        //    var href = target.attr('href'); // default behavior is to download, but it was not working so force it here.
        //    window.location = href;
        //    playerControls.downloadButton.click(); 
        //} else { // play or pause
        if (widget.hasClass('playingtrack')) {
            self.pause();
        } else {
            FLYFI.playingTrack.pause(); // If another track is playing in thie control the select gets confused.
            if (trackDict) {
                self.selectTrack(trackDict, true);
            }
        }
    };
    
    // Have to replace click handlers - not just add them - the widgets may have handlers from an earlier library.
    self.handle = function(widget, handler, event) {
        if (!widget || widget.length === 0) {
            return;
        }
        if (!event) {
            event = 'click';
        }
        widget.unbind(event);
        widget.bind(event, handler);
    };
    
    self.setPlaylistDict = function(playlistDict) {
        self.playlistDict = playlistDict;  // change the playlist associated with the controls
        if (playlistDict && playlistDict.writable) { 
            self.handle(self.deleteButton, self.onDelete); 
            self.deleteButton.removeClass("unavailable");
            self.handle(self.replaceTrackWidget, self.onReplaceTrack);
            self.replaceTrackWidget.removeClass("unavailable");
        } else {
            self.deleteButton.addClass("unavailable");
            self.handle(self.deleteButton, FLYFI.nop);
            self.replaceTrackWidget.addClass("unavailable");
            self.handle(self.replaceTrackWidget, FLYFI.nop);
         }
        if (playlistDict) {
            self.sharePlaylistInFacebookLink.attr('href', (window.level == 'live' ? '' : '/' + window.level) + '/share/facebook/?share=' + playlistDict.libraryID);
            self.sharePlaylistInFacebookLink.show();
        } else {
            self.sharePlaylistInFacebookLink.hide();
        }
    };
    
    self.setPlaylistDict(playlistDict);  // the (initial) library associated with the controls

    // do bindings at the end after the functions are defined
    if (FLYFI.isLikeIPhone()) {
        self.handle(self.playButton, self.onPlayIPhone);
    } else {
        self.handle(self.playButton, self.onPlay);
    }
    self.handle(self.nextButton, self.playNext);
    self.handle(self.previousButton, self.playPrevious);

    self.handle(self.voteUpButton, self.onVoteUp);
    self.handle(self.voteDownButton, self.onVoteDown);
    self.handle(self.useThisVideoButton, self.onVoteUp);
    self.handle(self.badVideoButton, self.onBadVideo);
    self.handle(self.nextVideoButton, self.onNextVideo);
    self.handle(self.prevVideoButton, self.onPrevVideo);
    self.handle(self.downloadButton, self.onKeep);
    self.handle(self.shareMenu, self.onShare);
    self.handle(self.emailButton, self.onEmail);
    
    self.handle(self.addButton, self.onAdd);
    self.handle(self.buyButton, self.onBuy);
    self.handle(self.findButton, self.onFind);
        
    if (self.playerControlsWidget.parents('.controls_on_page').length > 0) {
        self.handle(self.searchButton, self.onSearch); // Search widgets should target the main player.
        self.handle(self.searchInput, self.onSearchInputKeyup, 'keyup');
    }
    
    if (self.scrubber) { 
        self.handle(self.scrubber, self.onMouseDown, 'mousedown');
        self.handle(self.scrubber, self.onMouseMove, 'mousemove');
        self.handle(self.scrubber, self.onMouseUp, 'mouseup');
        self.handle(self.scrubber, self.onMouseUp, 'mouseout'); 
        self.handle(self.scrubber.find('*'), FLYFI.stopProp, 'mouseout'); // don't let children get in the way
    }
    self.handle(self.scrubThumb, FLYFI.nop); // don't let children get in the way
    self.handle(self.playerControlsWidget.parents('.playercolumn').find('.tracklist'), self.onClick_PlayerTrackList);
};

FLYFI.addTrackClassExclusive = function(target, className) {
    // set a class on the target track, clearing that class from all siblings
	if (!target) { // Carousel trackDicts do not have widgets.
		return;
	}
    var track = null;
    if (target.hasClass('trackcell')) {
        track = target;
    } else {
        track = target.parents('.trackcell');
    }
    try {
        var sibs = track.siblings();
    } catch(err) {
        console.log(">>>>>>>> error finding sibs: " + className + ": error=" + err);
    }
    try {
        track.siblings().removeClass(className);
    } catch(err2) {
        console.log(">>>>>>>> error removing class: " + className + ": error=" + err2);
    }
    try {
        track.addClass(className);
    } catch(err3) {
        console.log(">>>>>>>> error adding class: " + className + ": error=" + err3);
    }
};

FLYFI.initPlayerControls = function() {
    FLYFI.playerControls = new FLYFI.PlayerControls('#player_shell .player_controls', "videoplayer", null, true, FLYFI.PlayerTrackCell_onSoundFinishedCallback);
};
