Skip to content

Instantly share code, notes, and snippets.

@mattkenefick
Created January 17, 2013 00:55
Show Gist options
  • Save mattkenefick/4552555 to your computer and use it in GitHub Desktop.
Save mattkenefick/4552555 to your computer and use it in GitHub Desktop.
/**
* Application.ui.Playlist
*
* Description
*
*
*/
Application.ui.Playlist = new(function(){
// private vars
var _self = this;
var _ref = null;
var _playlistItem = null;
var _interval = null;
// public vars
this.name = "Application.ui.Playlist";
// ==================================================
// ======= Public Methods
// ==================================================
this.attach = function attach(){
// set reference to the playlist to be resused
_ref = $('#Playlist');
// add additional html
_self.setup();
_self.detatch();
// attach click handlers
_ref.find('li .track').bind ('click', _playlistItem_CLICK_handler);
_ref.find('.action_songDetails').bind ('click', _songDetails_CLICK_handler);
// always run the timing loop
_interval = setInterval (_currentTrack_INTERVAL_handler, 200);
// save template used to rebuild albums
if(!_playlistItem || _ref.children('li').size() > 0){
_playlistItem = _ref.children('li').first().clone();
};
// when we switch albums, we check to see if one is currently
// playing... if this is it, then highlight and start tracking
if(PlaylistModel.playlistIsVisible && PlaylistModel.artist != null){
_self.setActive(PlaylistModel.currentTrack);
PlaylistModel.setCurrentDOMTrack();
};
};
this.detatch = function detatch(){
// clear interval following progress
clearInterval(_interval);
_interval = null;
// rebind events to each song
_ref.find('li .track').unbind ('click', _playlistItem_CLICK_handler);
_ref.find('.action_songDetails').unbind ('click', _songDetails_CLICK_handler);
};
/**
* setup
*
* Adds extra HTML that isn't originally included in the markup.
* This includes graphic things like progress bar, or things
* that require authentication like "delete" buttons.
*
* Called by attach.
*/
this.setup = function setup(){
// add progress bar
_ref.children('li').append('<div class="progress"></div>');
_ref.children('li').append('<div class="buffer"></div>');
};
/**
* removeAllTracks
*
* Removes all elements from the current playlist. This is
* usually used by when you create a new album. Could also
* be used by mixes.
*/
this.removeAllTracks = function removeAllTracks(){
$("#Playlist li").slideUp(function(){
$(this).remove();
});
};
// ==================================================
// ======= Interaction Methods
// ==================================================
/**
* resetAllTracks
*
* Sets all tracks back to their default appearance
*/
this.resetAllTracks = function resetAllTracks(){
_ref.children('li').find('.duration').html ('00:00');
_ref.children('li').find('.time').html ('00:00');
// remove classes
_ref.children('li')
.removeClass('active')
.find('.progress, .buffer')
.animate({
width: '0px'
}, 300);
};
/**
* setActive
*
* Resets other tracks and sets the selected songIndex to
* be the current track.
*/
this.setActive = function setActive($songIndex){
var ref = null;
Application.debug("Setting Active: ", $songIndex);
// remove all other tracks active states
_self.resetAllTracks();
if(isNaN($songIndex)){
// get direct reference
ref = $($songIndex);
}else{
// get reference by index number
ref = _ref.children('li').eq($songIndex);
};
// add it
ref.addClass('active');
// add to model
PlaylistModel.currentDOMTrack = ref;
};
/**
* addTrack
*
* $params object artist, song, duration, ...
* $index number 1, 2, 3, 4... (1 == 0 regarding standard counting);
*/
this.addTrack = function addTrack($params, $index){
if(isNaN($index) || _ref.children('li').size() < $index ){
var _new = _playlistItem.clone().appendTo(_ref);
}else{
var _new = _playlistItem.clone().insertBefore( _ref.children('li').eq($index-1) );
};
// hide and set css
_new.hide();
// set data
_new.find('.song').html($params.song);
_new.find('.artist').html($params.artist);
_new.attr('data-url', $params.url || '0');
// renumber list
_self.renumber();
// animate in
_new.slideDown();
// dispatch signal
SignalDispatcher.sendSignal('Playlist', 'ADD_TRACK');
return _self;
};
/**
* renumber
*
* Change numbers on all tracks
*/
this.renumber = function renumber(){
$('#Playlist li').each(function($i, $t){
var index = leadingZeros($i + 1, 2);
$($t).find('.index h2').html( index );
});
// dispatch signal
SignalDispatcher.sendSignal('Playlist', 'RENUMBER');
};
/**
* togglePlayPause
*
*/
this.togglePlayPause = function togglePlayPause($target){
if(!MediaPlayer.playPause()){
$target.addClass('paused');
}else{
$target.removeClass('paused');
};
return false;
};
/**
* playNext
*
* Play the next song
*/
this.playNext = function playNext(){
var current = PlaylistModel.currentTrack;
var next = PlaylistModel.nextTrackIndex();
// we're at an end.
if(next == current)
return false;
// set the song element to be active
// if our playlist is visible
if(PlaylistModel.playlistIsVisible){
Application.ui.Playlist.setActive(next);
};
// play the actual song
Application.debug("Song: " + PlaylistModel.getTrack(next).source );
MediaPlayer.play( PlaylistModel.getTrack(next).source );
};
/**
* playPrev
*
* Play the previous song
*/
this.playPrev = function playPrev(){
var current = PlaylistModel.currentTrack;
var next = PlaylistModel.prevTrackIndex();
// we're at an end.
if(next == current)
return false;
// set the song element to be active
// if our playlist is visible
if(PlaylistModel.playlistIsVisible){
Application.ui.Playlist.setActive(next);
};
// play the actual song
Application.debug("Song: " + PlaylistModel.getTrack(next).source );
MediaPlayer.play( PlaylistModel.getTrack(next).source );
};
// ==================================================
// ======= Event Handlers
// ==================================================
/**
* _mediaPlayer_COMPLETE_handler
*
* Accepts event issued by the MediaPlayer class. This is
* fired when a song is finished. We then check our model
* to get the next indexed song. We attempt to play it and
* set it active. If it doesn't exist or fails, we should
* also handle that here.
*
*/
function _mediaPlayer_COMPLETE_handler($e, $d, $a){
var current = PlaylistModel.currentTrack;
var next = PlaylistModel.nextTrackIndex();
var url = null;
if(next == current){
// must be the last track
// see if there's a new playlist up
if(!PlaylistModel.playlistIsVisible){
$('#Playlist li .track').eq(0).click();
}else{
_self.resetAllTracks();
};
return false;
};
// set the song element to be active
// if our playlist is visible
if(PlaylistModel.playlistIsVisible){
_self.setActive(next);
};
// play the actual song
url = PlaylistModel.getTrack(next).source;
MediaPlayer.play(url, url.indexOf('youtube.com') === -1 );
};
/**
* _mediaPlayer_ERROR_handler
*
* When a video is taken down by WMG or something.. it
* throws an error. We should not only skip the song,
* but report it to the database.
*
*/
function _mediaPlayer_ERROR_handler($e, $d, $a){
// skip track
_mediaPlayer_COMPLETE_handler();
// call model
PlaylistModel.error();
}
/**
* _songDetails_CLICK_handler
*
* Each track has an object that'll open the side panel
* where they can see details about the song and things
* like that. This handles the click, sets the new content,
* and asks it to open.
*/
function _songDetails_CLICK_handler($e){
// get object, artist, song, from the list item
var li = $(this).parent().parent().parent();
var artist = li.find('.artist').html();
var song = li.find('.song').html();
// set some details in the PlaylistDetails object that it can use
// to populate/understand the request
Application.ui.PlaylistDetails.openedItem = li.attr('id');
Application.ui.PlaylistDetails.songTitle = song;
Application.ui.SearchForSong.setSearchValue( artist + ' - ' + song );
// set source
$('#PlaylistDetails #txtAddYoutubeUrl').val(li.data('url'));
$('#PlaylistDetails .action_viewUrl').attr('href', li.data('url'));
$("#PlaylistDetails .song-title").html(song);
$("#SetSourceFor").val(song);
if(li.children('#PlaylistDetails').size()){
// close panel
Application.ui.PlaylistDetails.hide();
}else{
// open panel
Application.ui.PlaylistDetails.insert(li);
Application.ui.PlaylistDetails.show();
};
return false;
};
/**
* _currentTrack_INTERVAL_handler
*
* Interval running to increase the progress bar,
* time, and other things related to the currently
* playing track.
*/
function _currentTrack_INTERVAL_handler($e){
var c = PlaylistModel.currentDOMTrack;
var time, duration, buffer, progress;
// ignore it if our current track isn't on the screen
if(!c){return false;};
// duration;
tDuration = PlaylistModel.getCurrentTrack().duration || MediaPlayer.duration();
tDuration = parseFloat(tDuration);
// get vars
time = formatTime(MediaPlayer.time());
duration = formatTime(tDuration);
progress = MediaPlayer.time()/tDuration * 100;
buffer = MediaPlayer.buffer();
// update
c.find('.time').html ( time );
c.find('.duration').html ( duration );
c.find('.progress').css ('width', progress + '%');
c.find('.buffer').css ('width', buffer + '%');
// special check (for special durations)
if(progress >= 100){
MediaPlayer.stop();
_self.playNext();
return false;
};
};
/**
* _playlistItem_CLICK_handler
*
* User initiates an actual click on a playlist item.
* Plays song, or opens panel if it doesn't exist.
*/
function _playlistItem_CLICK_handler($e){
var parent = $(this).parent();
var url = parent.data('url');
// check if it's valid.
if(!url){
Application.debug("Invalid video. Opening Details Panel.");
// forward this event to the song details handler
$(this).parent().find('.action_songDetails').click();
return false;
};
// set current album... if it's new
SignalDispatcher.sendSignal('Header', 'SETALBUM', {type: 'none'});
// tell everyone we're playing
SignalDispatcher.sendSignal('Playlist', 'PLAY', {ref: parent, index: parent.index()});
// make sure we know we're visible
PlaylistModel.playlistIsVisible = true;
// If our current song is playing already
// This should be handled by index, not active states
if( parent.hasClass('active') ){
_self.togglePlayPause(parent);
}else{
MediaPlayer.play(url, url.indexOf('youtube.com') === -1 );
// active
_self.setActive( $(this).parent() );
};
return false;
};
function _playlist_SAVE_handler($e, $d, $a){
$('#Playlist').data('albumId', $a.data.album_id);
};
// ==================================================
// ======= Constructor
// ==================================================
this.construct = function construct(){
// add listeners
SignalDispatcher.addSignal('Playlist', 'SAVE', _playlist_SAVE_handler);
SignalDispatcher.addSignal('MediaPlayer', 'COMPLETE', _mediaPlayer_COMPLETE_handler);
SignalDispatcher.addSignal('MediaPlayer', 'INVALIDSONG', _mediaPlayer_COMPLETE_handler);
SignalDispatcher.addSignal('MediaPlayer', 'ERROR', _mediaPlayer_ERROR_handler);
};
this.init = function init(){
_self.attach();
};
return Sage.create(this, {construct: 'construct', init: 'init'});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment