WebSocket Architecture Documentation
Overview
The WebSocket system in this architecture provides real-time communication between clients. It is primarily designed to manage client-to-client controls, where a user on one device (like a phone) can control playback or the queue on another device (like a TV).
The StreamManager
class is the core of this WebSocket
system, responsible for managing active WebSocket connections,
processing incoming messages, sending responses, and handling tasks such
as updating play queues and sending commands across devices. Client
connections are stored as WSS objects.
Flow of WebSocket Connections
Establishing a Connection
When a new WebSocket connection is initiated, it is handled by the
AddSocket
method in StreamManager
. This
method:
- Creates a new
WSS
object. - Adds the WebSocket session to the
Sockets
list. -
Begins handling incoming WebSocket messages asynchronously using the
HandleWebSocketAsync
method. -
Starts managing the connection via
ManageSocket
, which handles ping checks and connection closures.- The server will send a PING message out on the connection every 2 minutes, and if it isn't responded to by a PONG message, then the server will consider the client dead and disconnect from it.
Managing WebSocket Connections
WebSocket connections are monitored for activity. The server pings each connection at intervals to ensure it is still active. If a client fails to respond after a specific duration, the connection is closed.
Ping-Pong Mechanism
- The server sends a PING every two minutes to each connected WebSocket.
- The client must respond with a PONG message within the next minute.
- If no PONG is received, the server closes the connection.
Sending Commands
Once a WebSocket is connected, the
HandleWebSocketAsync
method listens for incoming messages.
The system supports a variety of commands:
-
GET QUEUE: Request to retrieve the current queue id
associated with this session/client.
- Example:
GET QUEUE
- Example:
-
SET QUEUE: Sets the queue for the session.
- Example:
SET QUEUE:66bc99b8ab7ba419497e21af
- Clients should send this command anytime they change the queue they are playing back from.
- Example:
-
SET DEVICE: Sets the name of the device controlled by
this WebSocket.
- Example:
SET Device:Epsi's Phone
- Clients are not given a name by default, and are not findable without one. Client should send this message once after connecting, and again anytime the user wants to change the client's name.
-
Client names MUST be unique, and the server will respond on the
WebSocket with
Device Name Taken
if you try to set a duplicate name.
- Example:
-
SET PUBLIC: Makes the WebSocket connection publicly
accessible or private.
- Example:
SET PUBLIC:true
- This makes the client's WebSocket session visible. Clients can use the Stream API endpoints to see visible connections and send commands to them.
-
Returns
DEVICE IS PUBLIC:true
indicating success and the current value.
- Example:
-
SEND PROGRESS: Begins sending media scanning
progress.
- Example:
SEND PROGRESS
- Clients can choose to have scan progress sent down the WebSocket for Realtime updates, instead of calling api/scan/progress and getting progress from one point in time.
-
Response progress updates will return every 100ms ish and look
like
PROGRESS:<Num of Scanned Files>:<Num of Total Files to Scan>
-
It will also send a progress info response anytime the status
string changes, and will look like
PROGRESS INFO:Scanning track metadata
- Example:
-
STOP PROGRESS: Stops the progress updates.
- Example:
STOP PROGRESS
- Example:
Client-to-Client Communication
One of the key features of the WebSocket system is enabling client-to-client communication. For example, a user might use their phone to control media playback on a TV. This is done by:
- Identifying the device: Each device has a name that is registered with the WebSocket.
-
Sending commands: Commands can be sent to a WebSocket
by using the Stream API endpoints, and are
received by the desired client over it's WebSocket. Those commands
are:
-
PLAY: will look like
PLAY QUEUE:66bc99b8ab7ba419497e21af
where the right side of:
is the queue the client should attempt to play.- If the queue is the same as the one already playing, resume playback instead of swapping queues.
-
PAUSE: will look like
PAUSE
and should tell the client to pause playback -
SKIP: will look like
SKIP
and should tell the client to skip to the next song -
REWIND: will look like
REWIND
and is supposed to tell the client to go back a song, but may also instead rewind the current track and require a second rewind call to go back a song. -
VOLUME: will look like
VOLUME:20
where the volume the client should set to is on the right side of the:
-
QUEUE UPDATE: will look like
UPDATE QUEUE
and tells the client that the queue has changed in some way, so it should reload it's info.- This is done by calling AlertQueueUpdate() anytime a queue is impacted by an API.
-
PLAY: will look like