diff options
Diffstat (limited to 'socket.js')
-rw-r--r-- | socket.js | 600 |
1 files changed, 341 insertions, 259 deletions
diff --git a/socket.js b/socket.js index bc35c76..5c54bbc 100644 --- a/socket.js +++ b/socket.js @@ -17,39 +17,41 @@ // library from there. This would be handy for people with a VPN with its own // hostname and firewall protections! -'use strict' // single quotes & no semicolons time babey +// single quotes & no semicolons time babey -// This is expected to be the same across both the client and the server. -// There will probably be inconsistencies between sender clients and receiving -// clients / the server otherwise. -const DEFAULT_NICKNAME = '(Unnamed)' - -const originalSymbol = Symbol('Original item') +import EventEmitter from 'node:events' +import net from 'node:net' -const EventEmitter = require('events') -const net = require('net') -const shortid = require('shortid') - -const { - saveBackend, - restoreBackend, - saveItemReference, - restoreNewItem, - updateRestoredTracksUsingPlaylists -} = require('./serialized-backend') +import shortid from 'shortid' -const { +import { getTimeStringsFromSec, - silenceEvents -} = require('./general-util') + parseWithoutPrototype, + silenceEvents, +} from './general-util.js' -const { +import { parentSymbol, updateGroupFormat, updateTrackFormat, isTrack, - isGroup -} = require('./playlist-utils') + isGroup, +} from './playlist-utils.js' + +import { + restoreBackend, + restoreNewItem, + saveBackend, + saveItemReference, + updateRestoredTracksUsingPlaylists, +} from './serialized-backend.js' + +// This is expected to be the same across both the client and the server. +// There will probably be inconsistencies between sender clients and receiving +// clients / the server otherwise. +const DEFAULT_NICKNAME = '(Unnamed)' + +export const originalSymbol = Symbol('Original item') function serializePartySource(item) { // Turn an item into a sanitized, compact format for sharing with the server @@ -67,7 +69,7 @@ function serializePartySource(item) { } } -function deserializePartySource(source) { +function deserializePartySource(source, parent = null) { // Reconstruct a party source into the ordinary group/track format. const recursive = source => { @@ -81,9 +83,16 @@ function deserializePartySource(source) { } const top = recursive(source) - return (isGroup(top) + + const item = (isGroup(top) ? updateGroupFormat(top) : updateTrackFormat(top)) + + if (parent) { + item[parentSymbol] = parent + } + + return item } function serializeCommandToData(command) { @@ -94,7 +103,11 @@ function serializeCommandToData(command) { function deserializeDataToCommand(data) { // Turn data received from a socket into a command that can be processed as // an action to apply to the mtui backend. - return JSON.parse(data) + return parseWithoutPrototype(data) +} + +function namePartySources(nickname) { + return `Party Sources - ${nickname}` } function isItemRef(ref) { @@ -144,26 +157,33 @@ function validateCommand(command) { switch (command.sender) { case 'server': switch (command.code) { - case 'initialize-backend': - return typeof command.backend === 'object' - case 'set-socket-id': + case 'initialize party': + return ( + typeof command.backend === 'object' && + typeof command.socketInfo === 'object' && + Object.values(command.socketInfo).every(info => ( + typeof info.nickname === 'string' && + Array.isArray(info.sharedSources) + )) + ) + case 'set socket id': return typeof command.socketId === 'string' } // No break here; servers can send commands which typically come from // clients too. case 'client': switch (command.code) { - case 'announce-join': + case 'announce join': return true - case 'clear-queue': + case 'clear queue': return typeof command.queuePlayer === 'string' - case 'clear-queue-past': - case 'clear-queue-up-to': + case 'clear queue past': + case 'clear queue up to': return ( typeof command.queuePlayer === 'string' && isItemRef(command.track) ) - case 'distribute-queue': + case 'distribute queue': return ( typeof command.queuePlayer === 'string' && isItemRef(command.topItem) && @@ -199,26 +219,26 @@ function validateCommand(command) { ) )) ) - case 'restore-queue': + case 'restore queue': return ( typeof command.queuePlayer === 'string' && Array.isArray(command.tracks) && command.tracks.every(track => isItemRef(track)) && ['shuffle'].includes(command.why) ) - case 'seek-to': + case 'seek to': return ( typeof command.queuePlayer === 'string' && typeof command.time === 'number' ) - case 'set-nickname': + case 'set nickname': return ( typeof command.nickname === 'string' && typeof command.oldNickname === 'string' && command.nickname.length >= 1 && command.nickname.length <= 12 ) - case 'set-pause': + case 'set pause': return ( typeof command.queuePlayer === 'string' && typeof command.paused === 'boolean' && @@ -227,21 +247,25 @@ function validateCommand(command) { command.sender === 'server' ) || !command.startingTrack ) - case 'share-with-party': + case 'added queue player': + return ( + typeof command.id === 'string' + ) + case 'share with party': return ( typeof command.item === 'string' || Array.isArray(command.item) ) case 'status': return ( - command.status === 'done-playing' || + command.status === 'done playing' || ( - command.status === 'ready-to-resume' && + command.status === 'ready to resume' && typeof command.queuePlayer === 'string' ) || - command.status === 'sync-playback' + command.status === 'sync playback' ) - case 'stop-playing': + case 'stop playing': return typeof command.queuePlayer === 'string' case 'unqueue': return ( @@ -278,7 +302,7 @@ function perLine(handleLine) { } } -function makeSocketServer() { +export function makeSocketServer() { // The socket server has two functions: to maintain a "canonical" backend // and synchronize newly connected clients with the relevent data in this // backend, and to receive command data from clients and relay this to @@ -290,23 +314,36 @@ function makeSocketServer() { const server = new net.Server() const socketMap = Object.create(null) + // Keeps track of details to share with newly joining sockets for + // synchronization. + const socketInfoMap = Object.create(null) + server.canonicalBackend = null // <variable> -> queue player id -> array: socket - const readyToResume = {} - const donePlaying = {} + const readyToResume = Object.create(null) + const donePlaying = Object.create(null) server.on('connection', socket => { const socketId = shortid.generate() - socketMap[socketId] = socket + const socketInfo = { + hasAnnouncedJoin: false, + nickname: DEFAULT_NICKNAME, + + // Unlike in client code, this isn't an array of actual playlist items; + // rather, it's the intermediary format used when transferring between + // client and server. + sharedSources: [] + } - let hasAnnouncedJoin = false - let nickname = DEFAULT_NICKNAME + socketMap[socketId] = socket + socketInfoMap[socketId] = socketInfo socket.on('close', () => { if (socketId in socketMap) { delete socketMap[socketId] + delete socketInfoMap[socketId] } }) @@ -322,7 +359,7 @@ function makeSocketServer() { command.sender = 'client' command.senderSocketId = socketId - command.senderNickname = nickname + command.senderNickname = socketInfo.nickname if (!validateCommand(command)) { return @@ -331,10 +368,10 @@ function makeSocketServer() { // If the socket hasn't announced its joining yet, it only has access to // a few commands. - if (!hasAnnouncedJoin) { + if (!socketInfo.hasAnnouncedJoin) { if (![ - 'announce-join', - 'set-nickname' + 'announce join', + 'set nickname' ].includes(command.code)) { return } @@ -345,7 +382,7 @@ function makeSocketServer() { if (command.code === 'status') { switch (command.status) { - case 'done-playing': { + case 'done playing': { const doneSockets = donePlaying[command.queuePlayer] if (doneSockets && !doneSockets.includes(socketId)) { doneSockets.push(socketId) @@ -359,17 +396,15 @@ function makeSocketServer() { } break } - case 'ready-to-resume': { + case 'ready to resume': { const readySockets = readyToResume[command.queuePlayer] if (readySockets && !readySockets.includes(socketId)) { readySockets.push(socketId) if (readySockets.length === Object.keys(socketMap).length) { - const QP = server.canonicalBackend.queuePlayers.find(QP => QP.id === command.queuePlayer) - silenceEvents(QP, ['set-pause'], () => QP.setPause(false)) for (const socket of Object.values(socketMap)) { socket.write(serializeCommandToData({ sender: 'server', - code: 'set-pause', + code: 'set pause', queuePlayer: command.queuePlayer, startingTrack: true, paused: false @@ -381,18 +416,18 @@ function makeSocketServer() { } break } - case 'sync-playback': + case 'sync playback': for (const QP of server.canonicalBackend.queuePlayers) { if (QP.timeData) { socket.write(serializeCommandToData({ sender: 'server', - code: 'seek-to', + code: 'seek to', queuePlayer: QP.id, time: QP.timeData.curSecTotal }) + '\n') socket.write(serializeCommandToData({ sender: 'server', - code: 'set-pause', + code: 'set pause', queuePlayer: QP.id, startingTrack: true, paused: QP.player.isPaused @@ -410,26 +445,34 @@ function makeSocketServer() { readyToResume[command.queuePlayer] = [] } - // If it's a 'set-nickname' command, save the nickname. + // If it's a 'set nickname' command, save the nickname. // Also attach the old nickname for display in log messages. - if (command.code === 'set-nickname') { - command.oldNickname = nickname - command.senderNickname = nickname - nickname = command.nickname + if (command.code === 'set nickname') { + command.oldNickname = socketInfo.nickname + command.senderNickname = socketInfo.nickname + socketInfo.nickname = command.nickname + } + + // If it's a 'share with party' command, keep track of the item being + // shared, so we can synchronize newly joining sockets with it. + + if (command.code === 'share with party') { + const { sharedSources } = socketInfoMap[socketId] + sharedSources.push(command.item) } - // If it's an 'announce-join' command, mark the variable for this! + // If it's an 'announce join' command, mark the variable for this! - if (command.code === 'announce-join') { - hasAnnouncedJoin = true; + if (command.code === 'announce join') { + socketInfo.hasAnnouncedJoin = true; } // If the socket hasn't announced its joining yet, don't relay the - // command. (Since hasAnnouncedJoin gets set above, 'announce-join' - // will meet this condition.) + // command. (Since hasAnnouncedJoin gets set above, 'announce join' + // will pass this condition.) - if (!hasAnnouncedJoin) { + if (!socketInfo.hasAnnouncedJoin) { return } @@ -452,21 +495,22 @@ function makeSocketServer() { socket.write(serializeCommandToData({ sender: 'server', - code: 'set-socket-id', + code: 'set socket id', socketId }) + '\n') socket.write(serializeCommandToData({ sender: 'server', - code: 'initialize-backend', - backend: savedBackend + code: 'initialize party', + backend: savedBackend, + socketInfo: socketInfoMap }) + '\n') }) return server } -function makeSocketClient() { +export function makeSocketClient() { // The socket client connects to a server and sends/receives commands to/from // that server. This doesn't actually connect the socket to a port/host; that // is the caller's responsibility (use client.socket.connect()). @@ -479,7 +523,7 @@ function makeSocketClient() { client.sendCommand = function(command) { const data = serializeCommandToData(command) client.socket.write(data + '\n') - client.emit('sent-command', command) + client.emit('sent command', command) } client.socket.on('data', perLine(line => { @@ -503,22 +547,22 @@ function makeSocketClient() { return client } -function attachBackendToSocketClient(backend, client) { +export function attachBackendToSocketClient(backend, client) { // All actual logic for instances of the mtui backend interacting with each // other through commands lives here. let hasAnnouncedJoin = false - const partyGrouplike = { - name: `Party Sources - ${client.nickname}`, + const sharedSources = { + name: namePartySources(client.nickname), isPartySources: true, items: [] } - const partyGrouplikeMap = Object.create(null) + const socketInfoMap = Object.create(null) const getPlaylistSources = () => - partyGrouplike.items.map(item => item[originalSymbol]) + sharedSources.items.map(item => item[originalSymbol]) backend.setHasAnnouncedJoin(false) backend.setAlwaysStartPaused(true) @@ -541,22 +585,22 @@ function attachBackendToSocketClient(backend, client) { let isVerbose = false switch (command.code) { - case 'announce-join': + case 'announce join': actionmsg = `joined the party` break - case 'clear-queue': + case 'clear queue': actionmsg = 'cleared the queue' break - case 'clear-queue-past': + case 'clear queue past': actionmsg = `cleared the queue past ${itemToMessage(command.track)}` break - case 'clear-queue-up-to': + case 'clear queue up to': actionmsg = `cleared the queue up to ${itemToMessage(command.track)}` break - case 'distribute-queue': + case 'distribute queue': actionmsg = `distributed ${itemToMessage(command.topItem)} across the queue ${command.opts.how}` break - case 'initialize-backend': + case 'initialize party': return case 'play': actionmsg = `started playing ${itemToMessage(command.track)}` @@ -571,52 +615,55 @@ function attachBackendToSocketClient(backend, client) { actionmsg = `queued ${itemToMessage(command.topItem)}` + afterMessage break } - case 'restore-queue': + case 'restore queue': if (command.why === 'shuffle') { actionmsg = 'shuffled the queue' } break - case 'share-with-party': + case 'share with party': // TODO: This isn't an outrageously expensive operation, but it still // seems a little unnecessary to deserialize it here if we also do that // when actually processing the source? actionmsg = `shared ${itemToMessage(deserializePartySource(command.item))} with the party` break - case 'seek-to': + case 'seek to': // TODO: the second value here should be the duration of the track // (this will make values like 0:0x:yy / 1:xx:yy appear correctly) actionmsg = `seeked to ${getTimeStringsFromSec(command.time, command.time).timeDone}` mayCombine = true break - case 'set-nickname': + case 'set nickname': actionmsg = `updated their nickname (from ${nickToMessage(command.oldNickname)})` senderNickname = command.nickname break - case 'set-socket-id': + case 'set socket id': return - case 'set-pause': + case 'set pause': if (command.paused) { actionmsg = 'paused the player' } else { actionmsg = 'resumed the player' } break - case 'stop-playing': + case 'stop playing': actionmsg = 'stopped the player' break case 'unqueue': actionmsg = `removed ${itemToMessage(command.topItem)} from the queue` break + case 'added queue player': + actionmsg = `created a new playback queue` + break case 'status': isVerbose = true switch (command.status) { - case 'ready-to-resume': + case 'ready to resume': actionmsg = `is ready to play!` break - case 'done-playing': + case 'done playing': actionmsg = `has finished playing` break - case 'sync-playback': + case 'sync playback': actionmsg = `synced playback with the server` break default: @@ -635,7 +682,7 @@ function attachBackendToSocketClient(backend, client) { }) } - client.on('sent-command', command => { + client.on('sent command', command => { command.senderNickname = client.nickname logCommand(command) }) @@ -645,18 +692,40 @@ function attachBackendToSocketClient(backend, client) { switch (command.sender) { case 'server': switch (command.code) { - case 'set-socket-id': + case 'set socket id': client.socketId = command.socketId - partyGrouplikeMap[command.socketId] = partyGrouplike - backend.loadPartyGrouplike(client.socketId, partyGrouplike) + socketInfoMap[command.socketId] = { + nickname: client.nickname, + sharedSources + } + backend.loadSharedSources(command.socketId, sharedSources) return - case 'initialize-backend': + case 'initialize party': + for (const [ socketId, info ] of Object.entries(command.socketInfo)) { + const nickname = info.nickname + + const sharedSources = { + name: namePartySources(nickname), + isPartySources: true + } + + sharedSources.items = info.sharedSources.map( + item => deserializePartySource(item, sharedSources)) + + socketInfoMap[socketId] = { + nickname, + sharedSources + } + + backend.loadSharedSources(socketId, sharedSources) + } await restoreBackend(backend, command.backend) - backend.on('playing', QP => { - QP.once('received time data', () => { - client.sendCommand({code: 'status', status: 'sync-playback'}) - }) - }) + attachPlaybackBackendListeners() + // backend.on('QP: playing', QP => { + // QP.once('received time data', () => { + // client.sendCommand({code: 'status', status: 'sync playback'}) + // }) + // }) return } // Again, no break. Client commands can come from the server. @@ -667,31 +736,34 @@ function attachBackendToSocketClient(backend, client) { ) switch (command.code) { - case 'announce-join': { - const partyGrouplike = { - name: `Party Sources - ${command.senderNickname}`, + case 'announce join': { + const sharedSources = { + name: namePartySources(command.senderNickname), isPartySources: true, items: [] } - partyGrouplikeMap[command.senderSocketId] = partyGrouplike - backend.loadPartyGrouplike(command.senderSocketId, partyGrouplike) + socketInfoMap[command.senderSocketId] = { + nickname: command.senderNickname, + sharedSources + } + backend.loadSharedSources(command.senderSocketId, sharedSources) return } - case 'clear-queue': - if (QP) silenceEvents(QP, ['clear-queue'], () => QP.clearQueue()) + case 'clear queue': + if (QP) silenceEvents(QP, ['clear queue'], () => QP.clearQueue()) return - case 'clear-queue-past': - if (QP) silenceEvents(QP, ['clear-queue-past'], () => QP.clearQueuePast( + case 'clear queue past': + if (QP) silenceEvents(QP, ['clear queue past'], () => QP.clearQueuePast( restoreNewItem(command.track, getPlaylistSources()) )) return - case 'clear-queue-up-to': - if (QP) silenceEvents(QP, ['clear-queue-up-to'], () => QP.clearQueueUpTo( + case 'clear queue up to': + if (QP) silenceEvents(QP, ['clear queue up to'], () => QP.clearQueueUpTo( restoreNewItem(command.track, getPlaylistSources()) )) return - case 'distribute-queue': - if (QP) silenceEvents(QP, ['distribute-queue'], () => QP.distributeQueue( + case 'distribute queue': + if (QP) silenceEvents(QP, ['distribute queue'], () => QP.distributeQueue( restoreNewItem(command.topItem), { how: command.opts.how, @@ -704,13 +776,13 @@ function attachBackendToSocketClient(backend, client) { QP.once('received time data', data => { client.sendCommand({ code: 'status', - status: 'ready-to-resume', + status: 'ready to resume', queuePlayer: QP.id }) }) - silenceEvents(QP, ['playing'], () => QP.play( - restoreNewItem(command.track, getPlaylistSources()) - )) + silenceEvents(QP, ['playing'], () => { + QP.play(restoreNewItem(command.track, getPlaylistSources())) + }) } return case 'queue': @@ -722,25 +794,26 @@ function attachBackendToSocketClient(backend, client) { } )) return - case 'restore-queue': + case 'restore queue': if (QP) { QP.replaceAllItems(command.tracks.map( refData => restoreNewItem(refData, getPlaylistSources()) )) } return - case 'seek-to': - if (QP) silenceEvents(QP, ['seek-to'], () => QP.seekTo(command.time)) + case 'seek to': + if (QP) silenceEvents(QP, ['seek to'], () => QP.seekTo(command.time)) return - case 'set-nickname': { - const partyGrouplike = partyGrouplikeMap[command.senderSocketId] - if (partyGrouplike) { - partyGrouplike.name = `Party Sources - ${command.senderNickname}` - backend.partyGrouplikeUpdated(client.socketId, partyGrouplike) - } + case 'set nickname': { + const info = socketInfoMap[command.senderSocketId] + info.nickname = command.senderNickname + info.sharedSources.name = namePartySources(command.senderNickname) + backend.sharedSourcesUpdated(client.socketId, info.sharedSources) return } - case 'set-pause': { + case 'set pause': { + // All this code looks very scary??? + /* // TODO: there's an event leak here when toggling pause while // nothing is playing let playingThisTrack = true @@ -749,20 +822,28 @@ function attachBackendToSocketClient(backend, client) { }) setTimeout(() => { if (playingThisTrack) { - if (QP) silenceEvents(QP, ['set-pause'], () => QP.setPause(command.paused)) + if (QP) silenceEvents(QP, ['set pause'], () => QP.setPause(command.paused)) } }, command.startingTrack ? 500 : 0) + */ + silenceEvents(QP, ['set pause'], () => QP.setPause(command.paused)) return } - case 'share-with-party': { - const deserialized = deserializePartySource(command.item) - const partyGrouplike = partyGrouplikeMap[command.senderSocketId] - deserialized[parentSymbol] = partyGrouplike - partyGrouplike.items.push(deserialized) - backend.partyGrouplikeUpdated(command.senderSocketId, partyGrouplike) + case 'added queue player': { + silenceEvents(backend, ['added queue player'], () => { + const QP = backend.addQueuePlayer() + QP.id = command.id + }) + return + } + case 'share with party': { + const { sharedSources } = socketInfoMap[command.senderSocketId] + const deserialized = deserializePartySource(command.item, sharedSources) + sharedSources.items.push(deserialized) + backend.sharedSourcesUpdated(command.senderSocketId, sharedSources) return } - case 'stop-playing': + case 'stop playing': if (QP) silenceEvents(QP, ['playing'], () => QP.stopPlaying()) return case 'unqueue': @@ -775,161 +856,162 @@ function attachBackendToSocketClient(backend, client) { } }) - backend.on('clear-queue', queuePlayer => { + backend.on('announce join party', () => { client.sendCommand({ - code: 'clear-queue', - queuePlayer: queuePlayer.id + code: 'announce join' }) }) - backend.on('clear-queue-past', (queuePlayer, track) => { - client.sendCommand({ - code: 'clear-queue-past', - queuePlayer: queuePlayer.id, - track: saveItemReference(track) - }) + backend.on('share with party', item => { + if (sharedSources.items.every(x => x[originalSymbol] !== item)) { + const serialized = serializePartySource(item) + const deserialized = deserializePartySource(serialized) + + deserialized[parentSymbol] = sharedSources + deserialized[originalSymbol] = item + + sharedSources.items.push(deserialized) + backend.sharedSourcesUpdated(client.socketId, sharedSources) + + updateRestoredTracksUsingPlaylists(backend, getPlaylistSources()) + + client.sendCommand({ + code: 'share with party', + item: serialized + }) + } }) - backend.on('clear-queue-up-to', (queuePlayer, track) => { - client.sendCommand({ - code: 'clear-queue-up-to', - queuePlayer: queuePlayer.id, - track: saveItemReference(track) - }) + backend.on('set party nickname', nickname => { + let oldNickname = client.nickname + sharedSources.name = namePartySources(nickname) + client.nickname = nickname + client.sendCommand({code: 'set nickname', nickname, oldNickname}) }) - backend.on('distribute-queue', (queuePlayer, topItem, opts) => { - client.sendCommand({ - code: 'distribute-queue', - queuePlayer: queuePlayer.id, - topItem: saveItemReference(topItem), - opts + function attachPlaybackBackendListeners() { + backend.on('QP: clear queue', queuePlayer => { + client.sendCommand({ + code: 'clear queue', + queuePlayer: queuePlayer.id + }) }) - }) - backend.on('done playing', queuePlayer => { - client.sendCommand({ - code: 'status', - status: 'done-playing', - queuePlayer: queuePlayer.id + backend.on('QP: clear queue past', (queuePlayer, track) => { + client.sendCommand({ + code: 'clear queue past', + queuePlayer: queuePlayer.id, + track: saveItemReference(track) + }) }) - }) - backend.on('playing', (queuePlayer, track) => { - if (track) { + backend.on('QP: clear queue up to', (queuePlayer, track) => { client.sendCommand({ - code: 'play', + code: 'clear queue up to', queuePlayer: queuePlayer.id, track: saveItemReference(track) }) - queuePlayer.once('received time data', data => { - client.sendCommand({ - code: 'status', - status: 'ready-to-resume', - queuePlayer: queuePlayer.id - }) + }) + + backend.on('QP: distribute queue', (queuePlayer, topItem, opts) => { + client.sendCommand({ + code: 'distribute queue', + queuePlayer: queuePlayer.id, + topItem: saveItemReference(topItem), + opts }) - } else { + }) + + backend.on('QP: done playing', queuePlayer => { client.sendCommand({ - code: 'stop-playing', + code: 'status', + status: 'done playing', queuePlayer: queuePlayer.id }) - } - }) - - backend.on('queue', (queuePlayer, topItem, afterItem, opts) => { - client.sendCommand({ - code: 'queue', - queuePlayer: queuePlayer.id, - topItem: saveItemReference(topItem), - afterItem: saveItemReference(afterItem), - opts }) - }) - function handleSeek(queuePlayer) { - client.sendCommand({ - code: 'seek-to', - queuePlayer: queuePlayer.id, - time: queuePlayer.time + backend.on('QP: playing', (queuePlayer, track) => { + if (track) { + client.sendCommand({ + code: 'play', + queuePlayer: queuePlayer.id, + track: saveItemReference(track) + }) + queuePlayer.once('received time data', data => { + client.sendCommand({ + code: 'status', + status: 'ready to resume', + queuePlayer: queuePlayer.id + }) + }) + } else { + client.sendCommand({ + code: 'stop playing', + queuePlayer: queuePlayer.id + }) + } }) - } - backend.on('seek-ahead', handleSeek) - backend.on('seek-back', handleSeek) - backend.on('seek-to', handleSeek) + backend.on('QP: queue', (queuePlayer, topItem, afterItem, opts) => { + client.sendCommand({ + code: 'queue', + queuePlayer: queuePlayer.id, + topItem: saveItemReference(topItem), + afterItem: saveItemReference(afterItem), + opts + }) + }) - backend.on('set party nickname', nickname => { - let oldNickname = client.nickname - partyGrouplike.name = `Party Sources - ${nickname}` - client.nickname = nickname - client.sendCommand({code: 'set-nickname', nickname, oldNickname}) - }) + function handleSeek(queuePlayer) { + client.sendCommand({ + code: 'seek to', + queuePlayer: queuePlayer.id, + time: queuePlayer.time + }) + } - backend.on('shuffle-queue', queuePlayer => { - client.sendCommand({ - code: 'restore-queue', - why: 'shuffle', - queuePlayer: queuePlayer.id, - tracks: queuePlayer.queueGrouplike.items.map(saveItemReference) - }) - }) + backend.on('QP: seek ahead', handleSeek) + backend.on('QP: seek back', handleSeek) + backend.on('QP: seek to', handleSeek) - backend.on('toggle-pause', queuePlayer => { - client.sendCommand({ - code: 'set-pause', - queuePlayer: queuePlayer.id, - paused: queuePlayer.player.isPaused + backend.on('QP: shuffle queue', queuePlayer => { + client.sendCommand({ + code: 'restore queue', + why: 'shuffle', + queuePlayer: queuePlayer.id, + tracks: queuePlayer.queueGrouplike.items.map(saveItemReference) + }) }) - }) - backend.on('unqueue', (queuePlayer, topItem) => { - client.sendCommand({ - code: 'unqueue', - queuePlayer: queuePlayer.id, - topItem: saveItemReference(topItem) + backend.on('QP: toggle pause', queuePlayer => { + client.sendCommand({ + code: 'set pause', + queuePlayer: queuePlayer.id, + paused: queuePlayer.player.isPaused + }) }) - }) - backend.on('announce join party', () => { - client.sendCommand({ - code: 'announce-join' + backend.on('QP: unqueue', (queuePlayer, topItem) => { + client.sendCommand({ + code: 'unqueue', + queuePlayer: queuePlayer.id, + topItem: saveItemReference(topItem) + }) }) - }) - - backend.on('share with party', item => { - if (partyGrouplike.items.every(x => x[originalSymbol] !== item)) { - const serialized = serializePartySource(item) - const deserialized = deserializePartySource(serialized) - - deserialized[parentSymbol] = partyGrouplike - deserialized[originalSymbol] = item - - partyGrouplike.items.push(deserialized) - backend.partyGrouplikeUpdated(client.socketId, partyGrouplike) - - updateRestoredTracksUsingPlaylists(backend, getPlaylistSources()) + backend.on('added queue player', (queuePlayer) => { client.sendCommand({ - code: 'share-with-party', - item: serialized + code: 'added queue player', + id: queuePlayer.id, }) - } - }) + }) + } } -function attachSocketServerToBackend(server, backend) { +export function attachSocketServerToBackend(server, backend) { // Unlike the function for attaching a backend to follow commands from a // client (attachBackendToSocketClient), this function is minimalistic. // It just sets the associated "canonical" backend. Actual logic for // de/serialization lives in serialized-backend.js. server.canonicalBackend = backend } - -Object.assign(module.exports, { - originalSymbol, - makeSocketServer, - makeSocketClient, - attachBackendToSocketClient, - attachSocketServerToBackend -}) |