.009 now playing support, added back button to track list

This commit is contained in:
Trevor Squillario 2011-09-30 22:55:08 -04:00
parent 53519a8375
commit 724443fd36
6 changed files with 872 additions and 29 deletions

10
README
View file

@ -7,3 +7,13 @@ External Subsonic Music Player
8/24/2011 .005 playlist fixes, added auto playlists 8/24/2011 .005 playlist fixes, added auto playlists
8/25/2011 .006 flexible layout, added buttons to player 8/25/2011 .006 flexible layout, added buttons to player
9/17/2011 .007 display tweaks for tablet, chat feature added 9/17/2011 .007 display tweaks for tablet, chat feature added
9/17/2011 .008 pause/play button tweak
9/30/2011 .009 now playing support, added back button to track list
TO DO: (In no particular order...)
- Jukebox Control
- Download Links
- What Other Are Listening To support
- Ratings
- Chrome App???
- Add notification when trial license has expired

BIN
images/rss_12x12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

View file

@ -242,6 +242,7 @@
} else { } else {
//loadChatMessages(); //loadChatMessages();
updateChatMessages(); updateChatMessages();
$('div#submenu_NowPlaying').fadeOut();
submenu.fadeIn(400); submenu.fadeIn(400);
} }
$('input#ChatMsg').focus(); $('input#ChatMsg').focus();
@ -260,6 +261,18 @@
$('#action_AddChatMsg').click(); $('#action_AddChatMsg').click();
} }
}); });
$('a#NowPlaying').click(function () {
var submenu = $('div#submenu_NowPlaying');
if (submenu.is(":visible")) {
submenu.fadeOut();
updaterNowPlaying.cancel();
} else {
//loadChatMessages();
updateNowPlaying();
$('div#submenu_Chat').fadeOut();
submenu.fadeIn(400);
}
});
// Preferences Click Events // Preferences Click Events
$('#SavePreferences').live('click', function () { $('#SavePreferences').live('click', function () {
@ -285,7 +298,7 @@
$.cookie('css', style, { expires: 365, path: '/' }); $.cookie('css', style, { expires: 365, path: '/' });
location.reload(true); location.reload(true);
}); });
}); // End document.ready }); // End document.ready
$(window).load(function () { $(window).load(function () {
// Set Default Height // Set Default Height
@ -306,7 +319,7 @@
resizeContent(); resizeContent();
} }
function resizeContent() { function resizeContent() {
var smwidth = $('.smsection').width() + 52; var smwidth = $('.smsection').width() + 54;
var lgwidth = $(window).width() - smwidth; var lgwidth = $(window).width() - smwidth;
if ((smwidth + lgwidth) >= 810) { if ((smwidth + lgwidth) >= 810) {
$('.lgsection').css({ 'width': (lgwidth) + 'px' }); $('.lgsection').css({ 'width': (lgwidth) + 'px' });
@ -322,7 +335,15 @@
trackEnded: function () { trackEnded: function () {
var next = $('ul.songlist li.playing').next(); var next = $('ul.songlist li.playing').next();
changeTrack(next); changeTrack(next);
} },
useFlash: (function() {
var a = document.createElement('audio'),
userAgent = navigator.userAgent.toLowerCase(),
version = parseFloat((userAgent.match( /.+(?:rv|it|ra|ie|me)[\/: ]([\d.]+)/ ) || [])[1]);
if (/chrome/.test(userAgent) && version < 10) return false;
return !(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
})()
}); });
// Load in the first track // Load in the first track
audio = a[0]; audio = a[0];
@ -439,13 +460,17 @@
<div class="floatleft"><audio src="" preload="none" /></div> <div class="floatleft"><audio src="" preload="none" /></div>
</div> </div>
</div> </div>
<div id="submenu_Chat" class="submenu positionabove shadow" style="display: none;"> <div id="submenu_Chat" class="submenu shadow" style="display: none;">
<div id="ChatMsgs"></div> <div id="ChatMsgs"></div>
<input type="text" id="ChatMsg" class="medium" /><a href="#" class="button" id="action_AddChatMsg" title="Add Chat Message"><img src="images/comment_stroke_12x11.png" /></a> <input type="text" id="ChatMsg" class="medium" /><a href="#" class="button" id="action_AddChatMsg" title="Add Chat Message"><img src="images/comment_stroke_12x11.png" /></a>
</div> </div>
<div id="submenu_NowPlaying" class="submenu shadow" style="display: none;">
<div id="NowPlayingList"><span class="user">Loading...</span></div>
</div>
<div class="playeractionssmall floatleft"> <div class="playeractionssmall floatleft">
<a href="#" class="button" id="Shuffle" title="Shuffle"><img src="images/fork_11x12.png" /></a> <a href="#" class="button" id="Shuffle" title="Shuffle"><img src="images/fork_11x12.png" /></a>
<a href="#" class="button" id="Chat" title="Chat"><img src="images/chat_alt_stroke_12x12.png" /> Chat</a> <a href="#" class="button" id="Chat" title="Chat"><img src="images/chat_alt_stroke_12x12.png" /> Chat</a>
<a href="#" class="button" id="NowPlaying" title="Now Playing"><img src="images/rss_12x12.png" /> Now Playing</a>
<span id="Notifications"></span> <span id="Notifications"></span>
</div> </div>
<div class="clear"></div> <div class="clear"></div>

View file

@ -52,7 +52,7 @@ function loadArtists(refresh) {
if (content == "") { if (content == "") {
// Load Artist List // Load Artist List
$.ajax({ $.ajax({
url: baseURL + '/getIndexes.view?v=1.5.0&c=subweb&f=json', url: baseURL + '/getIndexes.view?v=1.6.0&c=subweb&f=json',
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -79,9 +79,9 @@ function loadArtists(refresh) {
}); });
} }
} }
function getAlbums(id) { function getAlbums(id, albumid) {
$.ajax({ $.ajax({
url: baseURL + '/getMusicDirectory.view?v=1.5.0&c=subweb&f=json&id=' + id, url: baseURL + '/getMusicDirectory.view?v=1.6.0&c=subweb&f=json&id=' + id,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -109,17 +109,22 @@ function getAlbums(id) {
if (child.isDir == true) { if (child.isDir == true) {
albumhtml = '<li class=\"album ' + rowcolor + '\">'; albumhtml = '<li class=\"album ' + rowcolor + '\">';
albumhtml += '<div class=\"albumart\"><img class=\"floatleft\" src=\"' + baseURL + '/getCoverArt.view?v=1.5.0&c=subweb&f=json&size=50&id=' + child.coverArt + '\" /></div>'; albumhtml += '<div class=\"albumart\"><img class=\"floatleft\" src=\"' + baseURL + '/getCoverArt.view?v=1.6.0&c=subweb&f=json&size=50&id=' + child.coverArt + '\" /></div>';
albumhtml += '<a href=\"#\" onclick=\"javascript:getAlbums(\'' + child.id + '\'); return false;\">' + child.title + '</a>'; albumhtml += '<a href=\"#\" onclick=\"javascript:getAlbums(\'' + child.id + '\', \'' + child.parent + '\'); return false;\">' + child.title + '</a>';
albumhtml += '</li>'; albumhtml += '</li>';
$(albumhtml).appendTo("#AlbumContainer"); $(albumhtml).appendTo("#AlbumContainer");
} else { } else {
var track; var track;
if (child.track === undefined) { track = "&nbsp;"; } else { track = child.track; } if (child.track === undefined) { track = "&nbsp;"; } else { track = child.track; }
if (i == 0) {
var backhtml = '<li class=\"back\"><a href=\"#\" onclick=\"javascript:getAlbums(\'' + albumid + '\'); return false;\">&laquo; Back</a></li>';
$(backhtml).appendTo("#AlbumContainer");
}
var time = secondsToTime(child.duration); var time = secondsToTime(child.duration);
albumhtml = '<li class=\"song ' + rowcolor + '\" childid=\"' + child.id + '\" parentid=\"' + child.parent + '\">'; albumhtml = '<li class=\"song ' + rowcolor + '\" childid=\"' + child.id + '\" parentid=\"' + child.parent + '\">';
albumhtml += '<span class=\"track\">' + track + '</span> '; albumhtml += '<span class=\"track\">' + track + '</span> ';
albumhtml += child.title; albumhtml += '<span class=\"title\">' + child.title + '</span> ';
albumhtml += '<span class=\"album\">' + child.album + '</span> ';
albumhtml += ' <small>' + time['m'] + ':' + time['s'] + '</small>'; albumhtml += ' <small>' + time['m'] + ':' + time['s'] + '</small>';
albumhtml += '</li>'; albumhtml += '</li>';
$(albumhtml).appendTo("#AlbumContainer"); $(albumhtml).appendTo("#AlbumContainer");
@ -131,7 +136,7 @@ function getAlbums(id) {
} }
function playSong(action, el, songid, albumid) { function playSong(action, el, songid, albumid) {
$.ajax({ $.ajax({
url: baseURL + '/getMusicDirectory.view?v=1.5.0&c=subweb&f=json&id=' + albumid, url: baseURL + '/getMusicDirectory.view?v=1.6.0&c=subweb&f=json&id=' + albumid,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -154,9 +159,9 @@ function playSong(action, el, songid, albumid) {
$('#songdetails_song').attr('parentid', albumid); $('#songdetails_song').attr('parentid', albumid);
$('#songdetails_song').attr('childid', songid); $('#songdetails_song').attr('childid', songid);
$('#songdetails_artist').html(artist + ' - ' + album); $('#songdetails_artist').html(artist + ' - ' + album);
$('#coverartimage').attr('src', baseURL + '/getCoverArt.view?v=1.5.0&c=subweb&f=json&size=60&id=' + songid); $('#coverartimage').attr('src', baseURL + '/getCoverArt.view?v=1.6.0&c=subweb&f=json&size=60&id=' + songid);
if (action != 'selected') { if (action != 'selected') {
audio.load(baseURL + '/stream.view?v=1.5.0&c=subweb&f=json&id=' + songid); audio.load(baseURL + '/stream.view?v=1.6.0&c=subweb&f=json&id=' + songid);
audio.play(); audio.play();
$('ul.songlist li.song').removeClass('playing'); $('ul.songlist li.song').removeClass('playing');
$(el).addClass('playing'); $(el).addClass('playing');
@ -205,7 +210,7 @@ function changeTrack(next) {
function search(type, query) { function search(type, query) {
$.ajax({ $.ajax({
url: baseURL + '/search2.view?v=1.5.0&c=subweb&f=json&query=' + query, url: baseURL + '/search2.view?v=1.6.0&c=subweb&f=json&query=' + query,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -246,7 +251,7 @@ function search(type, query) {
var starttime; var starttime;
function loadChatMessages() { function loadChatMessages() {
$.ajax({ $.ajax({
url: baseURL + '/getChatMessages.view?v=1.5.0&c=subweb&f=json', url: baseURL + '/getChatMessages.view?v=1.6.0&c=subweb&f=json',
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -276,7 +281,7 @@ function updateChatMessages() {
updater = $.periodic({ period: 2000, decay: 1.5, max_period: 1800000 }, function () { updater = $.periodic({ period: 2000, decay: 1.5, max_period: 1800000 }, function () {
$.ajax({ $.ajax({
periodic: this, periodic: this,
url: baseURL + '/getChatMessages.view?v=1.5.0&c=subweb&f=json&since=' + starttime, url: baseURL + '/getChatMessages.view?v=1.6.0&c=subweb&f=json&since=' + starttime,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -327,6 +332,54 @@ function addChatMessage(msg) {
traditional: true // Fixes POST with an array in JQuery 1.4 traditional: true // Fixes POST with an array in JQuery 1.4
}); });
} }
var updaterNowPlaying;
var updaterNowPlayingData;
function updateNowPlaying() {
updaterNowPlaying = $.periodic({ period: 2000, decay: 1.5, max_period: 1800000 }, function () {
$.ajax({
periodic: this,
url: baseURL + '/getNowPlaying.view?v=1.6.0&c=subweb&f=json',
method: 'GET',
dataType: 'json',
beforeSend: function (req) {
req.setRequestHeader('Authorization', auth);
},
success: function (data) {
if (data["subsonic-response"].nowPlaying.entry === undefined) {
this.periodic.increment();
$("#NowPlayingList").empty();
var chathtml = '<div class=\"msg\">';
chathtml += '<span class=\"user\">Nothing :(</span></br>';
chathtml += '</div>';
$(chathtml).appendTo("#NowPlayingList");
} else if (updaterNowPlayingData == $.param(data)) {
this.periodic.increment();
} else {
$("#NowPlayingList").empty();
var msgs = [];
if (data["subsonic-response"].nowPlaying.entry.length > 0) {
msgs = data["subsonic-response"].nowPlaying.entry;
} else {
msgs[0] = data["subsonic-response"].nowPlaying.entry;
}
this.periodic.reset();
var sorted = msgs.sort(function (a, b) {
return a.minutesAgo - b.minutesAgo;
});
$.each(sorted, function (i, msg) {
var chathtml = '<div class=\"msg\">';
chathtml += '<span class=\"user\">' + msg.username + '</span></br>';
chathtml += '<span class=\"artist\">' + msg.artist + '</span> - ';
chathtml += '<span class=\"title\">' + msg.title + '</span>';
chathtml += '</div>';
$(chathtml).appendTo("#NowPlayingList");
});
updaterNowPlayingData = $.param(data);
}
}
});
});
}
function loadPlaylists(refresh) { function loadPlaylists(refresh) {
if (refresh) { if (refresh) {
@ -336,7 +389,7 @@ function loadPlaylists(refresh) {
if (content == "") { if (content == "") {
// Load Playlists // Load Playlists
$.ajax({ $.ajax({
url: baseURL + '/getPlaylists.view?v=1.5.0&c=subweb&f=json', url: baseURL + '/getPlaylists.view?v=1.6.0&c=subweb&f=json',
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -357,7 +410,7 @@ function loadPlaylists(refresh) {
function loadPlaylistsForMenu() { function loadPlaylistsForMenu() {
$('#submenu_AddToPlaylist').empty(); $('#submenu_AddToPlaylist').empty();
$.ajax({ $.ajax({
url: baseURL + '/getPlaylists.view?v=1.5.0&c=subweb&f=json', url: baseURL + '/getPlaylists.view?v=1.6.0&c=subweb&f=json',
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -375,7 +428,7 @@ function newPlaylist() {
var reply = prompt("Choose a name for your new playlist.", ""); var reply = prompt("Choose a name for your new playlist.", "");
if (reply != "") { if (reply != "") {
$.ajax({ $.ajax({
url: baseURL + '/createPlaylist.view?v=1.5.0&c=subweb&f=json&name=' + reply, url: baseURL + '/createPlaylist.view?v=1.6.0&c=subweb&f=json&name=' + reply,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -389,7 +442,7 @@ function newPlaylist() {
} }
function deletePlaylist(id) { function deletePlaylist(id) {
$.ajax({ $.ajax({
url: baseURL + '/deletePlaylist.view?v=1.5.0&c=subweb&f=json&id=' + id, url: baseURL + '/deletePlaylist.view?v=1.6.0&c=subweb&f=json&id=' + id,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -411,7 +464,7 @@ function addToPlaylist(playlistid) {
// Get songs from playlist // Get songs from playlist
var currentsongs = []; var currentsongs = [];
$.ajax({ $.ajax({
url: baseURL + '/getPlaylist.view?v=1.5.0&c=subweb&f=json&id=' + playlistid, url: baseURL + '/getPlaylist.view?v=1.6.0&c=subweb&f=json&id=' + playlistid,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -499,7 +552,7 @@ function savePlaylist(playlistid) {
} }
function getPlaylist(id) { function getPlaylist(id) {
$.ajax({ $.ajax({
url: baseURL + '/getPlaylist.view?v=1.5.0&c=subweb&f=json&id=' + id, url: baseURL + '/getPlaylist.view?v=1.6.0&c=subweb&f=json&id=' + id,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -542,7 +595,7 @@ function getPlaylist(id) {
} }
function getAlbumListBy(id) { function getAlbumListBy(id) {
$.ajax({ $.ajax({
url: baseURL + '/getAlbumList.view?v=1.5.0&c=subweb&f=json&type=' + id, url: baseURL + '/getAlbumList.view?v=1.6.0&c=subweb&f=json&type=' + id,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {
@ -563,7 +616,7 @@ function getAlbumListBy(id) {
var html; var html;
$.each(albums, function (i, album) { $.each(albums, function (i, album) {
$.ajax({ $.ajax({
url: baseURL + '/getMusicDirectory.view?v=1.5.0&c=subweb&f=json&size=5&id=' + album.id, url: baseURL + '/getMusicDirectory.view?v=1.6.0&c=subweb&f=json&size=5&id=' + album.id,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
beforeSend: function (req) { beforeSend: function (req) {

702
js/audiojs/audio.js Normal file
View file

@ -0,0 +1,702 @@
// A cross-browser javascript shim for html5 audio
(function(audiojs, audiojsInstance, container) {
// Use the path to the audio.js file to create relative paths to the swf and player graphics
// Remember that some systems (e.g. ruby on rails) append strings like '?1301478336' to asset paths
var path = (function() {
var re = new RegExp('audio(\.min)?\.js.*'),
scripts = document.getElementsByTagName('script');
for (var i = 0, ii = scripts.length; i < ii; i++) {
var path = scripts[i].getAttribute('src');
if(re.test(path)) return path.replace(re, '');
}
})();
// ##The audiojs interface
// This is the global object which provides an interface for creating new `audiojs` instances.
// It also stores all of the construction helper methods and variables.
container[audiojs] = {
instanceCount: 0,
instances: {},
// The markup for the swf. It is injected into the page if there is not support for the `<audio>` element. The `$n`s are placeholders.
// `$1` The name of the flash movie
// `$2` The path to the swf
// `$3` Cache invalidation
flashSource: '\
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="$1" width="1" height="1" name="$1" style="position: absolute; left: -1px;"> \
<param name="movie" value="$2?playerInstance='+audiojs+'.instances[\'$1\']&datetime=$3"> \
<param name="allowscriptaccess" value="always"> \
<embed name="$1" src="$2?playerInstance='+audiojs+'.instances[\'$1\']&datetime=$3" width="1" height="1" allowscriptaccess="always"> \
</object>',
// ### The main settings object
// Where all the default settings are stored. Each of these variables and methods can be overwritten by the user-provided `options` object.
settings: {
autoplay: false,
loop: false,
preload: true,
imageLocation: path + 'player-graphics.gif',
swfLocation: path + 'audiojs.swf',
useFlash: (function() {
var a = document.createElement('audio');
return !(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
})(),
hasFlash: (function() {
if (navigator.plugins && navigator.plugins.length && navigator.plugins['Shockwave Flash']) {
return true;
} else if (navigator.mimeTypes && navigator.mimeTypes.length) {
var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
return mimeType && mimeType.enabledPlugin;
} else {
try {
var ax = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
return true;
} catch (e) {}
}
return false;
})(),
// The default markup and classes for creating the player:
createPlayer: {
markup: '\
<div class="play-pause"> \
<p class="play"></p> \
<p class="pause"></p> \
<p class="loading"></p> \
<p class="error"></p> \
</div> \
<div class="scrubber"> \
<div class="progress"></div> \
<div class="loaded"></div> \
</div> \
<div class="time"> \
<em class="played">00:00</em>/<strong class="duration">00:00</strong> \
</div> \
<div class="error-message"></div>',
playPauseClass: 'play-pause',
scrubberClass: 'scrubber',
progressClass: 'progress',
loaderClass: 'loaded',
timeClass: 'time',
durationClass: 'duration',
playedClass: 'played',
errorMessageClass: 'error-message',
playingClass: 'playing',
loadingClass: 'loading',
errorClass: 'error'
},
// The css used by the default player. This is is dynamically injected into a `<style>` tag in the top of the head.
css: '\
.audiojs audio { position: absolute; left: -1px; } \
.audiojs { width: 460px; height: 36px; background: #404040; overflow: hidden; font-family: monospace; font-size: 12px; \
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444), color-stop(0.5, #555), color-stop(0.51, #444), color-stop(1, #444)); \
background-image: -moz-linear-gradient(center top, #444 0%, #555 50%, #444 51%, #444 100%); \
-webkit-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); -moz-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); \
-o-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); } \
.audiojs .play-pause { width: 25px; height: 40px; padding: 4px 6px; margin: 0px; float: left; overflow: hidden; border-right: 1px solid #000; } \
.audiojs p { display: none; width: 25px; height: 40px; margin: 0px; cursor: pointer; } \
.audiojs .play { display: block; } \
.audiojs .scrubber { position: relative; float: left; width: 280px; background: #5a5a5a; height: 14px; margin: 10px; border-top: 1px solid #3f3f3f; border-left: 0px; border-bottom: 0px; overflow: hidden; } \
.audiojs .progress { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #ccc; z-index: 1; \
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ccc), color-stop(0.5, #ddd), color-stop(0.51, #ccc), color-stop(1, #ccc)); \
background-image: -moz-linear-gradient(center top, #ccc 0%, #ddd 50%, #ccc 51%, #ccc 100%); } \
.audiojs .loaded { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #000; \
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #222), color-stop(0.5, #333), color-stop(0.51, #222), color-stop(1, #222)); \
background-image: -moz-linear-gradient(center top, #222 0%, #333 50%, #222 51%, #222 100%); } \
.audiojs .time { float: left; height: 36px; line-height: 36px; margin: 0px 0px 0px 6px; padding: 0px 6px 0px 12px; border-left: 1px solid #000; color: #ddd; text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.5); } \
.audiojs .time em { padding: 0px 2px 0px 0px; color: #f9f9f9; font-style: normal; } \
.audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; } \
.audiojs .error-message { float: left; display: none; margin: 0px 10px; height: 36px; width: 400px; overflow: hidden; line-height: 36px; white-space: nowrap; color: #fff; \
text-overflow: ellipsis; -o-text-overflow: ellipsis; -icab-text-overflow: ellipsis; -khtml-text-overflow: ellipsis; -moz-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; } \
.audiojs .error-message a { color: #eee; text-decoration: none; padding-bottom: 1px; border-bottom: 1px solid #999; white-space: wrap; } \
\
.audiojs .play { background: url("$1") -2px -1px no-repeat; } \
.audiojs .loading { background: url("$1") -2px -31px no-repeat; } \
.audiojs .error { background: url("$1") -2px -61px no-repeat; } \
.audiojs .pause { background: url("$1") -2px -91px no-repeat; } \
\
.playing .play, .playing .loading, .playing .error { display: none; } \
.playing .pause { display: block; } \
\
.loading .play, .loading .pause, .loading .error { display: none; } \
.loading .loading { display: block; } \
\
.error .time, .error .play, .error .pause, .error .scrubber, .error .loading { display: none; } \
.error .error { display: block; } \
.error .play-pause p { cursor: auto; } \
.error .error-message { display: block; }',
// The default event callbacks:
trackEnded: function(e) {},
flashError: function() {
var player = this.settings.createPlayer,
errorMessage = getByClass(player.errorMessageClass, this.wrapper),
html = 'Missing <a href="http://get.adobe.com/flashplayer/">flash player</a> plugin.';
if (this.mp3) html += ' <a href="'+this.mp3+'">Download audio file</a>.';
container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
container[audiojs].helpers.addClass(this.wrapper, player.errorClass);
errorMessage.innerHTML = html;
},
loadError: function(e) {
var player = this.settings.createPlayer,
errorMessage = getByClass(player.errorMessageClass, this.wrapper);
container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
container[audiojs].helpers.addClass(this.wrapper, player.errorClass);
errorMessage.innerHTML = 'Error loading: "'+this.mp3+'"';
},
init: function() {
var player = this.settings.createPlayer;
container[audiojs].helpers.addClass(this.wrapper, player.loadingClass);
},
loadStarted: function() {
var player = this.settings.createPlayer,
duration = getByClass(player.durationClass, this.wrapper),
m = Math.floor(this.duration / 60),
s = Math.floor(this.duration % 60);
container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
duration.innerHTML = ((m<10?'0':'')+m+':'+(s<10?'0':'')+s);
},
loadProgress: function(percent) {
var player = this.settings.createPlayer,
scrubber = getByClass(player.scrubberClass, this.wrapper),
loaded = getByClass(player.loaderClass, this.wrapper);
loaded.style.width = (scrubber.offsetWidth * percent) + 'px';
},
playPause: function() {
if (this.playing) this.settings.play();
else this.settings.pause();
},
play: function() {
var player = this.settings.createPlayer;
container[audiojs].helpers.addClass(this.wrapper, player.playingClass);
},
pause: function() {
var player = this.settings.createPlayer;
container[audiojs].helpers.removeClass(this.wrapper, player.playingClass);
},
updatePlayhead: function(percent) {
var player = this.settings.createPlayer,
scrubber = getByClass(player.scrubberClass, this.wrapper),
progress = getByClass(player.progressClass, this.wrapper);
progress.style.width = (scrubber.offsetWidth * percent) + 'px';
var played = getByClass(player.playedClass, this.wrapper),
p = this.duration * percent,
m = Math.floor(p / 60),
s = Math.floor(p % 60);
played.innerHTML = ((m<10?'0':'')+m+':'+(s<10?'0':'')+s);
}
},
// ### Contructor functions
// `create()`
// Used to create a single `audiojs` instance.
// If an array is passed then it calls back to `createAll()`.
// Otherwise, it creates a single instance and returns it.
create: function(element, options) {
var options = options || {}
if (element.length) {
return this.createAll(options, element);
} else {
return this.newInstance(element, options);
}
},
// `createAll()`
// Creates multiple `audiojs` instances.
// If `elements` is `null`, then automatically find any `<audio>` tags on the page and create `audiojs` instances for them.
createAll: function(options, elements) {
var audioElements = elements || document.getElementsByTagName('audio'),
instances = []
options = options || {};
for (var i = 0, ii = audioElements.length; i < ii; i++) {
instances.push(this.newInstance(audioElements[i], options));
}
return instances;
},
// ### Creating and returning a new instance
// This goes through all the steps required to build out a usable `audiojs` instance.
newInstance: function(element, options) {
var element = element,
s = this.helpers.clone(this.settings),
id = 'audiojs'+this.instanceCount,
wrapperId = 'audiojs_wrapper'+this.instanceCount,
instanceCount = this.instanceCount++;
// Check for `autoplay`, `loop` and `preload` attributes and write them into the settings.
if (element.getAttribute('autoplay') != null) s.autoplay = true;
if (element.getAttribute('loop') != null) s.loop = true;
if (element.getAttribute('preload') == 'none') s.preload = false;
// Merge the default settings with the user-defined `options`.
if (options) this.helpers.merge(s, options);
// Inject the player html if required.
if (s.createPlayer.markup) element = this.createPlayer(element, s.createPlayer, wrapperId);
else element.parentNode.setAttribute('id', wrapperId);
// Return a new `audiojs` instance.
var audio = new container[audiojsInstance](element, s);
// If css has been passed in, dynamically inject it into the `<head>`.
if (s.css) this.helpers.injectCss(audio, s.css);
// If `<audio>` or mp3 playback isn't supported, insert the swf & attach the required events for it.
if (s.useFlash && s.hasFlash) {
this.injectFlash(audio, id);
this.attachFlashEvents(audio.wrapper, audio);
} else if (s.useFlash && !s.hasFlash) {
this.settings.flashError.apply(audio);
}
// Attach event callbacks to the new audiojs instance.
if (!s.useFlash || (s.useFlash && s.hasFlash)) this.attachEvents(audio.wrapper, audio);
// Store the newly-created `audiojs` instance.
this.instances[id] = audio;
return audio;
},
// ### Helper methods for constructing a working player
// Inject a wrapping div and the markup for the html player.
createPlayer: function(element, player, id) {
var wrapper = document.createElement('div'),
newElement = element.cloneNode(true);
wrapper.setAttribute('class', 'audiojs');
wrapper.setAttribute('className', 'audiojs');
wrapper.setAttribute('id', id);
// Fix IE's broken implementation of `innerHTML` & `cloneNode` for HTML5 elements.
if (newElement.outerHTML && !document.createElement('audio').canPlayType) {
newElement = this.helpers.cloneHtml5Node(element);
wrapper.innerHTML = player.markup;
wrapper.appendChild(newElement);
element.outerHTML = wrapper.outerHTML;
wrapper = document.getElementById(id);
} else {
wrapper.appendChild(newElement);
wrapper.innerHTML = wrapper.innerHTML + player.markup;
element.parentNode.replaceChild(wrapper, element);
}
return wrapper.getElementsByTagName('audio')[0];
},
// Attaches useful event callbacks to an `audiojs` instance.
attachEvents: function(wrapper, audio) {
if (!audio.settings.createPlayer) return;
var player = audio.settings.createPlayer,
playPause = getByClass(player.playPauseClass, wrapper),
scrubber = getByClass(player.scrubberClass, wrapper),
leftPos = function(elem) {
var curleft = 0;
if (elem.offsetParent) {
do { curleft += elem.offsetLeft; } while (elem = elem.offsetParent);
}
return curleft;
};
container[audiojs].events.addListener(playPause, 'click', function(e) {
audio.playPause.apply(audio);
});
container[audiojs].events.addListener(scrubber, 'click', function(e) {
var relativeLeft = e.clientX - leftPos(this);
audio.skipTo(relativeLeft / scrubber.offsetWidth);
});
// _If flash is being used, then the following handlers don't need to be registered._
if (audio.settings.useFlash) return;
// Start tracking the load progress of the track.
container[audiojs].events.trackLoadProgress(audio);
container[audiojs].events.addListener(audio.element, 'timeupdate', function(e) {
audio.updatePlayhead.apply(audio);
});
container[audiojs].events.addListener(audio.element, 'ended', function(e) {
audio.trackEnded.apply(audio);
});
container[audiojs].events.addListener(audio.source, 'error', function(e) {
// on error, cancel any load timers that are running.
clearInterval(audio.readyTimer);
clearInterval(audio.loadTimer);
audio.settings.loadError.apply(audio);
});
},
// Flash requires a slightly different API to the `<audio>` element, so this method is used to overwrite the standard event handlers.
attachFlashEvents: function(element, audio) {
audio['swfReady'] = false;
audio['load'] = function(mp3) {
// If the swf isn't ready yet then just set `audio.mp3`. `init()` will load it in once the swf is ready.
audio.mp3 = mp3;
if (audio.swfReady) audio.element.load(mp3);
}
audio['loadProgress'] = function(percent, duration) {
audio.loadedPercent = percent;
audio.duration = duration;
audio.settings.loadStarted.apply(audio);
audio.settings.loadProgress.apply(audio, [percent]);
}
audio['skipTo'] = function(percent) {
if (percent > audio.loadedPercent) return;
audio.updatePlayhead.call(audio, [percent])
audio.element.skipTo(percent);
}
audio['updatePlayhead'] = function(percent) {
audio.settings.updatePlayhead.apply(audio, [percent]);
}
audio['play'] = function() {
// If the audio hasn't started preloading, then start it now.
// Then set `preload` to `true`, so that any tracks loaded in subsequently are loaded straight away.
if (!audio.settings.preload) {
audio.settings.preload = true;
audio.element.init(audio.mp3);
}
audio.playing = true;
// IE doesn't allow a method named `play()` to be exposed through `ExternalInterface`, so lets go with `pplay()`.
// <http://dev.nuclearrooster.com/2008/07/27/externalinterfaceaddcallback-can-cause-ie-js-errors-with-certain-keyworkds/>
audio.element.pplay();
audio.settings.play.apply(audio);
}
audio['pause'] = function() {
audio.playing = false;
// Use `ppause()` for consistency with `pplay()`, even though it isn't really required.
audio.element.ppause();
audio.settings.pause.apply(audio);
}
audio['setVolume'] = function(v) {
audio.element.setVolume(v);
}
audio['loadStarted'] = function() {
// Load the mp3 specified by the audio element into the swf.
audio.swfReady = true;
if (audio.settings.preload) audio.element.init(audio.mp3);
if (audio.settings.autoplay) audio.play.apply(audio);
}
},
// ### Injecting an swf from a string
// Build up the swf source by replacing the `$keys` and then inject the markup into the page.
injectFlash: function(audio, id) {
var flashSource = this.flashSource.replace(/\$1/g, id);
flashSource = flashSource.replace(/\$2/g, audio.settings.swfLocation);
// `(+new Date)` ensures the swf is not pulled out of cache. The fixes an issue with Firefox running multiple players on the same page.
flashSource = flashSource.replace(/\$3/g, (+new Date + Math.random()));
// Inject the player markup using a more verbose `innerHTML` insertion technique that works with IE.
var html = audio.wrapper.innerHTML,
div = document.createElement('div');
div.innerHTML = flashSource + html;
audio.wrapper.innerHTML = div.innerHTML;
audio.element = this.helpers.getSwf(id);
},
// ## Helper functions
helpers: {
// **Merge two objects, with `obj2` overwriting `obj1`**
// The merge is shallow, but that's all that is required for our purposes.
merge: function(obj1, obj2) {
for (attr in obj2) {
if (obj1.hasOwnProperty(attr) || obj2.hasOwnProperty(attr)) {
obj1[attr] = obj2[attr];
}
}
},
// **Clone a javascript object (recursively)**
clone: function(obj){
if (obj == null || typeof(obj) !== 'object') return obj;
var temp = new obj.constructor();
for (var key in obj) temp[key] = arguments.callee(obj[key]);
return temp;
},
// **Adding/removing classnames from elements**
addClass: function(element, className) {
var re = new RegExp('(\\s|^)'+className+'(\\s|$)');
if (re.test(element.className)) return;
element.className += ' ' + className;
},
removeClass: function(element, className) {
var re = new RegExp('(\\s|^)'+className+'(\\s|$)');
element.className = element.className.replace(re,' ');
},
// **Dynamic CSS injection**
// Takes a string of css, inserts it into a `<style>`, then injects it in at the very top of the `<head>`. This ensures any user-defined styles will take precedence.
injectCss: function(audio, string) {
// If an `audiojs` `<style>` tag already exists, then append to it rather than creating a whole new `<style>`.
var prepend = '',
styles = document.getElementsByTagName('style'),
css = string.replace(/\$1/g, audio.settings.imageLocation);
for (var i = 0, ii = styles.length; i < ii; i++) {
var title = styles[i].getAttribute('title');
if (title && ~title.indexOf('audiojs')) {
style = styles[i];
if (style.innerHTML === css) return;
prepend = style.innerHTML;
break;
}
};
var head = document.getElementsByTagName('head')[0],
firstchild = head.firstChild,
style = document.createElement('style');
if (!head) return;
style.setAttribute('type', 'text/css');
style.setAttribute('title', 'audiojs');
if (style.styleSheet) style.styleSheet.cssText = prepend + css;
else style.appendChild(document.createTextNode(prepend + css));
if (firstchild) head.insertBefore(style, firstchild);
else head.appendChild(styleElement);
},
// **Handle all the IE6+7 requirements for cloning `<audio>` nodes**
// Create a html5-safe document fragment by injecting an `<audio>` element into the document fragment.
cloneHtml5Node: function(audioTag) {
var fragment = document.createDocumentFragment();
fragment.createElement('audio');
var div = fragment.createElement('div');
fragment.appendChild(div);
div.innerHTML = audioTag.outerHTML;
return div.firstChild;
},
// **Cross-browser `<object>` / `<embed>` element selection**
getSwf: function(name) {
var swf = document[name] || window[name];
return swf.length > 1 ? swf[swf.length - 1] : swf;
}
},
// ## Event-handling
events: {
memoryLeaking: false,
listeners: [],
// **A simple cross-browser event handler abstraction**
addListener: function(element, eventName, func) {
// For modern browsers use the standard DOM-compliant `addEventListener`.
if (element.addEventListener) {
element.addEventListener(eventName, func, false);
// For older versions of Internet Explorer, use `attachEvent`.
// Also provide a fix for scoping `this` to the calling element and register each listener so the containing elements can be purged on page unload.
} else if (element.attachEvent) {
this.listeners.push(element);
if (!this.memoryLeaking) {
window.attachEvent('onunload', function() {
if(this.listeners) {
for (var i = 0, ii = this.listeners.length; i < ii; i++) {
container[audiojs].events.purge(this.listeners[i]);
}
}
});
this.memoryLeaking = true;
}
element.attachEvent('on' + eventName, function() {
func.call(element, window.event);
});
}
},
trackLoadProgress: function(audio) {
// If `preload` has been set to `none`, then we don't want to start loading the track yet.
if (!audio.settings.preload) return;
var readyTimer,
loadTimer,
audio = audio,
ios = (/(ipod|iphone|ipad)/i).test(navigator.userAgent);
// Use timers here rather than the official `progress` event, as Chrome has issues calling `progress` when loading mp3 files from cache.
readyTimer = setInterval(function() {
if (audio.element.readyState > -1) {
// iOS doesn't start preloading the mp3 until the user interacts manually, so this stops the loader being displayed prematurely.
if (!ios) audio.init.apply(audio);
}
if (audio.element.readyState > 1) {
if (audio.settings.autoplay) audio.play.apply(audio);
clearInterval(readyTimer);
// Once we have data, start tracking the load progress.
loadTimer = setInterval(function() {
audio.loadProgress.apply(audio);
if (audio.loadedPercent >= 1) clearInterval(loadTimer);
});
}
}, 10);
audio.readyTimer = readyTimer;
audio.loadTimer = loadTimer;
},
// **Douglas Crockford's IE6 memory leak fix**
// <http://javascript.crockford.com/memory/leak.html>
// This is used to release the memory leak created by the circular references created when fixing `this` scoping for IE. It is called on page unload.
purge: function(d) {
var a = d.attributes, i;
if (a) {
for (i = 0; i < a.length; i += 1) {
if (typeof d[a[i].name] === 'function') d[a[i].name] = null;
}
}
a = d.childNodes;
if (a) {
for (i = 0; i < a.length; i += 1) purge(d.childNodes[i]);
}
},
// **DOMready function**
// As seen here: <https://github.com/dperini/ContentLoaded/>.
ready: (function() { return function(fn) {
var win = window, done = false, top = true,
doc = win.document, root = doc.documentElement,
add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
pre = doc.addEventListener ? '' : 'on',
init = function(e) {
if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
(e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
if (!done && (done = true)) fn.call(win, e.type || e);
},
poll = function() {
try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
init('poll');
};
if (doc.readyState == 'complete') fn.call(win, 'lazy');
else {
if (doc.createEventObject && root.doScroll) {
try { top = !win.frameElement; } catch(e) { }
if (top) poll();
}
doc[add](pre + 'DOMContentLoaded', init, false);
doc[add](pre + 'readystatechange', init, false);
win[add](pre + 'load', init, false);
}
}
})()
}
}
// ## The audiojs class
// We create one of these per `<audio>` and then push them into `audiojs['instances']`.
container[audiojsInstance] = function(element, settings) {
// Each audio instance returns an object which contains an API back into the `<audio>` element.
this.element = element;
this.wrapper = element.parentNode;
this.source = element.getElementsByTagName('source')[0] || element;
// First check the `<audio>` element directly for a src and if one is not found, look for a `<source>` element.
this.mp3 = (function(element) {
var source = element.getElementsByTagName('source')[0];
return element.getAttribute('src') || (source ? source.getAttribute('src') : null);
})(element);
this.settings = settings;
this.loadStartedCalled = false;
this.loadedPercent = 0;
this.duration = 1;
this.playing = false;
}
container[audiojsInstance].prototype = {
// API access events:
// Each of these do what they need do and then call the matching methods defined in the settings object.
updatePlayhead: function() {
var percent = this.element.currentTime / this.duration;
this.settings.updatePlayhead.apply(this, [percent]);
},
skipTo: function(percent) {
if (percent > this.loadedPercent) return;
this.element.currentTime = this.duration * percent;
this.updatePlayhead();
},
load: function(mp3) {
this.loadStartedCalled = false;
this.source.setAttribute('src', mp3);
// The now outdated `load()` method is required for Safari 4
this.element.load();
this.mp3 = mp3;
container[audiojs].events.trackLoadProgress(this);
},
loadError: function() {
this.settings.loadError.apply(this);
},
init: function() {
this.settings.init.apply(this);
},
loadStarted: function() {
// Wait until `element.duration` exists before setting up the audio player.
if (!this.element.duration) return false;
this.duration = this.element.duration;
this.updatePlayhead();
this.settings.loadStarted.apply(this);
},
loadProgress: function() {
if (this.element.buffered != null && this.element.buffered.length) {
// Ensure `loadStarted()` is only called once.
if (!this.loadStartedCalled) {
this.loadStartedCalled = this.loadStarted();
}
var durationLoaded = this.element.buffered.end(this.element.buffered.length - 1);
this.loadedPercent = durationLoaded / this.duration;
this.settings.loadProgress.apply(this, [this.loadedPercent]);
}
},
playPause: function() {
if (this.playing) this.pause();
else this.play();
},
play: function() {
var ios = (/(ipod|iphone|ipad)/i).test(navigator.userAgent);
// On iOS this interaction will trigger loading the mp3, so run `init()`.
if (ios && this.element.readyState == 0) this.init.apply(this);
// If the audio hasn't started preloading, then start it now.
// Then set `preload` to `true`, so that any tracks loaded in subsequently are loaded straight away.
if (!this.settings.preload) {
this.settings.preload = true;
this.element.setAttribute('preload', 'auto');
container[audiojs].events.trackLoadProgress(this);
}
this.playing = true;
this.element.play();
this.settings.play.apply(this);
},
pause: function() {
this.playing = false;
this.element.pause();
this.settings.pause.apply(this);
},
setVolume: function(v) {
this.element.volume = v;
},
trackEnded: function(e) {
this.skipTo.apply(this, [0]);
if (!this.settings.loop) this.pause.apply(this);
this.settings.trackEnded.apply(this);
}
}
// **getElementsByClassName**
// Having to rely on `getElementsByTagName` is pretty inflexible internally, so a modified version of Dustin Diaz's `getElementsByClassName` has been included.
// This version cleans things up and prefers the native DOM method if it's available.
var getByClass = function(searchClass, node, tag) {
var matches = [];
if (document.getElementsByClassName) {
matches = node.getElementsByClassName(searchClass);
} else {
var node = node || document,
tag = tag || '*',
els = node.getElementsByTagName(tag),
pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
for (i = 0, j = 0, l = els.length; i < l; i++) {
if (pattern.test(els[i].className)) {
matches[j] = els[i];
j++;
}
}
}
return matches.length > 1 ? matches : matches[0];
}
// The global variable names are passed in here and can be changed if they conflict with anything else.
})('audiojs', 'audiojsInstance', this);

View file

@ -59,15 +59,18 @@ img
font-size: 17px; font-size: 17px;
text-decoration: none; text-decoration: none;
background: #f4f4f4; background: #f4f4f4;
border-top: 1px solid #CBCBCB; border-top: 1px solid #E4E4E4;
border-left: 1px solid #CBCBCB; border-left: 1px solid #E4E4E4;
border-right: 1px solid #CBCBCB; border-right: 1px solid #E4E4E4;
} }
#nav a.active #nav a.active
{ {
color: #545454; color: #545454;
background: #EDEDED; background: #EDEDED;
padding: 6px 15px 6px 15px; padding: 6px 15px 6px 15px;
border-top: 1px solid #CBCBCB;
border-left: 1px solid #CBCBCB;
border-right: 1px solid #CBCBCB;
} }
#nav a:hover { color: #545454; } #nav a:hover { color: #545454; }
#content #content
@ -238,6 +241,7 @@ ul.songlist li.song
padding: 8px; padding: 8px;
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
height: 19px;
} }
ul.songlist li.song span ul.songlist li.song span
{ {
@ -245,7 +249,25 @@ ul.songlist li.song span
float: left; float: left;
padding: 0 10px 0 0; padding: 0 10px 0 0;
text-align: right; text-align: right;
}
ul.songlist li.song span.track
{
width: 30px; width: 30px;
text-align: right;
}
ul.songlist li.song span.album
{
color: #B3B8BE;
font-size: 10px;
padding: 2px 0 0 0;
}
ul.songlist li.back a
{
color: #4B95E5;
}
ul.songlist li.back a:hover
{
text-decoration: underline;
} }
ul.songlist li small ul.songlist li small
{ {
@ -294,7 +316,7 @@ ul.songlist li.selected
{ {
text-decoration: underline; text-decoration: underline;
} }
.positionabove #submenu_Chat
{ {
left: 526px; left: 526px;
bottom: 55px; bottom: 55px;
@ -326,6 +348,37 @@ ul.songlist li.selected
font-size: 9px; font-size: 9px;
color: #CAD0D7; color: #CAD0D7;
} }
#submenu_NowPlaying
{
left: 641px;
bottom: 55px;
margin: 0 0 0 1px;
position: absolute;
width: 220px;
text-align: right;
}
#NowPlayingList
{
padding: 5px;
text-align: right;
}
#NowPlayingList .msg
{
margin: 0 0 5px 0;
font-size: 11px;
}
#NowPlayingList span.user
{
font-size: 10px;
color: #B3B8BE;
}
#NowPlayingList span.artist
{
font-style: italic;
}
#NowPlayingList span.title
{
}
/* Player Style */ /* Player Style */
#player #player