S
/*
* Author Sam Johnson
* Under MIT License
*/
;( function( $, window, document, undefined ) {
"use strict";
// Create the defaults once
var pluginName = "musicPlayer",
defaults = {
playlistItemSelector: 'li',
autoPlay: false ,
volume: 80,
loop: false,
timeSeparator: ' / ',
playerAbovePlaylist: true,
infoElements: ['title' , 'artist'] ,
elements: ['artwork', 'information', 'controls', 'progress', 'time', 'volume'],
timeElements: ['current', 'duration'],
controlElements: ['backward', 'play', 'forward', 'stop'],
onLoad: function() {},
onPlay: function() {},
onPause: function() {},
onStop: function() {},
onFwd: function() {},
onRew: function() {},
volumeChanged: function() {},
seeked: function() {} ,
trackClicked: function() {},
onMute: function() {}
};
//Setup Touch Events
var isTouch = 'ontouchstart' in window,
eStart = isTouch ? 'touchstart' : 'mousedown',
eMove = isTouch ? 'touchmove' : 'mousemove',
eEnd = isTouch ? 'touchend' : 'mouseup',
eCancel = isTouch ? 'touchcancel' : 'mouseup';
function Plugin ( element, options ) {
this.element = element;
this.settings = $.extend( {}, defaults, options );
this._defaults = defaults;
this._name = pluginName;
this.init();
}
$.extend( Plugin.prototype, {
init: function() {
var controlInnerElem = "",
timeInnerElem = "",
infoElem = "",
infoInnerElem = "",
fullPlayerElem = "",
volumeElem = "",
progressElem = "",
artworkElem = "",
timeElem = "",
controlElem = "",
titleElem = "",
artistElem = "",
backwardElem = "",
forwardElem = "",
stopElem = "",
playElem = "",
curTimeElem = "",
durTimeElem = "",
timeSeparator = "",
playerElem = "",
playerThis = this;
for( var elemItem in this.settings.elements )
{
//PREPARE VOLUME
if (this.settings.elements[elemItem] == "volume" ) {
volumeElem = "<div class='volume'><div class='volume-btn' title='Volume'></div><div class=' volume-adjust'><div><div></div></div></div></div>" ;
fullPlayerElem += volumeElem;
}
//PREPARE PROGRESS
else if (this.settings.elements[elemItem] == "progress" ) {
progressElem = "<div class='progressbar'><div class='bar-loaded' ></div><div class='bar-played'></div></div>";
fullPlayerElem += progressElem;
}
//PREPARE ARTWORK
else if (this.settings.elements[elemItem] == "artwork" ) {
artworkElem = "<div class='cover'></div>";
fullPlayerElem += artworkElem;
}
//PREPARE INFORMATION displayed by the player in the given order
else if (this.settings.elements[elemItem] == "information" ) {
$.inArray("title", this.settings.infoElements ) != '-1' ? titleElem = "<div class='title'></div>" : titleElem = " " ;
$.inArray("artist", this.settings.infoElements ) != '-1' ? artistElem = "<div class='artist'></div>" : artistElem = " " ;
for( var item in this.settings.infoElements ) {
if (this.settings.infoElements[item] == "title" ) { infoInnerElem += titleElem; }
else if ( this.settings.infoElements[item] == "artist" ) { infoInnerElem += artistElem ; }
}
infoElem = "<div class='info' >" + infoInnerElem + "</div>";
fullPlayerElem += infoElem;
}
//PREPARE TIME (current & Duration) in the given order
else if (this.settings.elements[elemItem] == "time" ) {
$.inArray("current", this.settings.timeElements) != '-1' ? curTimeElem = "<div class='time-current'></div>" : curTimeElem = " " ;
$.inArray("duration", this.settings.timeElements) != '-1' ? durTimeElem = "<div class='time-duration'></div>" : durTimeElem = " " ;
timeSeparator = "<div class='time-separator'>" + this.settings.timeSeparator.replace(/\s/g, ' ') + "</div>" ;
for( var item in this.settings.timeElements ) {
if( item == 1 ) { timeInnerElem += timeSeparator; }
if (this.settings.timeElements[item] == "current" ) { timeInnerElem += curTimeElem ; }
else if ( this.settings.timeElements[item] == "duration" ) { timeInnerElem += durTimeElem; }
}
timeElem = "<div class='timeHolder'>" + timeInnerElem + "</div>";
fullPlayerElem += timeElem;
}
//PREPARE CONTROLS inner elements to display [play, stop, forward or backward] in the given order
else if (this.settings.elements[elemItem] == "controls" ) {
$.inArray("backward", this.settings.controlElements) != '-1' ? backwardElem = "<div class='rew'></div>" : backwardElem = " " ;
$.inArray("forward", this.settings.controlElements) != '-1' ? forwardElem = "<div class='fwd'></div>" : forwardElem = " " ;
$.inArray("stop", this.settings.controlElements) != '-1' ? stopElem = "<div class='stop'></div>" : stopElem = " " ;
$.inArray("play", this.settings.controlElements) != '-1' ? playElem = "<div class='play'></div><div class='pause'></div>" : playElem = " " ;
for( var item in this.settings.controlElements ) {
if (this.settings.controlElements[item] == "backward" ) { controlInnerElem += backwardElem ; }
else if (this.settings.controlElements[item] == "play" ) { controlInnerElem += playElem; }
else if (this.settings.controlElements[item] == "forward" ) { controlInnerElem += forwardElem; }
else if (this.settings.controlElements[item] == "stop" ) { controlInnerElem += stopElem; }
}
controlElem = "<div class='controls'>" + controlInnerElem + "</div>";
fullPlayerElem += controlElem;
}
}
//ADD THE PREPARED ELEMENT SORTED IN THEIR RIGHT ORDER TO THE PLAYER ELEMENT
playerElem = $("<div class='player' >" + fullPlayerElem + "</div>");
//console.log(this.element);
if(this.settings.playerAbovePlaylist) {
$(playerElem).insertBefore($(this.element).find(".playlist"));
}else {
$(playerElem).insertAfter($(this.element).find(".playlist"));
}
this.playlistItemSelector = this.settings.playlistItemSelector;
this.playlistHolder = $(this.element).children(".playlist"),
this.playerHolder = $(this.element).children(".player");
this.song = "";
this.theBar = this.playerHolder.find('.progressbar');
this.barPlayed = this.playerHolder.find('.bar-played');
this.barLoaded = this.playerHolder.find('.bar-loaded' );
this.timeCurrent = this.playerHolder.find('.time-current');
this.timeDuration = this.playerHolder.find('.time-duration' );
this.timeSeparator = this.settings.timeSeparator;
this.volumeInfo = this.playerHolder.find('.volume');
this.volumeButton = this.playerHolder.find('.volume-btn');
this.volumeAdjuster = this.playerHolder.find('.volume-adjust' + ' > div' );
this.volumeValue = this.settings.volume / 100;
this.volumeDefault = 0;
this.trackInfo = this.playerHolder.find('.info');
//tracker = playerHolder.find('.progressbar'),
//volume = playerHolder.find('.volume'),
this.coverInfo = this.playerHolder.find('.cover');
this.controlsInfo = this.playerHolder.find('.controls');
this.controlPlay = $(this.controlsInfo).find('.play');
this.controlPause = $(this.controlsInfo).find('.pause');
this.controlStop = $(this.controlsInfo).find('.stop');
this.controlFwd = $(this.controlsInfo).find('.fwd');
this.controlRew = $(this.controlsInfo).find('.rew');
this.cssClass =
{
playing: 'playing',
mute: 'mute'
};
//Volume cannot be set using JavaScript, so the volumechange event will never be fired.
//Even if the user changes the volume on their device while mobile Safari is open, this event will not fire
//source: https://www.ibm.com/developerworks/library/wa-ioshtml5/
//Hide Volume control on IOS devices.
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) $(this.volumeInfo).hide();
// initialization - first element in playlist
this.initAudio( $(this.playlistHolder.children(this.playlistItemSelector + ":first-child") ) );
// set volume
this.song.volume = this.volumeValue;
//set default time Current and duration time
this.timeDuration.html( '…' );
this.timeCurrent.text( this.secondsToTime( 0 ) );
// play click
$(this.controlPlay).click(function (e) {
e.preventDefault();
playerThis.playAudio();
});
// pause click
$(this.controlPause).click(function (e) {
e.preventDefault();
playerThis.stopAudio();
//issue pause callback
playerThis.settings.onPause();
});
// forward click
$(this.controlFwd).click(function (e) {
e.preventDefault();
playerThis.stopAudio();
var next = $(playerThis.playlistHolder).find(playerThis.playlistItemSelector + '.active').next();
//Looping Activated : play the first item on the playlist if there is no next item with(looping)
if ( next.length == 0 ) {
next = $(playerThis.playlistHolder).find(playerThis.playlistItemSelector + ':first-child');
}
playerThis.loadNewSong(next);
playerThis.playAudio();
//issue forward callback
playerThis.settings.onFwd();
});
// rewind click
$(this.controlRew).click(function (e) {
e.preventDefault();
playerThis.stopAudio();
var prev = $(playerThis.playlistHolder).find(playerThis.playlistItemSelector + '.active').prev();
//play the last item on the playlist if there is no previous item (looping)
if (prev.length == 0 ) {
prev = $(playerThis.playlistHolder).find(playerThis.playlistItemSelector + ':last-child');
}
playerThis.loadNewSong(prev);
playerThis.playAudio();
//issue backward callback
playerThis.settings.onRew();
});
//stop click
$(this.controlStop).click(function (e) {
e.preventDefault();
playerThis.stopAudio();
playerThis.song.currentTime = 0;
//issue stop callback
playerThis.settings.onStop();
});
// Play clicked Playlist song.
$(this.playlistHolder).find(this.playlistItemSelector).click(function (e) {
e.preventDefault();
playerThis.stopAudio();
playerThis.loadNewSong($(this));
playerThis.playAudio();
//issue track clicked callback
playerThis.settings.trackClicked();
})
},
secondsToTime: function( secs )
{
var hours = Math.floor( secs / 3600 ),
minutes = Math.floor( secs % 3600 / 60 ),
seconds = Math.ceil( secs % 3600 % 60 );
return ( hours == 0 ? '' : hours > 0 && hours.toString().length < 2 ? '0'+hours+':' : hours+':' ) + ( minutes.toString().length < 2 ? '0'+minutes : minutes ) + ':' + ( seconds.toString().length < 2 ? '0'+seconds : seconds );
},
adjustVolume: function( e )
{
// volElemClicked = e.toElement.parentElement;
// volElemClicked = $(volElemClicked).parent().parent().parent().parent();
// console.log(e);
var theRealEvent = isTouch ? e.originalEvent.touches[ 0 ] : e;
this.song.volume = Math.abs( ( theRealEvent.pageX - ( this.volumeAdjuster.offset().left ) ) / this.volumeAdjuster.width() );
},
adjustCurrentTime: function( e )
{
var theRealEvent = isTouch ? e.originalEvent.touches[ 0 ] : e;
this.song.currentTime = Math.round( ( this.song.duration * ( theRealEvent.pageX - this.theBar.offset().left ) ) / this.theBar.width() );
},
initAudio: function(elem)
{
var url = elem.children("a:first-child").attr("href"),
title = elem.text(),
cover = elem.attr('data-cover'),
artist = elem.attr('data-artist'),
playerInstance = this;
//Set the title of the song on the player
$(this.trackInfo).children('.title').text(title);
//Set the artist name on the player
$(this.trackInfo).children('.artist').text(artist);
//Set the cover image for the player
$(this.coverInfo).css('background-image','url('+ cover +')');
this.song = new Audio(url);
//Force load
this.song.load();
//set the song time duration on player
this.song.addEventListener('loadeddata', function()
{
$(playerInstance.timeDuration).html(playerInstance.secondsToTime(this.duration) );
$(playerInstance.volumeAdjuster).find( 'div' ).width( this.volume * 100 + '%' );
playerInstance.volumeDefault = this.volume;
}, false);
//update bar loader
this.song.addEventListener('progress', function()
{
$(playerInstance.barLoaded).width( ( this.buffered.end( 0 ) / this.duration ) * 100 + '%' );
});
//timeupdate event listener (timeupdate used together with the current Time Property to return
// the current position of the audio playback in seconds)
this.song.addEventListener('timeupdate',function ()
{
$(playerInstance.timeCurrent).text( playerInstance.secondsToTime(this.currentTime ) );
$(playerInstance.barPlayed).width( (this.currentTime / this.duration ) * 100 + '%' );
});
this.song.addEventListener('volumechange', function()
{
if(Number(Math.round(this.volume * 100+'e'+1)+'e-'+1) <= 0.4 ){ this.volume = 0; }
$(playerInstance.volumeAdjuster).find( 'div' ).width( this.volume * 100 + '%' );
if( this.volume > 0 && playerInstance.playerHolder.hasClass( playerInstance.cssClass.mute ) ) playerInstance.playerHolder.removeClass( playerInstance.cssClass.mute );
if( this.volume <= 0 && !playerInstance.playerHolder.hasClass( playerInstance.cssClass.mute ) ) playerInstance.playerHolder.addClass( playerInstance.cssClass.mute );
playerInstance.volumeValue = this.volume;
});
this.song.addEventListener('ended', function()
{
//Play the loaded song when autoplay is activated
//$('.fwd').click();
if (playerInstance.settings.autoPlay){ playerInstance.autoPlayNext(); }
else {
//Hide playing class
playerInstance.playerHolder.removeClass( playerInstance.cssClass.playing );
//Hide pause Icon and show play
$(playerInstance.controlPlay).removeClass('hidden');
$(playerInstance.controlPause).removeClass('visible');
}
});
//Toggle Mute icon and reset Volume
$(this.volumeButton).on('click', function()
{
if( $(playerInstance.playerHolder).hasClass( playerInstance.cssClass.mute ) )
{
$(playerInstance.playerHolder).removeClass( playerInstance.cssClass.mute );
playerInstance.song.volume = playerInstance.volumeDefault;
}
else
{
$(playerInstance.playerHolder).addClass( playerInstance.cssClass.mute );
playerInstance.volumeDefault = playerInstance.song.volume;
playerInstance.song.volume = 0;
//issue callback to track mute action.
playerInstance.settings.onMute();
}
return false;
});
//when volume bar is clicked
$(this.volumeAdjuster).on( eStart, function( e )
{
playerInstance.adjustVolume( e );
playerInstance.volumeAdjuster.on( eMove, function( e ) { playerInstance.adjustVolume( e ); } );
//issue callback
playerInstance.settings.volumeChanged();
})
.on( eCancel, function()
{
playerInstance.volumeAdjuster.unbind( eMove );
});
//when trackbar is click
$(this.theBar).on( eStart, function( e )
{
playerInstance.adjustCurrentTime( e );
playerInstance.theBar.on( eMove, function( e ) { playerInstance.adjustCurrentTime( e ); } );
})
.on( eCancel, function()
{
playerInstance.theBar.unbind( eMove );
//issue callback
playerInstance.settings.seeked();
});
//Make active the loaded Song playing
$(this.playlistHolder).children(playerInstance.playlistItemSelector).removeClass('active');
elem.addClass('active');
//issue Callback
this.settings.onLoad();
//Play the loaded song when autoplay is activated
if (this.settings.autoPlay) this.playAudio();
},
playAudio: function()
{
this.song.play();
//Add playing class
this.playerHolder.addClass(this.cssClass.playing);
//Hide pause Icon and show play if they exist
if ( $.inArray("controls", this.settings.elements ) != '-1' && $.inArray("play", this.settings.controlElements ) != '-1' ) {
$(this.controlPlay).addClass('hidden');
$(this.controlPause).addClass('visible');
}
this.settings.onPlay();
},
stopAudio: function()
{
this.song.pause();
//Remove playing class
this.playerHolder.removeClass(this.cssClass.playing);
//Hide pause Icon and show play if they exist
if ( $.inArray("controls", this.settings.elements ) != '-1' && $.inArray("play", this.settings.controlElements ) != '-1' ) {
$(this.controlPlay).removeClass('hidden');
$(this.controlPause).removeClass('visible');
}
},
// Auto Play the next track and loop if lopp is activated
autoPlayNext: function()
{
this.stopAudio();
var next = $(this.playlistHolder).children(this.playlistItemSelector + '.active').next();
//Looping Activated : play the first item on the playlist if there is no next item with(looping)
if ( next.length == 0 && this.settings.loop ) {
next = $(this.playlistHolder).children(this.playlistItemSelector + ':first-child')
this.loadNewSong(next);
this.playAudio();
}
else if ( !next.length == 0 ) {
this.loadNewSong(next);
this.playAudio();
}
},
//initiate the give song maintaining current settings
loadNewSong: function(elem)
{
//save current volume level
this.volumeValue = this.song.volume;
//set up the next song to be played
this.initAudio(elem);
//set song volume to the previous track's volume to ensure consistency
this.song.volume = this.volumeValue;
this.volumeAdjuster.find( 'div' ).width( this.volumeValue * 100 + '%' );
//reset progress & loaded bar indicator to begin
this.barPlayed.width(0);
this.barLoaded.width(0);
}
});
$.fn[ pluginName ] = function( options ) {
return this.each( function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" +
pluginName, new Plugin( this, options ) );
}
} );
};
} )( jQuery, window, document );