12 KiB
Protocol
This document describes the workflows of the software.
Preliminaries
- Song: A reference to the file containing the audio and video. Can be separated into two files (e.g. mp3+cdg) or a link (e.g. a YouTube link)
- Source: A collection of songs, that can be searched and played back from. (e.g. a folder, a s3 storage or YouTube)
- Performer: The person(s) doing the actual singing
- Entry: A song together with a performer
- Queue: A list of entries. Once the first entry is completely played back, the next entry is played.
- Waiting Room: A list of entries. To limit one performer filling the entire queue, a waiting room can be configured. If so, each performer can only have one entry in the queue. Each additional entry is put in the waiting room. Once the last entry of a performer left the queue, the first entry of that performer in the waiting room is added at the end of the queue.
- Recents: A list of entries. Once an entry successfully leaves the queue, it is added to the recents.
- Playback client: Part of the software, that does the actual playback, usually hooked to a video output device like a monitor or a projector. This needs to have access to the configured sources.
- Web client: User facing part of the software. Used to search and add entries to the queue. Has an admin view to manipulate the queue and the waiting room.
- Room: One specific karaoke event, consisting of one queue, one recents, up to one waiting room, one playback client and several web clients. It has an identifier and a secret, used to authenticate as an admin.
- Server: Manages all rooms.
- State: The state of a room consists of its queue, waiting room, recents and the configuration values of the playback client
We will use the abbreviations P, W, and S when talking about the playback client, web client and the server.
Communication usually happens between P ↔ S and W ↔ S and as messages on top of web sockets, using socket.io.
Entry
Entries are regularly sent between all participants and are encoded in JSON as follows:
Key | Type | Description | Optional |
---|---|---|---|
ident | str |
Identifier for the entry in its given source. E.g. a file name or a YouTube Link | No |
source | str |
Name of the source (files , s3 , youtube , etc.) |
No |
duration | int |
Duration of the song | No |
title | str |
Name of the song | No |
artist | str |
Artist of the original song | No |
album | str |
Name of the collection this song belongs to | No |
uuid | str |
A UUID for this entry | Yes (generated automatically if omitted) |
Client Config
A client config specifies the knowlege the server has of a specific playback client.
Key | Type | Description | Optional | Default |
---|---|---|---|---|
server | str | URL of the server | Yes | https://localhost:8080 |
room | str | Identifier of the room the client wants to connect to | Yes | Generated by the server |
secret | str | The secret for the room | No | |
preview_duration | int | Time between songs, where a preview is shown for the next song | Yes | 3 |
last_song | int | Unix timestamp of the last song allowed to be played | Yes | None |
waiting_room_policy | str | forced if waiting room is forced, optional if performers are given the choice, None if deactivated |
Yes | None |
Workflow
Connect P ↔ S
When a playback client connects (or reconnects) to a server, it can provide a room identifier and a room secret. If none are given, the server will generate both and send them to the client. If the server does not know a room with that identifier, a new room is created with the given secret.
If the server has already registered a room with the given identifier, if the secret is the same, the connection to the new playback client is stored and the old connection is forgotten.
In case of a reconnect, client and server agree on a state. First the client sends its state (meaning Queue, Waiting Room, Recents and configuration) to the server. Configuration is merged and if Queue, Waiting Room and Recents are each non-empty, the respective value on the server-side is overwritten. Then the server returns its (possible) new Queue, Waiting Room and Recents to the Client.
The following messages are exchanged during connection:
Communication | Message | Params | Notes |
---|---|---|---|
P → S | connect |
-- | Socket.io connect |
S → P | connect |
-- | Socket.io connect |
P → S | register-client |
{ queue: list[Entry], waiting_room: list[Entry], recents: list[Entry], config: Config } |
The playback client can push an initial state to the server. |
S -> P | client-registered |
{ success: bool, room: str } |
success is true if requested room is not in use or secrets match, otherwise false . The server confirms the room name, if it was requested in register-client , otherwise a new room name is returned |
S -> P | state |
{ queue: list[Entry], waiting_room: list[Entry], recents: list[Entry], config: Config} |
The server returns its updated state (without the secret) |
P -> S | sources |
{ sources: list[str] } |
sources are the names of the configured sources, the server updates its list |
P -> S | get-first |
-- | See playback workflow. This is only sent if no song is currently playing |
S -> P | request-config |
{ source: str, update: True } |
This messsage is sent for each newly added source |
P -> S | config-chunk |
{ source: str, config: dict[str, Any], number: int, total: int } |
Configuration for each configured source. Potentially uses cached values on the client side. Can optionally be sent in chunks, will be merged on the server side |
P -> S | request-resend-config |
{ source: str } |
Cached values are should be updated before sending this message |
S -> P | request-config |
{ source: str, update: False } |
Old config on the server-side is discarded |
P -> S | config-chunk |
see above |
Connect W <-> S
When a web client connects to a server, it adds itself to a room. Optionally it can upgrade its connection to an admin connection, that allows manipulation messages for the queue and the waiting_room.
Communication | Message | Params | Returns | Notes |
---|---|---|---|---|
W -> S | connect |
-- | -- | Socket.io connect |
S -> W | connect |
-- | -- | Socket.io connect |
W -> S | register-web |
{ room: str} |
bool | Connect to a room, server returns true if room exists |
S -> W | state |
{ queue: list[Entry], waiting_room: list[Entry], recents: list[Entry], config: Config} |
-- | The server returns its initial state (without the secret) |
W -> S | register-admin |
{ secret: str } |
bool | Optional, enables admin mode, if secret matches configured room secret |
Playback
While the playback client handles the playback and is aware of the queue, the client must always explicitly request the next song from the server.
Communication | Message | Params | Notes |
---|---|---|---|
P -> S | get-first |
-- | This blocks until an entry is added to the queue |
S -> P | play |
Entry | A field started_at is added to the entries |
P -> S | pop-then-get-next |
-- | This should be sent after a song is completed |
S -> P,W | state |
see above | All web clients and the playback client are notified of the new state |
S -> P | play |
see above | see above |
Search
Communication | Message | Params | Notes |
---|---|---|---|
W -> S | search |
{ query: str} |
-- |
S -> W | search-results |
{ results: list[Result]} |
A Result is an entry only consiting of ident , source , title , artist , album |
Append
When appending, the web client does not get direct feedback in the success case, but the server sends a state
message after each change in state.
Communication | Message | Params | Notes |
---|---|---|---|
W -> S | append |
{ident: str, performer: str, source: str, uid: str} |
ident and source identify the song. uid is currently unused. |
S -> P,W | state |
see above | All web clients and the playback client are notified of the new state |
S -> W | msg |
{ msg: "Unable to append ident. Maybe try again?" } |
When something goes wrong |
S -> W | ask_for_waiting |
{ current_entry: Entry, old_entry: Entry } |
Response if waitingroom is configured and already in queue |
W -> S | append-anyway |
{ident: str, performer: str, source: str, uid: str} |
Append it anyway. Will be ignored, if waiting_room_policy is set to forced |
W -> S | waiting-room-append |
{ident: str, performer: str, source: str, uid: str} |
Append to the waiting room |