persist playlist on server restart. closes #98
This commit is contained in:
parent
6068a19f3e
commit
ea77be50b5
1 changed files with 93 additions and 26 deletions
119
lib/player.js
119
lib/player.js
|
|
@ -27,6 +27,7 @@ var cpuCount = require('os').cpus().length;
|
|||
var PLAYER_KEY_PREFIX = "Player.";
|
||||
var LIBRARY_KEY_PREFIX = "Library.";
|
||||
var LIBRARY_DIR_PREFIX = "LibraryDir.";
|
||||
var PLAYLIST_KEY_PREFIX = "Playlist.";
|
||||
|
||||
// these are the ones we store in the DB, not the ones we send to the web
|
||||
var DB_FILE_PROPS = [
|
||||
|
|
@ -201,14 +202,14 @@ Player.prototype.initialize = function(cb) {
|
|||
var posMs = playHead.pos * 1000;
|
||||
self.trackStartDate = new Date(nowMs - posMs);
|
||||
self.currentTrack = self.grooveItems[playHead.item.id];
|
||||
self.emit('currentTrack');
|
||||
playlistChanged(self);
|
||||
self.emit('currentTrack');
|
||||
} else if (!decodeHead.item) {
|
||||
// both play head and decode head are null. end of playlist.
|
||||
console.log("end of playlist");
|
||||
self.currentTrack = null;
|
||||
self.emit('currentTrack');
|
||||
playlistChanged(self);
|
||||
self.emit('currentTrack');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,12 +220,36 @@ Player.prototype.initialize = function(cb) {
|
|||
pend.go(cacheAllDb);
|
||||
pend.go(cacheAllDirs);
|
||||
pend.go(cacheAllOptions);
|
||||
pend.go(cacheAllPlaylist);
|
||||
pend.wait(function(err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
if (err) return cb(err);
|
||||
playlistChanged(self);
|
||||
lazyReplayGainScanPlaylist(self);
|
||||
self.requestUpdateDb();
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function cacheAllPlaylist(cb) {
|
||||
var stream = self.db.createReadStream({
|
||||
start: PLAYLIST_KEY_PREFIX,
|
||||
});
|
||||
stream.on('data', function(data) {
|
||||
if (data.key.indexOf(PLAYLIST_KEY_PREFIX) !== 0) {
|
||||
stream.removeAllListeners();
|
||||
stream.destroy();
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
self.requestUpdateDb();
|
||||
var plEntry = JSON.parse(data.value);
|
||||
self.playlist[plEntry.id] = plEntry;
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
stream.removeAllListeners();
|
||||
stream.destroy();
|
||||
cb(err);
|
||||
});
|
||||
stream.on('close', function() {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
|
@ -379,14 +404,15 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
var i;
|
||||
for (var scannedDirName in scannedDirs) {
|
||||
var dirEntry = scannedDirs[scannedDirName];
|
||||
self.persistDirEntry(dirEntry);
|
||||
var deletedFiles = [];
|
||||
for (baseName in dirEntry.entries) {
|
||||
id = dirEntry.entries[baseName];
|
||||
if (id !== thisScanId) deletedFiles.push(baseName);
|
||||
}
|
||||
for (i = 0; i < deletedFiles.length; i += 1) {
|
||||
onFileMissing(dirEntry, deletedFiles[i]);
|
||||
baseName = deletedFiles[i];
|
||||
delete dirEntry.entries[baseName];
|
||||
onFileMissing(dirEntry, baseName);
|
||||
}
|
||||
|
||||
var deletedDirs = [];
|
||||
|
|
@ -395,8 +421,12 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
if (id !== thisScanId) deletedDirs.push(baseName);
|
||||
}
|
||||
for (i = 0; i < deletedDirs.length; i += 1) {
|
||||
onDirMissing(dirEntry, deletedDirs[i]);
|
||||
baseName = deletedDirs[i];
|
||||
delete dirEntry.dirEntries[baseName];
|
||||
onDirMissing(dirEntry, baseName);
|
||||
}
|
||||
|
||||
self.persistDirEntry(dirEntry);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
|
|
@ -665,6 +695,16 @@ Player.prototype.persist = function(dbFile, cb) {
|
|||
}
|
||||
};
|
||||
|
||||
Player.prototype.persistPlaylistItem = function(item, cb) {
|
||||
this.db.put(PLAYLIST_KEY_PREFIX + item.id, serializePlaylistItem(item), cb || logIfError);
|
||||
|
||||
function logIfError(err) {
|
||||
if (err) {
|
||||
console.error("unable to persist playlist item:", item, err.stack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Player.prototype.persistOption = function(name, value, cb) {
|
||||
this.db.put(PLAYER_KEY_PREFIX + name, JSON.stringify(value), cb || logIfError);
|
||||
function logIfError(err) {
|
||||
|
|
@ -755,23 +795,14 @@ Player.prototype.addItems = function(items, tagAsRandom) {
|
|||
deleted: false,
|
||||
};
|
||||
self.playlist[id] = playlistItem;
|
||||
self.persistPlaylistItem(playlistItem);
|
||||
}
|
||||
playlistChanged(self);
|
||||
lazyReplayGainScanPlaylist(self);
|
||||
}
|
||||
|
||||
Player.prototype.clearPlaylist = function() {
|
||||
for (var id in this.playlist) {
|
||||
var track = this.playlist[id];
|
||||
if (track.grooveFile) closeFile(track.grooveFile);
|
||||
// we set this so that any callbacks that return which were trying to
|
||||
// set the groveItem can check if the item got deleted
|
||||
track.deleted = true;
|
||||
}
|
||||
this.playlist = {};
|
||||
this.currentTrack = null;
|
||||
this.emit('currentTrack');
|
||||
playlistChanged(this);
|
||||
this.removePlaylistItems(Object.keys(this.playlist));
|
||||
}
|
||||
|
||||
Player.prototype.shufflePlaylist = function() {
|
||||
|
|
@ -787,10 +818,34 @@ Player.prototype.shufflePlaylist = function() {
|
|||
}
|
||||
|
||||
Player.prototype.removePlaylistItems = function(ids) {
|
||||
ids.forEach(function(id) {
|
||||
var delCmds = [];
|
||||
var currentTrackChanged = false;
|
||||
for (var i = 0; i < ids.length; i += 1) {
|
||||
var id = ids[i];
|
||||
delCmds.push({type: 'del', key: PLAYLIST_KEY_PREFIX + id});
|
||||
|
||||
var item = this.playlist[id];
|
||||
if (item.grooveFile) closeFile(item.grooveFile);
|
||||
// we set this so that any callbacks that return which were trying to
|
||||
// set the grooveItem can check if the item got deleted
|
||||
item.deleted = true;
|
||||
if (item === this.currentTrack) {
|
||||
this.currentTrack = null;
|
||||
currentTrackChanged = true;
|
||||
}
|
||||
|
||||
delete this.playlist[id];
|
||||
}.bind(this));
|
||||
}
|
||||
if (delCmds.length > 0) this.db.batch(delCmds, logIfError);
|
||||
|
||||
playlistChanged(this);
|
||||
if (currentTrackChanged) this.emit('currentTrack');
|
||||
|
||||
function logIfError(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting playlist entries from db:", err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// items looks like {id: {sortKey}}
|
||||
|
|
@ -799,6 +854,7 @@ Player.prototype.movePlaylistItems = function(items) {
|
|||
var track = this.playlist[id];
|
||||
if (track == null) continue; // race conditions, etc.
|
||||
track.sortKey = items[id].sortKey;
|
||||
this.persistPlaylistItem(track);
|
||||
}
|
||||
playlistChanged(this);
|
||||
}
|
||||
|
|
@ -829,6 +885,7 @@ Player.prototype.moveIdsToPos = function(ids, toPos) {
|
|||
var thisSortKey = keese(prevSortKey, nextSortKey);
|
||||
prevSortKey = thisSortKey;
|
||||
track.sortKey = thisSortKey;
|
||||
this.persistPlaylistItem(track);
|
||||
}
|
||||
playlistChanged(this);
|
||||
}
|
||||
|
|
@ -838,8 +895,8 @@ Player.prototype.pause = function() {
|
|||
this.isPlaying = false;
|
||||
this.pausedTime = (new Date() - this.trackStartDate) / 1000;
|
||||
this.groovePlaylist.pause();
|
||||
this.emit('currentTrack');
|
||||
playlistChanged(this);
|
||||
this.emit('currentTrack');
|
||||
}
|
||||
|
||||
Player.prototype.play = function() {
|
||||
|
|
@ -850,8 +907,8 @@ Player.prototype.play = function() {
|
|||
}
|
||||
this.groovePlaylist.play();
|
||||
this.isPlaying = true;
|
||||
this.emit('currentTrack');
|
||||
playlistChanged(this);
|
||||
this.emit('currentTrack');
|
||||
}
|
||||
|
||||
// This function should be avoided in favor of seek. Note that it is called by
|
||||
|
|
@ -861,8 +918,8 @@ Player.prototype.seekToIndex = function(index, pos) {
|
|||
this.isPlaying = true;
|
||||
this.groovePlaylist.play();
|
||||
this.seekRequestPos = pos;
|
||||
this.emit('currentTrack');
|
||||
playlistChanged(this);
|
||||
this.emit('currentTrack');
|
||||
};
|
||||
|
||||
Player.prototype.seek = function(id, pos) {
|
||||
|
|
@ -870,8 +927,8 @@ Player.prototype.seek = function(id, pos) {
|
|||
this.isPlaying = true;
|
||||
this.groovePlaylist.play();
|
||||
this.seekRequestPos = pos;
|
||||
this.emit('currentTrack');
|
||||
playlistChanged(this);
|
||||
this.emit('currentTrack');
|
||||
}
|
||||
|
||||
Player.prototype.next = function() {
|
||||
|
|
@ -900,8 +957,8 @@ Player.prototype.setRepeat = function(value) {
|
|||
if (value === this.repeat) return;
|
||||
this.repeat = value;
|
||||
this.persistOption('repeat', this.repeat);
|
||||
this.emit('repeatUpdate');
|
||||
playlistChanged(this);
|
||||
this.emit('repeatUpdate');
|
||||
};
|
||||
|
||||
Player.prototype.setDynamicModeOn = function(value) {
|
||||
|
|
@ -1209,6 +1266,7 @@ function lazyReplayGainScanPlaylist(self) {
|
|||
var albumGain = {};
|
||||
self.tracksInOrder.forEach(function(track) {
|
||||
var dbFile = self.libraryIndex.trackTable[track.key];
|
||||
if (!dbFile) return;
|
||||
var albumKey = self.libraryIndex.getAlbumKey(dbFile);
|
||||
var needScan = dbFile.replayGainAlbumGain == null ||
|
||||
dbFile.replayGainTrackGain == null ||
|
||||
|
|
@ -1409,6 +1467,15 @@ function deserializeFileData(dataStr) {
|
|||
return JSON.parse(dataStr);
|
||||
}
|
||||
|
||||
function serializePlaylistItem(item) {
|
||||
return JSON.stringify({
|
||||
id: item.id,
|
||||
key: item.key,
|
||||
sortKey: item.sortKey,
|
||||
isRandom: item.isRandom,
|
||||
});
|
||||
}
|
||||
|
||||
function trackWithoutIndex(props, dbFile) {
|
||||
var out = {};
|
||||
props.forEach(function(propName) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue