/**
* Created by 23rd and Walnut for Codebasehero.com
* www.23andwalnut.com
* www.codebasehero.com
* User: Saleem El-Amin
* Date: 6/11/11
* Time: 6:41 AM
*
* Version: 1.01
* License: MIT License
*/
(function($) {
$.fn.ttwMusicPlayer = function(playlist, userOptions) {
var $self = this, defaultOptions, options, cssSelector, appMgr, playlistMgr, interfaceMgr, ratingsMgr, playlist,
layout, ratings, myPlaylist, current;
cssSelector = {
jPlayer: "#jquery_jplayer",
jPlayerInterface: '.jp-interface',
playerPrevious: ".jp-interface .jp-previous",
playerNext: ".jp-interface .jp-next",
trackList:'.tracklist',
tracks:'.tracks',
track:'.track',
trackRating:'.rating-bar',
trackInfo:'.track-info',
rating:'.rating',
ratingLevel:'.rating-level',
ratingLevelOn:'.on',
title: '.title',
duration: '.duration',
buy:'.buy',
buyNotActive:'.not-active',
playing:'.playing',
moreButton:'.more',
player:'.player',
artist:'.artist',
artistOuter:'.artist-outer',
albumCover:'.img'
};
defaultOptions = {
ratingCallback:null,
currencySymbol:'$',
buyText:'BUY',
tracksToShow:5,
autoPlay:false,
jPlayer:{}
};
options = $.extend(true, {}, defaultOptions, userOptions);
myPlaylist = playlist;
current = 0;
appMgr = function() {
playlist = new playlistMgr();
layout = new interfaceMgr();
layout.buildInterface();
playlist.init(options.jPlayer);
//don't initialize the ratings until the playlist has been built, which wont happen until after the jPlayer ready event
$self.bind('mbPlaylistLoaded', function() {
$self.bind('mbInterfaceBuilt', function() {
ratings = new ratingsMgr();
});
layout.init();
});
};
playlistMgr = function() {
var playing = false, markup, $myJplayer = {},$tracks,showHeight = 0,remainingHeight = 0,$tracksWrapper, $more;
markup = {
listItem:'
' +
'' +
'' +
'' +
'' +
'',
ratingBar:''
};
function init(playlistOptions) {
$myJplayer = $('.ttw-music-player .jPlayer-container');
var jPlayerDefaults, jPlayerOptions;
jPlayerDefaults = {
swfPath: "jquery-jplayer",
supplied: "mp3, oga",
cssSelectorAncestor: cssSelector.jPlayerInterface,
errorAlerts: false,
warningAlerts: false
};
//apply any user defined jPlayer options
jPlayerOptions = $.extend(true, {}, jPlayerDefaults, playlistOptions);
$myJplayer.bind($.jPlayer.event.ready, function() {
//Bind jPlayer events. Do not want to pass in options object to prevent them from being overridden by the user
$myJplayer.bind($.jPlayer.event.ended, function(event) {
playlistNext();
});
$myJplayer.bind($.jPlayer.event.play, function(event) {
$myJplayer.jPlayer("pauseOthers");
$tracks.eq(current).addClass(attr(cssSelector.playing)).siblings().removeClass(attr(cssSelector.playing));
});
$myJplayer.bind($.jPlayer.event.playing, function(event) {
playing = true;
});
$myJplayer.bind($.jPlayer.event.pause, function(event) {
playing = false;
});
//Bind next/prev click events
$(cssSelector.playerPrevious).click(function() {
playlistPrev();
$(this).blur();
return false;
});
$(cssSelector.playerNext).click(function() {
playlistNext();
$(this).blur();
return false;
});
$self.bind('mbInitPlaylistAdvance', function(e) {
var changeTo = this.getData('mbInitPlaylistAdvance');
if (changeTo != current) {
current = changeTo;
playlistAdvance(current);
}
else {
if (!$myJplayer.data('jPlayer').status.srcSet) {
playlistAdvance(0);
}
else {
togglePlay();
}
}
});
buildPlaylist();
//If the user doesn't want to wait for widget loads, start playlist now
$self.trigger('mbPlaylistLoaded');
playlistInit(options.autoplay);
});
//Initialize jPlayer
$myJplayer.jPlayer(jPlayerOptions);
}
function playlistInit(autoplay) {
current = 0;
if (autoplay) {
playlistAdvance(current);
}
else {
playlistConfig(current);
$self.trigger('mbPlaylistInit');
}
}
function playlistConfig(index) {
current = index;
$myJplayer.jPlayer("setMedia", myPlaylist[current]);
}
function playlistAdvance(index) {
playlistConfig(index);
if (index >= options.tracksToShow)
showMore();
$self.trigger('mbPlaylistAdvance');
$myJplayer.jPlayer("play");
}
function playlistNext() {
var index = (current + 1 < myPlaylist.length) ? current + 1 : 0;
playlistAdvance(index);
}
function playlistPrev() {
var index = (current - 1 >= 0) ? current - 1 : myPlaylist.length - 1;
playlistAdvance(index);
}
function togglePlay() {
if (!playing)
$myJplayer.jPlayer("play");
else $myJplayer.jPlayer("pause");
}
function buildPlaylist() {
var $ratings = $();
$tracksWrapper = $self.find(cssSelector.tracks);
//set up the html for the track ratings
for (var i = 0; i < 5; i++)
$ratings = $ratings.add(markup.ratingBar);
for (var j = 0; j < myPlaylist.length; j++) {
var $track = $(markup.listItem);
//since $ratings refers to a specific object, if we just use .html($ratings) we would be moving the $rating object from one list item to the next
$track.find(cssSelector.rating).html($ratings.clone());
$track.find(cssSelector.title).html(trackName(j));
$track.find(cssSelector.duration).html(duration(j));
setRating('track', $track, j);
setBuyLink($track, j);
$track.data('index', j);
$tracksWrapper.append($track);
}
$tracks = $(cssSelector.track);
$tracks.slice(0, options.tracksToShow).each(function() {
showHeight += $(this).outerHeight();
});
$tracks.slice(options.tracksToShow, myPlaylist.length).each(function() {
remainingHeight += $(this).outerHeight();
});
if (remainingHeight > 0) {
var $trackList = $(cssSelector.trackList);
$tracksWrapper.height(showHeight);
$trackList.addClass('show-more-button');
$trackList.find(cssSelector.moreButton).click(function() {
$more = $(this);
showMore();
});
}
$tracks.find('.title').click(function() {
playlistAdvance($(this).parents('li').data('index'));
});
}
function showMore() {
if (isUndefined($more))
$more = $self.find(cssSelector.moreButton);
$tracksWrapper.animate({height: showHeight + remainingHeight}, function() {
$more.animate({opacity:0}, function() {
$more.slideUp(function() {
$more.parents(cssSelector.trackList).removeClass('show-more-button');
$more.remove();
});
});
});
}
function duration(index) {
return !isUndefined(myPlaylist[index].duration) ? myPlaylist[index].duration : '-';
}
function setBuyLink($track, index) {
if (!isUndefined(myPlaylist[index].buy)) {
$track.find(cssSelector.buy).removeClass(attr(cssSelector.buyNotActive)).attr('href', myPlaylist[index].buy).html(buyText(index));
}
}
function buyText(index) {
return (!isUndefined(myPlaylist[index].price) ? options.currencySymbol + myPlaylist[index].price : '') + ' ' + options.buyText;
}
return{
init:init,
playlistInit:playlistInit,
playlistAdvance:playlistAdvance,
playlistNext:playlistNext,
playlistPrev:playlistPrev,
togglePlay:togglePlay,
$myJplayer:$myJplayer
};
};
ratingsMgr = function() {
var $tracks = $self.find(cssSelector.track);
function bindEvents() {
//Handler for when user hovers over a rating
$(cssSelector.rating).find(cssSelector.ratingLevel).hover(function() {
$(this).addClass('hover').prevAll().addClass('hover').end().nextAll().removeClass('hover');
});
//Restores previous rating when user is finished hovering (assuming there is no new rating)
$(cssSelector.rating).mouseleave(function() {
$(this).find(cssSelector.ratingLevel).removeClass('hover');
});
//Set the new rating when the user clicks
$(cssSelector.ratingLevel).click(function() {
var $this = $(this), rating = $this.parent().children().index($this) + 1, index;
if ($this.hasClass(attr(cssSelector.trackRating))) {
rating = rating / 2;
index = $this.parents('li').data('index');
if (index == current)
applyCurrentlyPlayingRating(rating);
}
else {
index = current;
applyTrackRating($tracks.eq(index), rating);
}
$this.prevAll().add($this).addClass(attr(cssSelector.ratingLevelOn)).end().end().nextAll().removeClass(attr(cssSelector.ratingLevelOn));
processRating(index, rating);
});
}
function processRating(index, rating) {
myPlaylist[index].rating = rating;
runCallback(options.ratingCallback, index, myPlaylist[index], rating);
}
bindEvents();
};
interfaceMgr = function() {
var $player, $title, $artist, $albumCover;
function init() {
$player = $(cssSelector.player),
$title = $player.find(cssSelector.title),
$artist = $player.find(cssSelector.artist),
$albumCover = $player.find(cssSelector.albumCover);
$self.bind('mbPlaylistAdvance mbPlaylistInit', function() {
setTitle();
setArtist();
setRating('current', null, current);
setCover();
});
}
function buildInterface() {
var markup, $interface;
//I would normally use the templating plugin for something like this, but I wanted to keep this plugin's footprint as small as possible
markup = '' +
'
' +
'
' +
'' +
' ' +
'
' +
'
' +
'
' +
'
By
' +
'
' +
' ' +
' ' +
' ' +
' ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'' +
'
' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
View More...
' +
'
' +
'
' +
'
';
$interface = $(markup).css({display:'none', opacity:0}).appendTo($self).slideDown('slow', function() {
$interface.animate({opacity:1});
$self.trigger('mbInterfaceBuilt');
});
}
function setTitle() {
$title.html(trackName(current));
}
function setArtist() {
if (isUndefined(myPlaylist[current].artist))
$artist.parent(cssSelector.artistOuter).animate({opacity:0}, 'fast');
else {
$artist.html(myPlaylist[current].artist).parent(cssSelector.artistOuter).animate({opacity:1}, 'fast');
}
}
function setCover() {
$albumCover.animate({opacity:0}, 'fast', function() {
if (!isUndefined(myPlaylist[current].cover)) {
var now = current;
$('', this).imagesLoaded(function(){
if(now == current)
$albumCover.html($(this)).animate({opacity:1})
});
}
});
}
return{
buildInterface:buildInterface,
init:init
}
};
/** Common Functions **/
function trackName(index) {
if (!isUndefined(myPlaylist[index].title))
return myPlaylist[index].title;
else if (!isUndefined(myPlaylist[index].mp3))
return fileName(myPlaylist[index].mp3);
else if (!isUndefined(myPlaylist[index].oga))
return fileName(myPlaylist[index].oga);
else return '';
}
function fileName(path) {
path = path.split('/');
return path[path.length - 1];
}
function setRating(type, $track, index) {
if (type == 'track') {
if (!isUndefined(myPlaylist[index].rating)) {
applyTrackRating($track, myPlaylist[index].rating);
}
}
else {
//if the rating isn't set, use 0
var rating = !isUndefined(myPlaylist[index].rating) ? Math.ceil(myPlaylist[index].rating) : 0;
applyCurrentlyPlayingRating(rating);
}
}
function applyCurrentlyPlayingRating(rating) {
//reset the rating to 0, then set the rating defined above
$self.find(cssSelector.trackInfo).find(cssSelector.ratingLevel).removeClass(attr(cssSelector.ratingLevelOn)).slice(0, rating).addClass(attr(cssSelector.ratingLevelOn));
}
function applyTrackRating($track, rating) {
//multiply rating by 2 since the list ratings have 10 levels rather than 5
$track.find(cssSelector.ratingLevel).removeClass(attr(cssSelector.ratingLevelOn)).slice(0, rating * 2).addClass(attr(cssSelector.ratingLevelOn));
}
/** Utility Functions **/
function attr(selector) {
return selector.substr(1);
}
function runCallback(callback) {
var functionArgs = Array.prototype.slice.call(arguments, 1);
if ($.isFunction(callback)) {
callback.apply(this, functionArgs);
}
}
function isUndefined(value) {
return typeof value == 'undefined';
}
appMgr();
};
})(jQuery);
(function($) {
// $('img.photo',this).imagesLoaded(myFunction)
// execute a callback when all images have loaded.
// needed because .load() doesn't work on cached images
// mit license. paul irish. 2010.
// webkit fix from Oren Solomianik. thx!
// callback function is passed the last image to load
// as an argument, and the collection as `this`
$.fn.imagesLoaded = function(callback) {
var elems = this.filter('img'),
len = elems.length;
elems.bind('load',
function() {
if (--len <= 0) {
callback.call(elems, this);
}
}).each(function() {
// cached images don't fire load sometimes, so we reset src.
if (this.complete || this.complete === undefined) {
var src = this.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
// data uri bypasses webkit log warning (thx doug jones)
this.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
this.src = src;
}
});
return this;
};
})(jQuery);