Created
January 17, 2013 00:55
-
-
Save mattkenefick/4552555 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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