347 lines
9.3 KiB
JavaScript
347 lines
9.3 KiB
JavaScript
var uuid = require('uuid');
|
|
var jsondiffpatch = require('jsondiffpatch');
|
|
var Player = require('./player');
|
|
|
|
module.exports = PlayerServer;
|
|
|
|
PlayerServer.plugins = [];
|
|
|
|
PlayerServer.actions = {
|
|
'addid': {
|
|
permission: 'add',
|
|
args: 'object',
|
|
fn: function(self, client, items) {
|
|
self.player.addItems(items);
|
|
},
|
|
},
|
|
'clear': {
|
|
permission: 'control',
|
|
fn: function(self) {
|
|
self.player.clearPlaylist();
|
|
},
|
|
},
|
|
'deleteTracks': {
|
|
permission: 'admin',
|
|
args: 'array',
|
|
fn: function(self, client, keys) {
|
|
for (var i = 0; i < keys.length; i += 1) {
|
|
var key = keys[i];
|
|
self.player.deleteFile(key);
|
|
}
|
|
},
|
|
},
|
|
'deleteid': {
|
|
permission: 'control',
|
|
args: 'array',
|
|
fn: function(self, client, ids) {
|
|
self.player.removePlaylistItems(ids);
|
|
},
|
|
},
|
|
'dynamicModeOn': {
|
|
permission: 'control',
|
|
args: 'boolean',
|
|
fn: function(self, client, on) {
|
|
self.player.setDynamicModeOn(on);
|
|
},
|
|
},
|
|
'dynamicModeHistorySize': {
|
|
permission: 'control',
|
|
args: 'number',
|
|
fn: function(self, client, size) {
|
|
self.player.setDynamicModeHistorySize(size);
|
|
},
|
|
},
|
|
'dynamicModeFutureSize': {
|
|
permission: 'control',
|
|
args: 'number',
|
|
fn: function(self, client, size) {
|
|
self.player.setDynamicModeFutureSize(size);
|
|
},
|
|
},
|
|
'importUrl': {
|
|
permission: 'control',
|
|
args: 'object',
|
|
fn: function(self, client, args) {
|
|
var urlString = String(args.url);
|
|
var id = args.id;
|
|
self.player.importUrl(urlString, function(err, dbFile) {
|
|
var key = null;
|
|
if (err) {
|
|
console.error("Unable to import url:", urlString, "error:", err.stack);
|
|
} else if (!dbFile) {
|
|
console.error("Unable to import file due to race condition.");
|
|
} else {
|
|
key = dbFile.key;
|
|
}
|
|
client.sendMessage('importUrl', {id: id, key: key});
|
|
});
|
|
},
|
|
},
|
|
'subscribe': {
|
|
permission: 'read',
|
|
args: 'object',
|
|
fn: function(self, client, args) {
|
|
var name = args.name;
|
|
var subscription = self.subscriptions[name];
|
|
if (!subscription) {
|
|
console.warn("Invalid subscription item:", name);
|
|
return;
|
|
}
|
|
if (args.delta) {
|
|
client.subscriptions[name] = 'delta';
|
|
if (args.version !== subscription.version) {
|
|
client.sendMessage(name, {
|
|
version: subscription.version,
|
|
reset: true,
|
|
delta: jsondiffpatch.diff(undefined, subscription.value),
|
|
});
|
|
}
|
|
} else {
|
|
client.subscriptions[name] = 'simple';
|
|
client.sendMessage(name, subscription.value);
|
|
}
|
|
},
|
|
},
|
|
'updateTags': {
|
|
permission: 'admin',
|
|
args: 'object',
|
|
fn: function(self, client, obj) {
|
|
self.player.updateTags(obj);
|
|
},
|
|
},
|
|
'unsubscribe': {
|
|
permission: 'read',
|
|
args: 'string',
|
|
fn: function(self, client, name) {
|
|
delete client.subscriptions[name];
|
|
},
|
|
},
|
|
'move': {
|
|
permission: 'control',
|
|
args: 'object',
|
|
fn: function(self, client, items) {
|
|
self.player.movePlaylistItems(items);
|
|
},
|
|
},
|
|
'password': {
|
|
permission: null,
|
|
args: 'string',
|
|
fn: function(self, client, password) {
|
|
var perms = self.authenticate(password);
|
|
var success = perms != null;
|
|
if (success) client.permissions = perms;
|
|
client.sendMessage('permissions', client.permissions);
|
|
},
|
|
},
|
|
'pause': {
|
|
permission: 'control',
|
|
fn: function(self, client) {
|
|
self.player.pause();
|
|
},
|
|
},
|
|
'play': {
|
|
permission: 'control',
|
|
fn: function(self, client) {
|
|
self.player.play();
|
|
},
|
|
},
|
|
'seek': {
|
|
permission: 'control',
|
|
args: 'object',
|
|
fn: function(self, client, args) {
|
|
self.player.seek(args.id, args.pos);
|
|
},
|
|
},
|
|
'repeat': {
|
|
permission: 'control',
|
|
args: 'number',
|
|
fn: function(self, client, mode) {
|
|
self.player.setRepeat(mode);
|
|
},
|
|
},
|
|
'setvol': {
|
|
permission: 'control',
|
|
args: 'number',
|
|
fn: function(self, client, vol) {
|
|
self.player.setVolume(vol);
|
|
},
|
|
},
|
|
'shuffle': {
|
|
permission: 'control',
|
|
fn: function(self, client) {
|
|
self.player.shufflePlaylist();
|
|
},
|
|
},
|
|
'stop': {
|
|
permission: 'control',
|
|
fn: function(self, client) {
|
|
self.player.stop();
|
|
},
|
|
},
|
|
};
|
|
|
|
function PlayerServer(options) {
|
|
this.player = options.player;
|
|
this.authenticate = options.authenticate;
|
|
this.defaultPermissions = options.defaultPermissions;
|
|
this.subscriptions = {};
|
|
this.clients = [];
|
|
|
|
this.playlistId = uuid();
|
|
this.libraryId = uuid();
|
|
this.initialize();
|
|
}
|
|
|
|
PlayerServer.prototype.initialize = function() {
|
|
var self = this;
|
|
self.player.on('currentTrack', addSubscription('currentTrack', getCurrentTrack));
|
|
self.player.on('dynamicModeOn', addSubscription('dynamicModeOn', getDynamicModeOn));
|
|
self.player.on('dynamicModeHistorySize', addSubscription('dynamicModeHistorySize', getDynamicModeHistorySize));
|
|
self.player.on('dynamicModeFutureSize', addSubscription('dynamicModeFutureSize', getDynamicModeFutureSize));
|
|
self.player.on('repeatUpdate', addSubscription('repeat', getRepeat));
|
|
self.player.on('volumeUpdate', addSubscription('volume', getVolume));
|
|
self.player.on('playlistUpdate', addSubscription('playlist', serializePlaylist));
|
|
|
|
var onLibraryUpdate = addSubscription('library', serializeLibrary);
|
|
self.player.on('addDbTrack', onLibraryUpdate);
|
|
self.player.on('updateDbTrack', onLibraryUpdate);
|
|
self.player.on('deleteDbTrack', onLibraryUpdate);
|
|
self.player.on('scanComplete', onLibraryUpdate);
|
|
|
|
self.player.on('seek', function() {
|
|
self.clients.forEach(function(client) {
|
|
client.sendMessage('seek');
|
|
});
|
|
});
|
|
|
|
setInterval(function() {
|
|
self.clients.forEach(function(client) {
|
|
client.sendMessage('time', new Date());
|
|
});
|
|
}, 30000);
|
|
|
|
function addSubscription(name, serializeFn) {
|
|
var subscription = self.subscriptions[name] = {
|
|
version: uuid(),
|
|
value: serializeFn(),
|
|
};
|
|
return function() {
|
|
var newValue = serializeFn();
|
|
var delta = jsondiffpatch.diff(subscription.value, newValue);
|
|
if (!delta) return; // no delta, nothing to send!
|
|
subscription.value = newValue;
|
|
subscription.version = uuid();
|
|
self.clients.forEach(function(client) {
|
|
var clientSubscription = client.subscriptions[name];
|
|
if (clientSubscription === 'simple') {
|
|
client.sendMessage(name, newValue);
|
|
} else if (clientSubscription === 'delta') {
|
|
client.sendMessage(name, {
|
|
version: subscription.version,
|
|
delta: delta,
|
|
});
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
function getVolume(client) {
|
|
return self.player.volume;
|
|
}
|
|
|
|
function getTime(client) {
|
|
return new Date();
|
|
}
|
|
|
|
function getRepeat(client) {
|
|
return self.player.repeat;
|
|
}
|
|
|
|
function getCurrentTrack() {
|
|
return {
|
|
currentItemId: self.player.currentTrack && self.player.currentTrack.id,
|
|
isPlaying: self.player.isPlaying,
|
|
trackStartDate: self.player.trackStartDate,
|
|
pausedTime: self.player.pausedTime,
|
|
};
|
|
}
|
|
|
|
function getDynamicModeOn() {
|
|
return self.player.dynamicModeOn;
|
|
}
|
|
|
|
function getDynamicModeFutureSize() {
|
|
return self.player.dynamicModeFutureSize;
|
|
}
|
|
|
|
function getDynamicModeHistorySize() {
|
|
return self.player.dynamicModeHistorySize;
|
|
}
|
|
|
|
function serializePlaylist() {
|
|
var playlist = self.player.playlist;
|
|
var o = {};
|
|
for (var id in playlist) {
|
|
var item = playlist[id];
|
|
o[id] = {
|
|
key: item.key,
|
|
sortKey: item.sortKey,
|
|
isRandom: item.isRandom,
|
|
};
|
|
}
|
|
return o;
|
|
}
|
|
|
|
function serializeLibrary() {
|
|
var table = {};
|
|
for (var key in self.player.libraryIndex.trackTable) {
|
|
var track = self.player.libraryIndex.trackTable[key];
|
|
table[key] = Player.trackWithoutIndex('read', track);
|
|
}
|
|
return table;
|
|
}
|
|
};
|
|
|
|
PlayerServer.prototype.handleNewClient = function(client) {
|
|
var self = this;
|
|
client.subscriptions = {};
|
|
client.permissions = self.defaultPermissions;
|
|
client.on('message', onMessage);
|
|
client.sendMessage('permissions', client.permissions);
|
|
client.sendMessage('time', new Date());
|
|
client.on('close', onClose);
|
|
self.clients.push(client);
|
|
PlayerServer.plugins.forEach(function(plugin) {
|
|
plugin.handleNewClient(client);
|
|
});
|
|
|
|
function onClose() {
|
|
var index = self.clients.indexOf(client);
|
|
if (index >= 0) self.clients.splice(index, 1);
|
|
}
|
|
|
|
function onMessage(name, args) {
|
|
var action = PlayerServer.actions[name];
|
|
if (!action) {
|
|
console.warn("Invalid command:", name);
|
|
client.sendMessage("error", "invalid command: " + JSON.stringify(name));
|
|
return;
|
|
}
|
|
var perm = action.permission;
|
|
if (perm != null && !client.permissions[perm]) {
|
|
var errText = "command " + JSON.stringify(name) +
|
|
" requires permission " + JSON.stringify(perm);
|
|
console.warn("permissions error:", errText);
|
|
client.sendMessage("error", errText);
|
|
return;
|
|
}
|
|
var argsType = Array.isArray(args) ? 'array' : typeof args;
|
|
if (action.args && argsType !== action.args) {
|
|
console.warn("expected arg type", action.args, args);
|
|
client.sendMessage("error", "expected " + action.args + ": " + JSON.stringify(args));
|
|
return;
|
|
}
|
|
console.info("ok command", name, args);
|
|
action.fn(self, client, args);
|
|
}
|
|
};
|