From f59086b36dfa47a476320ce618ce1bf0383a6e99 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 25 Apr 2021 10:36:45 -0300 Subject: implement basic share-with-party command --- socket.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/socket.js b/socket.js index d928b61..2890efb 100644 --- a/socket.js +++ b/socket.js @@ -51,6 +51,41 @@ const { isGroup } = require('./playlist-utils') +function serializePartySource(item) { + // Turn an item into a sanitized, compact format for sharing with the server + // and other sockets in the party. + // + // TODO: We'll probably need to assign a unique ID to the root item, since + // otherwise we don't have a way to target it to un-share it. + + if (isGroup(item)) { + return [item.name, ...item.items.map(serializePartySource).filter(Boolean)] + } else if (isTrack(item)) { + return item.name + } else { + return null + } +} + +function deserializePartySource(source) { + // Reconstruct a party source into the ordinary group/track format. + + const recursive = source => { + if (Array.isArray(source)) { + return {name: source[0], items: source.slice(1).map(recursive).filter(Boolean)} + } else if (typeof source === 'string') { + return {name: source, downloaderArg: '-'} + } else { + return null + } + } + + const top = recursive(source) + return (isGroup(top) + ? updateGroupFormat(top) + : updateTrackFormat(top)) +} + function serializeCommandToData(command) { // Turn a command into a string/buffer that can be sent over a socket. return JSON.stringify(command) @@ -192,6 +227,11 @@ function validateCommand(command) { command.sender === 'server' ) || !command.startingTrack ) + case 'share-with-party': + return ( + typeof command.item === 'string' || + Array.isArray(command.item) + ) case 'status': return ( command.status === 'done-playing' || @@ -535,6 +575,12 @@ function attachBackendToSocketClient(backend, client, { actionmsg = 'shuffled the queue' } break + 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': // 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) @@ -709,6 +755,14 @@ function attachBackendToSocketClient(backend, client, { }, command.startingTrack ? 500 : 0) 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) + return + } case 'stop-playing': if (QP) silenceEvents(QP, ['playing'], () => QP.stopPlaying()) return @@ -844,24 +898,21 @@ function attachBackendToSocketClient(backend, client, { }) }) - backend.on('share with party', origItem => { - let newItem = { - ...origItem, - [parentSymbol]: partyGrouplike, - [originalSymbol]: origItem - } + backend.on('share with party', item => { + if (partyGrouplike.items.every(x => x[originalSymbol] !== item)) { + const serialized = serializePartySource(item) + const deserialized = deserializePartySource(serialized) - if (isGroup(newItem)) { - newItem = updateGroupFormat(newItem) - } else if (isTrack(newItem)) { - newItem = updateTrackFormat(newItem) - } else { - return - } + deserialized[parentSymbol] = partyGrouplike + deserialized[originalSymbol] = item - if (partyGrouplike.items.every(x => x[originalSymbol] !== origItem)) { - partyGrouplike.items.push(newItem) + partyGrouplike.items.push(deserialized) backend.partyGrouplikeUpdated(client.socketId, partyGrouplike) + + client.sendCommand({ + code: 'share-with-party', + item: serialized + }) } }) } -- cgit 1.3.0-6-gf8a5