factored out music library index. and more...
* use keese * relative file path is no longer the db key
This commit is contained in:
parent
983937132e
commit
1349bf6f91
13 changed files with 646 additions and 878 deletions
191
lib/player.js
191
lib/player.js
|
|
@ -9,6 +9,8 @@ var chokidar = require('chokidar');
|
|||
var shuffle = require('mess');
|
||||
var mv = require('mv');
|
||||
var zfill = require('zfill');
|
||||
var MusicLibraryIndex = require('music-library-index');
|
||||
var keese = require('keese');
|
||||
var safePath = require('./safe_path');
|
||||
|
||||
module.exports = Player;
|
||||
|
|
@ -37,7 +39,8 @@ function Player(db, musicDirectory) {
|
|||
EventEmitter.call(this);
|
||||
this.db = db;
|
||||
this.musicDirectory = musicDirectory;
|
||||
this.allDbFiles = {};
|
||||
this.dbFilesByPath = {};
|
||||
this.libraryIndex = new MusicLibraryIndex();
|
||||
this.addQueue = new Pend();
|
||||
this.addQueue.max = 10;
|
||||
|
||||
|
|
@ -123,13 +126,18 @@ Player.prototype.initialize = function(cb) {
|
|||
|
||||
function onFileMissing(fullPath) {
|
||||
var relPath = path.relative(self.musicDirectory, fullPath);
|
||||
self.delDbEntry(relPath);
|
||||
var dbFile = self.dbFilesByPath[relPath];
|
||||
if (!dbFile) {
|
||||
console.warn("File reported deleted that was not in our db:", fullPath);
|
||||
return;
|
||||
}
|
||||
self.delDbEntry(dbFile);
|
||||
}
|
||||
|
||||
function onAddOrChange(fullPath, stat) {
|
||||
// check the mtime against the mtime of the same file in the db
|
||||
var relPath = path.relative(self.musicDirectory, fullPath);
|
||||
var dbFile = self.allDbFiles[relPath];
|
||||
var dbFile = self.dbFilesByPath[relPath];
|
||||
var fileMtime = stat.mtime.getTime();
|
||||
|
||||
if (dbFile) {
|
||||
|
|
@ -154,8 +162,9 @@ Player.prototype.initialize = function(cb) {
|
|||
cb();
|
||||
return;
|
||||
}
|
||||
var file = data.key.substring(LIBRARY_KEY_PREFIX.length);
|
||||
self.allDbFiles[file] = deserializeFileData(data.value);
|
||||
var dbFile = deserializeFileData(data.value);
|
||||
self.libraryIndex.addTrack(dbFile);
|
||||
self.dbFilesByPath[dbFile.file] = dbFile;
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
stream.removeAllListeners();
|
||||
|
|
@ -168,24 +177,30 @@ Player.prototype.initialize = function(cb) {
|
|||
}
|
||||
};
|
||||
|
||||
Player.prototype.deleteFile = function(relPath) {
|
||||
Player.prototype.deleteFile = function(key) {
|
||||
var self = this;
|
||||
var fullPath = path.join(self.musicDirectory, relPath);
|
||||
var dbFile = self.libraryIndex.trackTable[key];
|
||||
if (!dbFile) {
|
||||
console.error("Error deleting file - no entry:", key);
|
||||
return;
|
||||
}
|
||||
var fullPath = path.join(self.musicDirectory, dbFile.file);
|
||||
fs.unlink(fullPath, function(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting", relPath, err.stack);
|
||||
console.error("Error deleting", dbFile.file, err.stack);
|
||||
}
|
||||
});
|
||||
self.delDbEntry(relPath);
|
||||
self.delDbEntry(dbFile);
|
||||
};
|
||||
|
||||
Player.prototype.delDbEntry = function(relPath) {
|
||||
Player.prototype.delDbEntry = function(dbFile) {
|
||||
var self = this;
|
||||
delete self.allDbFiles[relPath];
|
||||
self.emit('delete', relPath);
|
||||
self.db.del(relPath, function(err) {
|
||||
self.libraryIndex.removeTrack(dbFile.key);
|
||||
delete self.dbFilesByPath[dbFile.file];
|
||||
self.emit('delete', dbFile);
|
||||
self.db.del(LIBRARY_KEY_PREFIX + dbFile.key, function(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting db entry", relPath, err.stack);
|
||||
console.error("Error deleting db entry", dbFile.key, err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -211,7 +226,7 @@ Player.prototype.importFile = function(fullPath, filenameHint, cb) {
|
|||
groove.open(fullPath, function(err, file) {
|
||||
if (err) return cb(err);
|
||||
var newDbFile = grooveFileToDbFile(file, filenameHint);
|
||||
var suggestedPath = getSuggestedPath(newDbFile, filenameHint);
|
||||
var suggestedPath = self.getSuggestedPath(newDbFile, filenameHint);
|
||||
pend.go(testSuggestedPath);
|
||||
pend.go(function(cb) {
|
||||
file.close(cb);
|
||||
|
|
@ -248,10 +263,11 @@ Player.prototype.importFile = function(fullPath, filenameHint, cb) {
|
|||
|
||||
Player.prototype.persist = function(dbFile, cb) {
|
||||
cb = cb || logIfError;
|
||||
var prevDbFile = this.allDbFiles[dbFile.file];
|
||||
this.allDbFiles[dbFile.file] = dbFile;
|
||||
var prevDbFile = this.libraryIndex.trackTable[dbFile.key];
|
||||
this.libraryIndex.addTrack(dbFile);
|
||||
this.dbFilesByPath[dbFile.file] = dbFile;
|
||||
this.emit('update', prevDbFile, dbFile);
|
||||
this.db.put(LIBRARY_KEY_PREFIX + dbFile.file, serializeFileData(dbFile), cb);
|
||||
this.db.put(LIBRARY_KEY_PREFIX + dbFile.key, serializeFileData(dbFile), cb);
|
||||
|
||||
function logIfError(err) {
|
||||
if (err) {
|
||||
|
|
@ -290,21 +306,23 @@ Player.prototype.queueAddToLibrary = function(relPath, mtime) {
|
|||
});
|
||||
};
|
||||
|
||||
Player.prototype.appendFiles = function(files, tagAsRandom) {
|
||||
Player.prototype.appendTracks = function(keys, tagAsRandom) {
|
||||
var lastTrack = this.tracksInOrder[this.tracksInOrder.length - 1];
|
||||
var items = {};
|
||||
var nextSortKey = lastTrack ? lastTrack.sort_key + 1 : 0;
|
||||
files.forEach(function(file) {
|
||||
var lastSortKey = lastTrack ? lastTrack.sortKey : keese();
|
||||
keys.forEach(function(key) {
|
||||
var id = uuid();
|
||||
var nextSortKey = keese(lastSortKey, null);
|
||||
lastSortKey = nextSortKey;
|
||||
items[id] = {
|
||||
file: file,
|
||||
sort_key: nextSortKey++,
|
||||
key: key,
|
||||
sortKey: nextSortKey,
|
||||
};
|
||||
});
|
||||
this.addItems(items, tagAsRandom);
|
||||
};
|
||||
|
||||
// items looks like {id: {file, sort_key}}
|
||||
// items looks like {id: {key, sortKey}}
|
||||
Player.prototype.addItems = function(items, tagAsRandom) {
|
||||
var self = this;
|
||||
tagAsRandom = !!tagAsRandom;
|
||||
|
|
@ -312,10 +330,9 @@ Player.prototype.addItems = function(items, tagAsRandom) {
|
|||
var item = items[id];
|
||||
var playlistItem = {
|
||||
id: id,
|
||||
file: item.file,
|
||||
sort_key: item.sort_key,
|
||||
is_random: tagAsRandom,
|
||||
time: self.allDbFiles[item.file].time,
|
||||
key: item.key,
|
||||
sortKey: item.sortKey,
|
||||
isRandom: tagAsRandom,
|
||||
grooveFile: null,
|
||||
pendingGrooveFile: false,
|
||||
deleted: false,
|
||||
|
|
@ -340,10 +357,12 @@ Player.prototype.clearPlaylist = function() {
|
|||
|
||||
Player.prototype.shufflePlaylist = function() {
|
||||
shuffle(this.tracksInOrder);
|
||||
// fix sort_key and index properties
|
||||
// fix sortKey and index properties
|
||||
var nextSortKey = keese(null, null);
|
||||
this.tracksInOrder.forEach(function(track, index) {
|
||||
track.index = index;
|
||||
track.sort_key = index;
|
||||
track.sortKey = nextSortKey;
|
||||
nextSortKey = keese(nextSortKey, null);
|
||||
});
|
||||
playlistChanged(this);
|
||||
}
|
||||
|
|
@ -355,10 +374,10 @@ Player.prototype.removePlaylistItems = function(ids) {
|
|||
playlistChanged(this);
|
||||
}
|
||||
|
||||
// items looks like {id: {sort_key}}
|
||||
// items looks like {id: {sortKey}}
|
||||
Player.prototype.movePlaylistItems = function(items) {
|
||||
for (var id in items) {
|
||||
this.playlist[id].sort_key = items[id].sort_key;
|
||||
this.playlist[id].sortKey = items[id].sortKey;
|
||||
}
|
||||
playlistChanged(this);
|
||||
}
|
||||
|
|
@ -414,38 +433,40 @@ Player.prototype.stop = function() {
|
|||
playlistChanged(this);
|
||||
}
|
||||
|
||||
function operatorCompare(a, b) {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
Player.prototype.getSuggestedPath = function(track, filenameHint) {
|
||||
var p = "";
|
||||
if (track.albumArtistName) {
|
||||
p = path.join(p, safePath(track.albumArtistName));
|
||||
} else if (track.compilation) {
|
||||
p = path.join(p, safePath(this.libraryIndex.variousArtistsName));
|
||||
}
|
||||
if (track.albumName) {
|
||||
p = path.join(p, safePath(track.albumName));
|
||||
}
|
||||
var t = "";
|
||||
if (track.track != null) {
|
||||
t += safePath(zfill(track.track, 2)) + " ";
|
||||
}
|
||||
t += safePath(track.name + path.extname(filenameHint));
|
||||
return path.join(p, t);
|
||||
}
|
||||
|
||||
// TODO: use keese
|
||||
function generateSortKey(previous_key, next_key){
|
||||
if (previous_key != null) {
|
||||
if (next_key != null) {
|
||||
return (previous_key + next_key) / 2;
|
||||
} else {
|
||||
return 0 | previous_key + 1;
|
||||
}
|
||||
} else {
|
||||
if (next_key != null) {
|
||||
return (0 + next_key) / 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function operatorCompare(a, b) {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
|
||||
function disambiguateSortKeys(self) {
|
||||
var previousUniqueKey = null;
|
||||
var previousKey = null;
|
||||
self.tracksInOrder.forEach(function(track, i) {
|
||||
if (track.sort_key === previousKey) {
|
||||
if (track.sortKey === previousKey) {
|
||||
// move the repeat back
|
||||
track.sort_key = generateSortKey(previousUniqueKey, track.sort_key);
|
||||
previousUniqueKey = track.sort_key;
|
||||
track.sortKey = keese(previousUniqueKey, track.sortKey);
|
||||
previousUniqueKey = track.sortKey;
|
||||
} else {
|
||||
previousUniqueKey = previousKey;
|
||||
previousKey = track.sort_key;
|
||||
previousKey = track.sortKey;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -459,7 +480,7 @@ function cacheTracksArray(self) {
|
|||
});
|
||||
|
||||
function asc(a, b) {
|
||||
return operatorCompare(a.sort_key, b.sort_key);
|
||||
return operatorCompare(a.sortKey, b.sortKey);
|
||||
}
|
||||
function trackById(id) {
|
||||
return self.playlist[id];
|
||||
|
|
@ -494,12 +515,13 @@ function playlistChanged(self) {
|
|||
}
|
||||
|
||||
function preloadFile(self, track) {
|
||||
var fullPath = path.join(self.musicDirectory, track.file);
|
||||
var relPath = self.libraryIndex.trackTable[track.key].file;
|
||||
var fullPath = path.join(self.musicDirectory, relPath);
|
||||
track.pendingGrooveFile = true;
|
||||
groove.open(fullPath, function(err, file) {
|
||||
track.pendingGrooveFile = false;
|
||||
if (err) {
|
||||
console.error("Error opening", track.file, err.stack);
|
||||
console.error("Error opening", relPath, err.stack);
|
||||
return;
|
||||
}
|
||||
if (track.deleted) {
|
||||
|
|
@ -627,10 +649,6 @@ function shouldSuppressWarning(filename) {
|
|||
return IGNORE_OK_EXTS.indexOf(ext) !== -1;
|
||||
}
|
||||
|
||||
function getFile(item) {
|
||||
return item.file;
|
||||
}
|
||||
|
||||
function closeFile(file) {
|
||||
file.close(function(err) {
|
||||
if (err) {
|
||||
|
|
@ -639,38 +657,43 @@ function closeFile(file) {
|
|||
});
|
||||
}
|
||||
|
||||
function parseIntOrNull(n){
|
||||
function parseTrackString(trackStr) {
|
||||
if (!trackStr) return {};
|
||||
var parts = trackStr.split('/');
|
||||
if (parts.length > 1) {
|
||||
return {
|
||||
track: parseIntOrNull(parts[0]),
|
||||
trackCount: parseIntOrNull(parts[1]),
|
||||
};
|
||||
}
|
||||
return {
|
||||
track: parseIntOrNull(parts[0]),
|
||||
};
|
||||
}
|
||||
|
||||
function parseIntOrNull(n) {
|
||||
n = parseInt(n, 10);
|
||||
if (isNaN(n)) n = null;
|
||||
if (isNaN(n)) return null;
|
||||
return n;
|
||||
}
|
||||
|
||||
function getSuggestedPath(track, filenameHint) {
|
||||
var p = "";
|
||||
if (track.album_artist_name) {
|
||||
p = path.join(p, safePath(track.album_artist_name));
|
||||
}
|
||||
if (track.album_name) {
|
||||
p = path.join(p, safePath(track.album_name));
|
||||
}
|
||||
var t = "";
|
||||
if (track.track != null) {
|
||||
t += safePath(zfill(track.track, 2)) + " ";
|
||||
}
|
||||
t += safePath(track.name + path.extname(filenameHint));
|
||||
return path.join(p, t);
|
||||
}
|
||||
|
||||
function grooveFileToDbFile(file, filenameHint) {
|
||||
var parsedTrack = parseTrackString(file.getMetadata("track"));
|
||||
var parsedDisc = parseTrackString(file.getMetadata("disc"));
|
||||
return {
|
||||
key: uuid(),
|
||||
name: file.getMetadata("title") || trackNameFromFile(filenameHint),
|
||||
artist_name: (file.getMetadata("artist") || "").trim(),
|
||||
artist_disambiguation: "",
|
||||
album_artist_name: (file.getMetadata("album_artist") || "").trim(),
|
||||
album_name: (file.getMetadata("album") || "").trim(),
|
||||
track: parseIntOrNull(file.getMetadata("track")),
|
||||
time: file.duration(),
|
||||
artistName: (file.getMetadata("artist") || "").trim(),
|
||||
albumArtistName: (file.getMetadata("album_artist") || "").trim(),
|
||||
albumName: (file.getMetadata("album") || "").trim(),
|
||||
compilation: !!parseInt(file.getMetadata("TCP"), 10),
|
||||
track: parsedTrack.track,
|
||||
trackCount: parsedTrack.trackCount,
|
||||
disc: parsedDisc.disc,
|
||||
discCount: parsedDisc.discCount,
|
||||
duration: file.duration(),
|
||||
year: parseInt(file.getMetadata("date") || "0", 10),
|
||||
genre: file.getMetadata("genre"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ var actions = {
|
|||
'listallinfo': {
|
||||
permission: 'read',
|
||||
fn: function(client, msg, cb) {
|
||||
cb({msg: this.player.allDbFiles});
|
||||
cb({msg: this.player.libraryIndex.trackTable});
|
||||
},
|
||||
},
|
||||
'move': {
|
||||
|
|
@ -67,7 +67,7 @@ var actions = {
|
|||
'playid': {
|
||||
permission: 'control',
|
||||
fn: function(client, msg, cb) {
|
||||
this.player.playId(msg.track_id);
|
||||
this.player.playId(msg.trackId);
|
||||
cb({});
|
||||
},
|
||||
},
|
||||
|
|
@ -112,8 +112,8 @@ var actions = {
|
|||
volume: this.player.volume,
|
||||
repeat: this.player.repeat,
|
||||
state: this.player.isPlaying ? 'play' : 'pause',
|
||||
track_start_date: this.player.trackStartDate,
|
||||
paused_time: this.player.pausedTime,
|
||||
trackStartDate: this.player.trackStartDate,
|
||||
pausedTime: this.player.pausedTime,
|
||||
}});
|
||||
},
|
||||
},
|
||||
|
|
@ -138,7 +138,7 @@ PlayerServer.prototype.createClient = function(socket, permissions) {
|
|||
socket.on('request', function(request){
|
||||
request = JSON.parse(request);
|
||||
self.request(client, request.cmd, function(arg){
|
||||
var response = {callback_id: request.callback_id};
|
||||
var response = {callbackId: request.callbackId};
|
||||
response.err = arg.err;
|
||||
response.msg = arg.msg;
|
||||
socket.emit('PlayerResponse', JSON.stringify(response));
|
||||
|
|
@ -202,10 +202,9 @@ function serializePlaylist(playlist) {
|
|||
for (var id in playlist) {
|
||||
var item = playlist[id];
|
||||
o[id] = {
|
||||
file: item.file,
|
||||
sort_key: item.sort_key,
|
||||
is_random: item.is_random,
|
||||
time: item.time,
|
||||
key: item.key,
|
||||
sortKey: item.sortKey,
|
||||
isRandom: item.isRandom,
|
||||
};
|
||||
}
|
||||
return o;
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@ function Delete(gb) {
|
|||
|
||||
function onSocketConnection(client) {
|
||||
var self = this;
|
||||
client.on('DeleteFromLibrary', function(data) {
|
||||
client.on('DeleteFromLibrary', function(keys) {
|
||||
if (!client.permissions.admin) {
|
||||
console.warn("User without admin permission trying to delete songs");
|
||||
return;
|
||||
}
|
||||
var files = JSON.parse(data);
|
||||
files.forEach(function(file) {
|
||||
self.gb.player.deleteFile(file);
|
||||
keys.forEach(function(key) {
|
||||
self.gb.player.deleteFile(key);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,14 @@ function setup(self) {
|
|||
self.downloadPath(dlPath, zipName, req, resp);
|
||||
});
|
||||
app.post('/download/custom', express.urlencoded(), function(req, resp) {
|
||||
var reqFiles = req.body.file;
|
||||
if (!Array.isArray(reqFiles)) {
|
||||
reqFiles = [reqFiles];
|
||||
var reqKeys = req.body.key;
|
||||
if (!Array.isArray(reqKeys)) {
|
||||
reqKeys = [reqKeys];
|
||||
}
|
||||
var files = reqFiles.map(function(f) { return path.join(musicDir, f); });
|
||||
var files = reqKeys.map(function(key) {
|
||||
var dbFile = self.gb.player.libraryIndex.trackTable[key];
|
||||
return dbFile && path.join(musicDir, dbFile.file);
|
||||
});
|
||||
var reqZipName = req.body.zipName || "music";
|
||||
var zipName = safePath(reqZipName.toString()) + ".zip";
|
||||
self.sendZipOfFiles(zipName, files, req, resp);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ DynamicMode.prototype.checkDynamicMode = function() {
|
|||
allIds[track.id] = true;
|
||||
if (self.previousIds[track.id] == null) {
|
||||
// tag any newly queued tracks
|
||||
var dbFile = player.allDbFiles[track.file];
|
||||
var dbFile = player.libraryIndex.trackTable[track.key];
|
||||
dbFile.lastQueueDate = now;
|
||||
player.persist(dbFile);
|
||||
}
|
||||
|
|
@ -95,19 +95,19 @@ DynamicMode.prototype.checkDynamicMode = function() {
|
|||
didAnything = true;
|
||||
}
|
||||
|
||||
var files = self.getRandomSongFiles(addCount);
|
||||
if (files.length > 0) didAnything = true;
|
||||
if (didAnything) player.appendFiles(files, true);
|
||||
var keys = self.getRandomSongKeys(addCount);
|
||||
if (keys.length > 0) didAnything = true;
|
||||
if (didAnything) player.appendTracks(keys, true);
|
||||
};
|
||||
|
||||
DynamicMode.prototype.getRandomSongFiles = function(count) {
|
||||
DynamicMode.prototype.getRandomSongKeys = function(count) {
|
||||
if (count === 0) return [];
|
||||
var player = this.gb.player;
|
||||
|
||||
var neverQueued = [];
|
||||
var sometimesQueued = [];
|
||||
for (var key in player.allDbFiles) {
|
||||
var dbFile = player.allDbFiles[key];
|
||||
for (var key in player.libraryIndex.trackTable) {
|
||||
var dbFile = player.libraryIndex.trackTable[key];
|
||||
if (dbFile.lastQueueDate == null) {
|
||||
neverQueued.push(dbFile);
|
||||
} else {
|
||||
|
|
@ -130,17 +130,17 @@ DynamicMode.prototype.getRandomSongFiles = function(count) {
|
|||
var totalSize = triangleArea + rectangleArea;
|
||||
if (totalSize === 0) return [];
|
||||
// decode indexes through the distribution shape
|
||||
var files = [];
|
||||
var keys = [];
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
var index = Math.random() * totalSize;
|
||||
if (index < triangleArea) {
|
||||
// triangle
|
||||
files.push(sometimesQueued[Math.floor(Math.sqrt(index))].file);
|
||||
keys.push(sometimesQueued[Math.floor(Math.sqrt(index))].key);
|
||||
} else {
|
||||
files.push(neverQueued[Math.floor((index - triangleArea) / maxWeight)].file);
|
||||
keys.push(neverQueued[Math.floor((index - triangleArea) / maxWeight)].key);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
return keys;
|
||||
};
|
||||
|
||||
function broadcastUpdate(self, socket) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@
|
|||
"findit": "~1.1.0",
|
||||
"archiver": "~0.4.9",
|
||||
"temp": "~0.6.0",
|
||||
"uuid": "~1.4.1"
|
||||
"uuid": "~1.4.1",
|
||||
"music-library-index": "0.0.1",
|
||||
"keese": "~1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"handlebars": "1.0.7",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,8 @@ var removeDiacritics = require('diacritics').remove;
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var uuid = require('uuid');
|
||||
var MusicLibraryIndex = require('music-library-index');
|
||||
var keese = require('keese');
|
||||
|
||||
module.exports = PlayerClient;
|
||||
|
||||
|
|
@ -16,11 +18,7 @@ module.exports = PlayerClient;
|
|||
*
|
||||
* */
|
||||
|
||||
var PREFIXES_TO_STRIP = [/^\s*the\s+/, /^\s*a\s+/, /^\s*an\s+/];
|
||||
var VARIOUS_ARTISTS_KEY = "VariousArtists";
|
||||
var VARIOUS_ARTISTS_NAME = "Various Artists";
|
||||
var compareSortKeyAndId = makeCompareProps(['sort_key', 'id']);
|
||||
|
||||
var compareSortKeyAndId = makeCompareProps(['sortKey', 'id']);
|
||||
|
||||
PlayerClient.REPEAT_OFF = 0;
|
||||
PlayerClient.REPEAT_ALL = 1;
|
||||
|
|
@ -55,66 +53,62 @@ function PlayerClient(socket) {
|
|||
}
|
||||
|
||||
PlayerClient.prototype.handleConnectionStart = function(){
|
||||
var this$ = this;
|
||||
var self = this;
|
||||
this.updateLibrary(function(){
|
||||
this$.updateStatus();
|
||||
this$.updatePlaylist();
|
||||
self.updateStatus();
|
||||
self.updatePlaylist();
|
||||
});
|
||||
};
|
||||
|
||||
PlayerClient.prototype.updateLibrary = function(callback){
|
||||
var this$ = this;
|
||||
var self = this;
|
||||
callback = callback || noop;
|
||||
this.sendCommandName('listallinfo', function(err, track_table){
|
||||
var res$, _, track, tracks, last_query;
|
||||
if (err) {
|
||||
return callback(err);
|
||||
this.sendCommandName('listallinfo', function(err, trackTable){
|
||||
if (err) return callback(err);
|
||||
self.library.clear();
|
||||
for (var key in trackTable) {
|
||||
var track = trackTable[key];
|
||||
self.library.addTrack(track);
|
||||
}
|
||||
res$ = [];
|
||||
for (_ in track_table) {
|
||||
track = track_table[_];
|
||||
res$.push(track);
|
||||
}
|
||||
tracks = res$;
|
||||
addSearchTags(tracks);
|
||||
this$.buildArtistAlbumTree(tracks, this$.library);
|
||||
this$.have_file_list_cache = true;
|
||||
last_query = this$.last_query;
|
||||
this$.last_query = "";
|
||||
this$.search(last_query);
|
||||
self.library.rebuild();
|
||||
self.haveFileListCache = true;
|
||||
var lastQuery = self.lastQuery;
|
||||
self.lastQuery = null;
|
||||
self.search(lastQuery);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
PlayerClient.prototype.updatePlaylist = function(callback){
|
||||
var this$ = this;
|
||||
var self = this;
|
||||
callback = callback || noop;
|
||||
this.sendCommandName('playlistinfo', function(err, tracks){
|
||||
if (err) return callback(err);
|
||||
this$.clearPlaylist();
|
||||
self.clearPlaylist();
|
||||
for (var id in tracks) {
|
||||
var item = tracks[id];
|
||||
var track = this$.library.track_table[item.file];
|
||||
track.time = item.time;
|
||||
this$.playlist.item_table[id] = {
|
||||
var track = self.library.trackTable[item.key];
|
||||
self.playlist.itemTable[id] = {
|
||||
id: id,
|
||||
sort_key: item.sort_key,
|
||||
is_random: item.is_random,
|
||||
sortKey: item.sortKey,
|
||||
isRandom: item.isRandom,
|
||||
track: track,
|
||||
playlist: this$.playlist,
|
||||
playlist: self.playlist,
|
||||
};
|
||||
}
|
||||
this$.refreshPlaylistList();
|
||||
if (this$.status.current_item != null) {
|
||||
this$.status.current_item = this$.playlist.item_table[this$.status.current_item.id];
|
||||
self.refreshPlaylistList();
|
||||
if (self.currentItem != null) {
|
||||
self.currentItem = self.playlist.itemTable[self.currentItem.id];
|
||||
}
|
||||
if (this$.status.current_item != null) {
|
||||
this$.emit('playlistupdate');
|
||||
if (self.currentItem != null) {
|
||||
self.emit('playlistupdate');
|
||||
callback();
|
||||
} else {
|
||||
this$.updateStatus(function(err){
|
||||
self.updateStatus(function(err){
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
this$.emit('playlistupdate');
|
||||
self.emit('playlistupdate');
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
|
@ -122,168 +116,121 @@ PlayerClient.prototype.updatePlaylist = function(callback){
|
|||
};
|
||||
|
||||
PlayerClient.prototype.updateStatus = function(callback){
|
||||
var this$ = this;
|
||||
var self = this;
|
||||
callback = callback || noop;
|
||||
this.sendCommandName('status', function(err, o){
|
||||
var ref$;
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
ref$ = this$.status;
|
||||
ref$.volume = o.volume;
|
||||
ref$.repeat = o.repeat;
|
||||
ref$.state = o.state;
|
||||
this$.status.track_start_date = o.track_start_date != null ? new Date(o.track_start_date) : null;
|
||||
this$.status.paused_time = o.paused_time;
|
||||
if (err) return callback(err);
|
||||
self.volume = o.volume;
|
||||
self.repeat = o.repeat;
|
||||
self.state = o.state;
|
||||
self.trackStartDate = o.trackStartDate != null ? new Date(o.trackStartDate) : null;
|
||||
self.pausedTime = o.pausedTime;
|
||||
});
|
||||
this.sendCommandName('currentsong', function(err, id){
|
||||
if (err) return callback(err);
|
||||
if (id != null) {
|
||||
this$.status.current_item = this$.playlist.item_table[id];
|
||||
if (this$.status.current_item != null) {
|
||||
this$.status.time = this$.status.current_item.track.time;
|
||||
this$.emit('statusupdate');
|
||||
self.currentItem = self.playlist.itemTable[id];
|
||||
if (self.currentItem != null) {
|
||||
self.duration = self.currentItem.track.duration;
|
||||
self.emit('statusupdate');
|
||||
callback();
|
||||
} else {
|
||||
this$.status.current_item = null;
|
||||
this$.updatePlaylist(function(err){
|
||||
self.currentItem = null;
|
||||
self.updatePlaylist(function(err){
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
this$.emit('statusupdate');
|
||||
self.emit('statusupdate');
|
||||
callback();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this$.status.current_item = null;
|
||||
self.currentItem = null;
|
||||
callback();
|
||||
this$.emit('statusupdate');
|
||||
self.emit('statusupdate');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PlayerClient.prototype.search = function(query){
|
||||
PlayerClient.prototype.search = function(query) {
|
||||
query = query.trim();
|
||||
if (query.length === 0) {
|
||||
this.search_results = this.library;
|
||||
this.emit('libraryupdate');
|
||||
this.last_query = query;
|
||||
return;
|
||||
}
|
||||
var words = formatSearchable(query).split(/\s+/);
|
||||
|
||||
var words = query.split(/\s+/);
|
||||
query = words.join(" ");
|
||||
if (query === this.last_query) {
|
||||
return;
|
||||
}
|
||||
this.last_query = query;
|
||||
var result = [];
|
||||
for (var k in this.library.track_table) {
|
||||
var track = this.library.track_table[k];
|
||||
var is_match = fn$();
|
||||
if (is_match) {
|
||||
result.push(track);
|
||||
}
|
||||
}
|
||||
this.buildArtistAlbumTree(result, this.search_results = {});
|
||||
if (query === this.lastQuery) return;
|
||||
|
||||
this.lastQuery = query;
|
||||
this.searchResults = this.library.search(query);
|
||||
this.emit('libraryupdate');
|
||||
function fn$(){
|
||||
var i, ref$, len$, word;
|
||||
for (i = 0, len$ = (ref$ = words).length; i < len$; ++i) {
|
||||
word = ref$[i];
|
||||
if (track.search_tags.indexOf(word) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.getDefaultQueuePosition = function(){
|
||||
var ref$, previous_key, next_key, start_pos, i, to$, track, sort_key;
|
||||
previous_key = (ref$ = this.status.current_item) != null ? ref$.sort_key : void 8;
|
||||
next_key = null;
|
||||
start_pos = ((ref$ = (ref$ = this.status.current_item) != null ? ref$.pos : void 8) != null ? ref$ : -1) + 1;
|
||||
for (i = start_pos, to$ = this.playlist.item_list.length; i < to$; ++i) {
|
||||
track = this.playlist.item_list[i];
|
||||
sort_key = track.sort_key;
|
||||
if (track.is_random) {
|
||||
next_key = sort_key;
|
||||
PlayerClient.prototype.getDefaultQueuePosition = function() {
|
||||
var previousKey = this.currentItem && this.currentItem.sortKey;
|
||||
var nextKey = null;
|
||||
var startPos = this.currentItem ? this.currentItem.index + 1 : 0;
|
||||
for (var i = startPos; i < this.playlist.itemList.length; i += 1) {
|
||||
var track = this.playlist.itemList[i];
|
||||
var sortKey = track.sortKey;
|
||||
if (track.isRandom) {
|
||||
nextKey = sortKey;
|
||||
break;
|
||||
}
|
||||
previous_key = sort_key;
|
||||
previousKey = sortKey;
|
||||
}
|
||||
return {
|
||||
previous_key: previous_key,
|
||||
next_key: next_key
|
||||
previousKey: previousKey,
|
||||
nextKey: nextKey
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: use keese
|
||||
PlayerClient.prototype.generateSortKey = function(previous_key, next_key){
|
||||
if (previous_key != null) {
|
||||
if (next_key != null) {
|
||||
return (previous_key + next_key) / 2;
|
||||
} else {
|
||||
return 0 | previous_key + 1;
|
||||
}
|
||||
} else {
|
||||
if (next_key != null) {
|
||||
return (0 + next_key) / 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
PlayerClient.prototype.queueTracks = function(keys, previousKey, nextKey) {
|
||||
if (!keys.length) return;
|
||||
|
||||
PlayerClient.prototype.queueFiles = function(files, previous_key, next_key, is_random){
|
||||
var ref$, items, i, len$, file, sort_key, id;
|
||||
if (!files.length) {
|
||||
return;
|
||||
if (previousKey == null && nextKey == null) {
|
||||
var defaultPos = this.getDefaultQueuePosition();
|
||||
previousKey = defaultPos.previousKey;
|
||||
nextKey = defaultPos.nextKey;
|
||||
}
|
||||
is_random = Boolean(is_random);
|
||||
if (previous_key == null && next_key == null) {
|
||||
ref$ = this.getDefaultQueuePosition(), previous_key = ref$.previous_key, next_key = ref$.next_key;
|
||||
}
|
||||
items = {};
|
||||
for (i = 0, len$ = files.length; i < len$; ++i) {
|
||||
file = files[i];
|
||||
sort_key = this.generateSortKey(previous_key, next_key);
|
||||
id = uuid();
|
||||
|
||||
var items = {};
|
||||
for (var i = 0; i < keys.length; i += 1) {
|
||||
var key = keys[i];
|
||||
var sortKey = keese(previousKey, nextKey);
|
||||
var id = uuid();
|
||||
items[id] = {
|
||||
file: file,
|
||||
sort_key: sort_key,
|
||||
is_random: is_random
|
||||
key: key,
|
||||
sortKey: sortKey,
|
||||
};
|
||||
this.playlist.item_table[id] = {
|
||||
this.playlist.itemTable[id] = {
|
||||
id: id,
|
||||
sort_key: sort_key,
|
||||
is_random: is_random,
|
||||
track: this.library.track_table[file]
|
||||
key: key,
|
||||
sortKey: sortKey,
|
||||
isRandom: false,
|
||||
track: this.library.trackTable[key],
|
||||
};
|
||||
previous_key = sort_key;
|
||||
previousKey = sortKey;
|
||||
}
|
||||
this.refreshPlaylistList();
|
||||
this.sendCommand({
|
||||
name: 'addid',
|
||||
items: items
|
||||
items: items,
|
||||
});
|
||||
this.emit('playlistupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.queueFilesNext = function(files){
|
||||
var curItem = this.status.current_item;
|
||||
var prevKey = curItem && curItem.sort_key;
|
||||
PlayerClient.prototype.queueTracksNext = function(keys) {
|
||||
var prevKey = this.currentItem && this.currentItem.sortKey;
|
||||
var nextKey = null;
|
||||
var itemList = this.playlist.item_list;
|
||||
var itemList = this.playlist.itemList;
|
||||
for (var i = 0; i < itemList.length; ++i) {
|
||||
var track = itemList[i];
|
||||
if (prevKey == null || track.sort_key > prevKey) {
|
||||
if (nextKey == null || track.sort_key < nextKey) {
|
||||
nextKey = track.sort_key;
|
||||
if (prevKey == null || track.sortKey > prevKey) {
|
||||
if (nextKey == null || track.sortKey < nextKey) {
|
||||
nextKey = track.sortKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.queueFiles(files, prevKey, nextKey);
|
||||
this.queueTracks(keys, prevKey, nextKey);
|
||||
};
|
||||
|
||||
PlayerClient.prototype.clear = function(){
|
||||
|
|
@ -298,133 +245,131 @@ PlayerClient.prototype.shuffle = function(){
|
|||
|
||||
PlayerClient.prototype.stop = function(){
|
||||
this.sendCommandName('stop');
|
||||
this.status.state = "stop";
|
||||
this.state = "stop";
|
||||
this.emit('statusupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.play = function(){
|
||||
this.sendCommandName('play');
|
||||
if (this.status.state === "pause") {
|
||||
this.status.track_start_date = elapsedToDate(this.status.paused_time);
|
||||
this.status.state = "play";
|
||||
if (this.state === "pause") {
|
||||
this.trackStartDate = elapsedToDate(this.pausedTime);
|
||||
this.state = "play";
|
||||
this.emit('statusupdate');
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.pause = function(){
|
||||
this.sendCommandName('pause');
|
||||
if (this.status.state === "play") {
|
||||
this.status.paused_time = dateToElapsed(this.status.track_start_date);
|
||||
this.status.state = "pause";
|
||||
if (this.state === "play") {
|
||||
this.pausedTime = dateToElapsed(this.trackStartDate);
|
||||
this.state = "pause";
|
||||
this.emit('statusupdate');
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.next = function(){
|
||||
var currentItem = this.status.current_item;
|
||||
var pos = currentItem ? currentItem.pos + 1 : 0;
|
||||
var index = this.currentItem ? this.currentItem.index + 1 : 0;
|
||||
|
||||
// handle the case of Repeat All
|
||||
if (pos >= this.playlist.item_list.length &&
|
||||
this.status.repeat === PlayerClient.REPEAT_ALL)
|
||||
if (index >= this.playlist.itemList.length &&
|
||||
this.repeat === PlayerClient.REPEAT_ALL)
|
||||
{
|
||||
pos = 0;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
var item = this.playlist.item_list[pos];
|
||||
var item = this.playlist.itemList[index];
|
||||
var id = item && item.id;
|
||||
|
||||
this.playId(id);
|
||||
};
|
||||
|
||||
PlayerClient.prototype.prev = function(){
|
||||
var currentItem = this.status.current_item;
|
||||
var pos = currentItem ? currentItem.pos - 1 : this.playlist.item_list.length - 1;
|
||||
var index = this.currentItem ? this.currentItem.index - 1 : this.playlist.itemList.length - 1;
|
||||
|
||||
// handle case of Repeat All
|
||||
if (pos < 0 && this.status.repeat === PlayerClient.REPEAT_ALL) {
|
||||
pos = this.playlist.item_list.length - 1;
|
||||
if (index < 0 && this.repeat === PlayerClient.REPEAT_ALL) {
|
||||
index = this.playlist.itemList.length - 1;
|
||||
}
|
||||
|
||||
var item = this.playlist.item_list[pos];
|
||||
var item = this.playlist.itemList[index];
|
||||
var id = item && item.id;
|
||||
|
||||
this.playId(id);
|
||||
};
|
||||
|
||||
PlayerClient.prototype.playId = function(track_id){
|
||||
PlayerClient.prototype.playId = function(trackId){
|
||||
this.sendCommand({
|
||||
name: 'playid',
|
||||
track_id: track_id
|
||||
trackId: trackId
|
||||
});
|
||||
this.anticipatePlayId(track_id);
|
||||
this.anticipatePlayId(trackId);
|
||||
};
|
||||
|
||||
PlayerClient.prototype.moveIds = function(track_ids, previous_key, next_key){
|
||||
var res$, i, len$, id, track, tracks, items, sort_key;
|
||||
res$ = [];
|
||||
for (i = 0, len$ = track_ids.length; i < len$; ++i) {
|
||||
id = track_ids[i];
|
||||
if ((track = this.playlist.item_table[id]) != null) {
|
||||
res$.push(track);
|
||||
}
|
||||
PlayerClient.prototype.moveIds = function(trackIds, previousKey, nextKey){
|
||||
var track, i;
|
||||
var tracks = [];
|
||||
for (i = 0; i < trackIds.length; i += 1) {
|
||||
var id = trackIds[i];
|
||||
track = this.playlist.itemTable[id];
|
||||
if (track) tracks.push(track);
|
||||
}
|
||||
tracks = res$;
|
||||
tracks.sort(compareSortKeyAndId);
|
||||
items = {};
|
||||
for (i = 0, len$ = tracks.length; i < len$; ++i) {
|
||||
var items = {};
|
||||
for (i = 0; i < tracks.length; i += 1) {
|
||||
track = tracks[i];
|
||||
sort_key = this.generateSortKey(previous_key, next_key);
|
||||
items[id] = {
|
||||
sort_key: sort_key
|
||||
var sortKey = keese(previousKey, nextKey);
|
||||
items[track.id] = {
|
||||
sortKey: sortKey,
|
||||
};
|
||||
track.sort_key = sort_key;
|
||||
previous_key = sort_key;
|
||||
track.sortKey = sortKey;
|
||||
previousKey = sortKey;
|
||||
}
|
||||
this.refreshPlaylistList();
|
||||
this.sendCommand({
|
||||
name: 'move',
|
||||
items: items
|
||||
items: items,
|
||||
});
|
||||
this.emit('playlistupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.shiftIds = function(track_id_set, offset){
|
||||
var items, previous_key, next_key, i, ref$, len$, track, sort_key;
|
||||
items = {};
|
||||
previous_key = null;
|
||||
next_key = null;
|
||||
PlayerClient.prototype.shiftIds = function(trackIdSet, offset){
|
||||
var i;
|
||||
var items = {};
|
||||
var previousKey = null;
|
||||
var nextKey = null;
|
||||
var itemList = this.playlist.itemList;
|
||||
var track, sortKey;
|
||||
if (offset < 0) {
|
||||
for (i = 0, len$ = (ref$ = this.playlist.item_list).length; i < len$; ++i) {
|
||||
track = ref$[i];
|
||||
if (track.id in track_id_set) {
|
||||
if (next_key == null) {
|
||||
for (i = 0; i < itemList.length; i += 1) {
|
||||
track = itemList[i];
|
||||
if (track.id in trackIdSet) {
|
||||
if (nextKey == null) {
|
||||
continue;
|
||||
}
|
||||
sort_key = this.generateSortKey(previous_key, next_key);
|
||||
sortKey = keese(previousKey, nextKey);
|
||||
items[track.id] = {
|
||||
sort_key: sort_key
|
||||
sortKey: sortKey
|
||||
};
|
||||
track.sort_key = sort_key;
|
||||
track.sortKey = sortKey;
|
||||
}
|
||||
previous_key = next_key;
|
||||
next_key = track.sort_key;
|
||||
previousKey = nextKey;
|
||||
nextKey = track.sortKey;
|
||||
}
|
||||
} else {
|
||||
for (i = (ref$ = this.playlist.item_list).length - 1; i >= 0; --i) {
|
||||
track = ref$[i];
|
||||
if (track.id in track_id_set) {
|
||||
if (previous_key == null) {
|
||||
for (i = itemList.length - 1; i >= 0; i -= 1) {
|
||||
track = itemList[i];
|
||||
if (track.id in trackIdSet) {
|
||||
if (previousKey == null) {
|
||||
continue;
|
||||
}
|
||||
sort_key = this.generateSortKey(previous_key, next_key);
|
||||
sortKey = keese(previousKey, nextKey);
|
||||
items[track.id] = {
|
||||
sort_key: sort_key
|
||||
sortKey: sortKey
|
||||
};
|
||||
track.sort_key = sort_key;
|
||||
track.sortKey = sortKey;
|
||||
}
|
||||
next_key = previous_key;
|
||||
previous_key = track.sort_key;
|
||||
nextKey = previousKey;
|
||||
previousKey = track.sortKey;
|
||||
}
|
||||
}
|
||||
this.refreshPlaylistList();
|
||||
|
|
@ -435,20 +380,18 @@ PlayerClient.prototype.shiftIds = function(track_id_set, offset){
|
|||
this.emit('playlistupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.removeIds = function(track_ids){
|
||||
var ids, i, len$, track_id, ref$, item;
|
||||
if (track_ids.length === 0) {
|
||||
return;
|
||||
}
|
||||
ids = [];
|
||||
for (i = 0, len$ = track_ids.length; i < len$; ++i) {
|
||||
track_id = track_ids[i];
|
||||
if (((ref$ = this.status.current_item) != null ? ref$.id : void 8) === track_id) {
|
||||
this.status.current_item = null;
|
||||
PlayerClient.prototype.removeIds = function(trackIds){
|
||||
if (trackIds.length === 0) return;
|
||||
var ids = [];
|
||||
for (var i = 0; i < trackIds.length; i += 1) {
|
||||
var trackId = trackIds[i];
|
||||
var currentId = this.currentItem && this.currentItem.id;
|
||||
if (currentId === trackId) {
|
||||
this.currentItem = null;
|
||||
}
|
||||
ids.push(track_id);
|
||||
item = this.playlist.item_table[track_id];
|
||||
delete this.playlist.item_table[item.id];
|
||||
ids.push(trackId);
|
||||
var item = this.playlist.itemTable[trackId];
|
||||
delete this.playlist.itemTable[item.id];
|
||||
this.refreshPlaylistList();
|
||||
}
|
||||
this.sendCommand({
|
||||
|
|
@ -458,15 +401,15 @@ PlayerClient.prototype.removeIds = function(track_ids){
|
|||
this.emit('playlistupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.seek = function(pos){
|
||||
PlayerClient.prototype.seek = function(pos) {
|
||||
pos = parseFloat(pos, 10);
|
||||
if (pos < 0) pos = 0;
|
||||
if (pos > this.status.time) pos = this.status.time;
|
||||
if (pos > this.duration) pos = this.duration;
|
||||
this.sendCommand({
|
||||
name: 'seek',
|
||||
pos: pos
|
||||
pos: pos,
|
||||
});
|
||||
this.status.track_start_date = elapsedToDate(pos);
|
||||
this.trackStartDate = elapsedToDate(pos);
|
||||
this.emit('statusupdate');
|
||||
};
|
||||
|
||||
|
|
@ -475,12 +418,12 @@ PlayerClient.prototype.setVolume = function(vol){
|
|||
name: "setvol",
|
||||
vol: vol,
|
||||
});
|
||||
this.status.volume = vol;
|
||||
this.volume = vol;
|
||||
this.emit('statusupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.setRepeatMode = function(mode) {
|
||||
this.status.repeat = mode;
|
||||
this.repeat = mode;
|
||||
this.sendCommand({
|
||||
name: 'repeat',
|
||||
mode: mode,
|
||||
|
|
@ -489,7 +432,6 @@ PlayerClient.prototype.setRepeatMode = function(mode) {
|
|||
};
|
||||
|
||||
PlayerClient.prototype.authenticate = function(password, callback){
|
||||
var this$ = this;
|
||||
callback = callback || noop;
|
||||
this.sendCommand({
|
||||
name: 'password',
|
||||
|
|
@ -508,19 +450,20 @@ PlayerClient.prototype.sendCommandName = function(name, cb){
|
|||
|
||||
PlayerClient.prototype.sendCommand = function(cmd, cb){
|
||||
cb = cb || noop;
|
||||
var callback_id = this.next_response_handler_id++;
|
||||
this.response_handlers[callback_id] = cb;
|
||||
var callbackId = this.nextResponseHandlerId++;
|
||||
this.responseHandlers[callbackId] = cb;
|
||||
this.socket.emit('request', JSON.stringify({
|
||||
cmd: cmd,
|
||||
callback_id: callback_id
|
||||
callbackId: callbackId
|
||||
}));
|
||||
};
|
||||
|
||||
PlayerClient.prototype.handleResponse = function(arg){
|
||||
var err, msg, callback_id, handler;
|
||||
err = arg.err, msg = arg.msg, callback_id = arg.callback_id;
|
||||
handler = this.response_handlers[callback_id];
|
||||
delete this.response_handlers[callback_id];
|
||||
var err = arg.err;
|
||||
var msg = arg.msg;
|
||||
var callbackId = arg.callbackId;
|
||||
var handler = this.responseHandlers[callbackId];
|
||||
delete this.responseHandlers[callbackId];
|
||||
handler(err, msg);
|
||||
};
|
||||
|
||||
|
|
@ -534,160 +477,62 @@ PlayerClient.prototype.handleStatus = function(systems){
|
|||
|
||||
PlayerClient.prototype.clearPlaylist = function(){
|
||||
this.playlist = {
|
||||
item_list: [],
|
||||
item_table: {},
|
||||
pos: null,
|
||||
itemList: [],
|
||||
itemTable: {},
|
||||
index: null,
|
||||
name: null
|
||||
};
|
||||
};
|
||||
|
||||
PlayerClient.prototype.anticipatePlayId = function(track_id){
|
||||
var item = this.playlist.item_table[track_id];
|
||||
this.status.current_item = item;
|
||||
this.status.state = "play";
|
||||
this.status.time = item.track.time;
|
||||
this.status.track_start_date = new Date();
|
||||
PlayerClient.prototype.anticipatePlayId = function(trackId){
|
||||
var item = this.playlist.itemTable[trackId];
|
||||
this.currentItem = item;
|
||||
this.state = "play";
|
||||
this.duration = item.track.duration;
|
||||
this.trackStartDate = new Date();
|
||||
this.emit('statusupdate');
|
||||
};
|
||||
|
||||
PlayerClient.prototype.anticipateSkip = function(direction){
|
||||
var ref$, next_item;
|
||||
next_item = this.playlist.item_list[((ref$ = this.status.current_item) != null ? ref$.pos : void 8) + direction];
|
||||
if (next_item != null) {
|
||||
this.anticipatePlayId(next_item.id);
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.buildArtistAlbumTree = function(tracks, library){
|
||||
var ref$;
|
||||
var len$, track, album_key, album, artist_table, k, album_artists, i, ref1$, album_artist_name, artist_key, artist, various_artist;
|
||||
library.track_table = {};
|
||||
library.album_table = {};
|
||||
for (i = 0, len$ = tracks.length; i < len$; ++i) {
|
||||
track = tracks[i];
|
||||
library.track_table[track.file] = track;
|
||||
album_key = albumKey(track);
|
||||
album = getOrCreate(album_key, library.album_table, fn$);
|
||||
track.album = album;
|
||||
album.tracks.push(track);
|
||||
if (album.year == null) {
|
||||
album.year = track.year;
|
||||
}
|
||||
}
|
||||
artist_table = {};
|
||||
for (k in library.album_table) {
|
||||
album = library.album_table[k];
|
||||
album_artists = {};
|
||||
album.tracks.sort(trackComparator);
|
||||
for (i = 0, len$ = (ref1$ = album.tracks).length; i < len$; ++i) {
|
||||
track = ref1$[i];
|
||||
track.pos = i;
|
||||
album_artist_name = track.album_artist_name;
|
||||
album_artists[artistKey(album_artist_name)] = true;
|
||||
album_artists[artistKey(track.artist_name)] = true;
|
||||
}
|
||||
if (moreThanOneKey(album_artists)) {
|
||||
album_artist_name = VARIOUS_ARTISTS_NAME;
|
||||
artist_key = VARIOUS_ARTISTS_KEY;
|
||||
for (i = 0, len$ = (ref1$ = album.tracks).length; i < len$; ++i) {
|
||||
track = ref1$[i];
|
||||
track.artist_disambiguation = track.artist_name;
|
||||
}
|
||||
} else {
|
||||
artist_key = artistKey(album_artist_name);
|
||||
}
|
||||
artist = getOrCreate(artist_key, artist_table, fn1$);
|
||||
album.artist = artist;
|
||||
artist.albums.push(album);
|
||||
}
|
||||
library.artists = [];
|
||||
various_artist = null;
|
||||
for (k in artist_table) {
|
||||
artist = artist_table[k];
|
||||
artist.albums.sort(albumComparator);
|
||||
for (i = 0, len$ = (ref$ = artist.albums).length; i < len$; ++i) {
|
||||
album = ref$[i];
|
||||
album.pos = i;
|
||||
}
|
||||
if (artist.key === VARIOUS_ARTISTS_KEY) {
|
||||
various_artist = artist;
|
||||
} else {
|
||||
library.artists.push(artist);
|
||||
}
|
||||
}
|
||||
library.artists.sort(artistComparator);
|
||||
if (various_artist != null) {
|
||||
library.artists.splice(0, 0, various_artist);
|
||||
}
|
||||
for (i = 0, len$ = (ref$ = library.artists).length; i < len$; ++i) {
|
||||
artist = ref$[i];
|
||||
artist.pos = i;
|
||||
}
|
||||
library.artist_table = artist_table;
|
||||
function fn$(){
|
||||
return {
|
||||
name: track.album_name,
|
||||
year: track.year,
|
||||
tracks: [],
|
||||
key: album_key
|
||||
};
|
||||
}
|
||||
function fn1$(){
|
||||
return {
|
||||
name: album_artist_name,
|
||||
albums: [],
|
||||
key: artist_key
|
||||
};
|
||||
PlayerClient.prototype.anticipateSkip = function(direction) {
|
||||
if (this.currentItem) {
|
||||
var nextItem = this.playlist.itemList[this.currentItem.index + direction];
|
||||
if (nextItem) this.anticipatePlayId(nextItem.id);
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.refreshPlaylistList = function(){
|
||||
var ref$;
|
||||
var id, item, i, len$;
|
||||
this.playlist.item_list = [];
|
||||
for (id in this.playlist.item_table) {
|
||||
item = this.playlist.item_table[id];
|
||||
this.playlist.itemList = [];
|
||||
var item;
|
||||
for (var id in this.playlist.itemTable) {
|
||||
item = this.playlist.itemTable[id];
|
||||
item.playlist = this.playlist;
|
||||
this.playlist.item_list.push(item);
|
||||
this.playlist.itemList.push(item);
|
||||
}
|
||||
this.playlist.item_list.sort(compareSortKeyAndId);
|
||||
for (i = 0, len$ = (ref$ = this.playlist.item_list).length; i < len$; ++i) {
|
||||
item = ref$[i];
|
||||
item.pos = i;
|
||||
this.playlist.itemList.sort(compareSortKeyAndId);
|
||||
for (var i = 0; i < this.playlist.itemList.length; i += 1) {
|
||||
item = this.playlist.itemList[i];
|
||||
item.index = i;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerClient.prototype.resetServerState = function(){
|
||||
this.buffer = "";
|
||||
this.response_handlers = {};
|
||||
this.next_response_handler_id = 0;
|
||||
this.have_file_list_cache = false;
|
||||
this.library = {
|
||||
artists: [],
|
||||
track_table: {}
|
||||
};
|
||||
this.search_results = this.library;
|
||||
this.last_query = "";
|
||||
this.responseHandlers = {};
|
||||
this.nextResponseHandlerId = 0;
|
||||
this.haveFileListCache = false;
|
||||
this.library = new MusicLibraryIndex({
|
||||
searchFields: MusicLibraryIndex.defaultSearchFields.concat('file'),
|
||||
});
|
||||
this.searchResults = this.library;
|
||||
this.lastQuery = "";
|
||||
this.clearPlaylist();
|
||||
this.status = {
|
||||
repeat: 0,
|
||||
current_item: null
|
||||
};
|
||||
this.repeat = 0;
|
||||
this.currentItem = null;
|
||||
|
||||
this.stored_playlist_table = {};
|
||||
this.stored_playlist_item_table = {};
|
||||
this.stored_playlists = [];
|
||||
};
|
||||
|
||||
function stripPrefixes(str){
|
||||
var i, ref$, len$, regex;
|
||||
for (i = 0, len$ = (ref$ = PREFIXES_TO_STRIP).length; i < len$; ++i) {
|
||||
regex = ref$[i];
|
||||
str = str.replace(regex, '');
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function elapsedToDate(elapsed){
|
||||
return new Date(new Date() - elapsed * 1000);
|
||||
}
|
||||
|
|
@ -696,106 +541,10 @@ function dateToElapsed(date){
|
|||
return (new Date() - date) / 1000;
|
||||
}
|
||||
|
||||
function sortableTitle(title){
|
||||
return stripPrefixes(formatSearchable(title));
|
||||
}
|
||||
|
||||
function titleCompare(a, b){
|
||||
var _a = sortableTitle(a);
|
||||
var _b = sortableTitle(b);
|
||||
if (_a < _b) {
|
||||
return -1;
|
||||
} else if (_a > _b) {
|
||||
return 1;
|
||||
} else {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function noop(err){
|
||||
if (err) throw err;
|
||||
}
|
||||
|
||||
function getOrCreate(key, table, initObjFunc){
|
||||
var result;
|
||||
result = table[key];
|
||||
if (result == null) {
|
||||
result = initObjFunc();
|
||||
table[key] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function trackComparator(a, b){
|
||||
if (a.track < b.track) {
|
||||
return -1;
|
||||
} else if (a.track > b.track) {
|
||||
return 1;
|
||||
} else {
|
||||
return titleCompare(a.name, b.name);
|
||||
}
|
||||
}
|
||||
|
||||
function albumComparator(a, b){
|
||||
if (a.year < b.year) {
|
||||
return -1;
|
||||
} else if (a.year > b.year) {
|
||||
return 1;
|
||||
} else {
|
||||
return titleCompare(a.name, b.name);
|
||||
}
|
||||
}
|
||||
|
||||
function artistComparator(a, b){
|
||||
return titleCompare(a.name, b.name);
|
||||
}
|
||||
|
||||
function albumKey(track){
|
||||
if (track.album_name) {
|
||||
return formatSearchable(track.album_name);
|
||||
} else {
|
||||
return formatSearchable(track.album_artist_name);
|
||||
}
|
||||
}
|
||||
|
||||
function artistKey(artist_name){
|
||||
return formatSearchable(artist_name);
|
||||
}
|
||||
|
||||
function moreThanOneKey(object){
|
||||
var count = -2;
|
||||
for (var k in object) {
|
||||
if (!++count) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addSearchTags(tracks){
|
||||
var i, len$, track;
|
||||
for (i = 0, len$ = tracks.length; i < len$; ++i) {
|
||||
track = tracks[i];
|
||||
track.search_tags = formatSearchable([
|
||||
track.artist_name,
|
||||
track.album_artist_name,
|
||||
track.album_name,
|
||||
track.name,
|
||||
track.file,
|
||||
].join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
function formatSearchable(str) {
|
||||
return removeDiacritics(str).toLowerCase();
|
||||
}
|
||||
|
||||
function operatorCompare(a, b){
|
||||
if (a === b) {
|
||||
return 0;
|
||||
|
|
@ -808,14 +557,11 @@ function operatorCompare(a, b){
|
|||
}
|
||||
|
||||
function makeCompareProps(props){
|
||||
return function(a, b){
|
||||
var i, ref$, len$, prop, result;
|
||||
for (i = 0, len$ = (ref$ = props).length; i < len$; ++i) {
|
||||
prop = ref$[i];
|
||||
result = operatorCompare(a[prop], b[prop]);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return function(a, b) {
|
||||
for (var i = 0; i < props.length; i += 1) {
|
||||
var prop = props[i];
|
||||
var result = operatorCompare(a[prop], b[prop]);
|
||||
if (result) return result;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ function getUrl(){
|
|||
}
|
||||
|
||||
function updatePlayer() {
|
||||
var should_stream = trying_to_stream && player.status.state === "play";
|
||||
var should_stream = trying_to_stream && player.state === "play";
|
||||
if (actually_streaming === should_stream) return;
|
||||
if (should_stream) {
|
||||
soundManager.destroySound('stream');
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
{{#albums}}
|
||||
{{#albumList}}
|
||||
<li>
|
||||
<div class="clickable expandable" id="{{albumid key}}" data-key="{{key}}" data-type="album">
|
||||
<div class="ui-icon ui-icon-triangle-1-e"></div>
|
||||
<span>{{#if name}}{{name}}{{else}}[Unknown Album]{{/if}}</span>
|
||||
</div>
|
||||
<ul style="display: none;">
|
||||
{{#tracks}}
|
||||
{{#trackList}}
|
||||
<li>
|
||||
<div class="clickable" id="{{trackid file}}" data-key="{{file}}" data-type="track">
|
||||
<span>{{#if track}}{{track}}. {{/if}}{{#if artist_disambiguation}}{{artist_disambiguation}} - {{/if}}{{name}}</span>
|
||||
<div class="clickable" id="{{trackid key}}" data-key="{{key}}" data-type="track">
|
||||
<span>{{#if track}}{{track}}. {{/if}}{{#if compilation}}{{artistName}} - {{/if}}{{name}}</span>
|
||||
</div>
|
||||
</li>
|
||||
{{/tracks}}
|
||||
{{/trackList}}
|
||||
</ul>
|
||||
</li>
|
||||
{{/albums}}
|
||||
{{/albumList}}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{{#if artists}}
|
||||
{{#if artistList}}
|
||||
<ul>
|
||||
{{#artists}}
|
||||
{{#artistList}}
|
||||
<li>
|
||||
<div class="clickable expandable" id="{{artistid key}}" data-key="{{key}}" data-type="artist">
|
||||
<div class="ui-icon ui-icon-triangle-1-e"></div>
|
||||
|
|
@ -8,11 +8,11 @@
|
|||
</div>
|
||||
<ul></ul>
|
||||
</li>
|
||||
{{/artists}}
|
||||
{{/artistList}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="ui-state-highlight ui-corner-all">
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
<strong>{{empty_library_message}}</strong>
|
||||
<strong>{{emptyLibraryMessage}}</strong>
|
||||
</p>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<div class="pl-item" id="playlist-track-{{id}}" data-id="{{id}}">
|
||||
<span class="track">{{track.track}}</span>
|
||||
<span class="title">{{track.name}}</span>
|
||||
<span class="artist">{{track.artist_name}}</span>
|
||||
<span class="album">{{track.album_name}}</span>
|
||||
<span class="time">{{time track.time}}</span>
|
||||
<span class="artist">{{track.artistName}}</span>
|
||||
<span class="album">{{track.albumName}}</span>
|
||||
<span class="time">{{time track.duration}}</span>
|
||||
</div>
|
||||
{{/playlist}}
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{{#item_list}}
|
||||
{{#itemList}}
|
||||
<li>
|
||||
<div class="clickable" id="{{storedplaylistitemid id}}" data-key="{{id}}" data-type="stored_playlist_item">
|
||||
<span>
|
||||
{{#track}}
|
||||
{{#if artist_name}}{{artist_name}} - {{/if}}{{name}}
|
||||
{{#if artistName}}{{artistName}} - {{/if}}{{name}}
|
||||
{{/track}}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{{/item_list}}
|
||||
{{/itemList}}
|
||||
|
|
|
|||
Loading…
Reference in a new issue