Introduce new WebSockets receiver impl

When enabled, this new receiver will spin up a local WebSockets server
and will send the currently playing song information to any clients that
connect. It's designed with Übersicht in mind, since WebSockets is the
easiest way to efficiently push events into an Übersicht widget, but
I'm sure it'd work for a variety of other purposes too.

Currently the socket is only used in one direction, pushing the current
song info from server to client, but I'll probably extend it to support
sending MPD commands from WebSockets clients as well.
This commit is contained in:
Danielle McLean 2024-07-13 18:34:53 +10:00
parent 75206a97f1
commit 582a4628b7
Signed by: 00dani
GPG key ID: 6854781A0488421C
8 changed files with 379 additions and 10 deletions

View file

@ -0,0 +1,52 @@
from pathlib import Path
import ormsgpack
from websockets import broadcast
from websockets.server import WebSocketServerProtocol, serve
from ...config.model import WebsocketsReceiverConfig
from ...player import Player
from ...song import Song
from ...song_receiver import DefaultLoopFactory, Receiver
MSGPACK_NULL = ormsgpack.packb(None)
def default(value: object) -> object:
if isinstance(value, Path):
return str(value)
raise TypeError
class WebsocketsReceiver(Receiver):
config: WebsocketsReceiverConfig
player: Player
connections: set[WebSocketServerProtocol]
last_status: bytes = MSGPACK_NULL
def __init__(self, config: WebsocketsReceiverConfig):
self.config = config
self.connections = set()
@classmethod
def loop_factory(cls) -> DefaultLoopFactory:
return DefaultLoopFactory()
async def start(self, player: Player) -> None:
self.player = player
await serve(self.handle, host=self.config.host, port=self.config.port)
async def handle(self, conn: WebSocketServerProtocol) -> None:
self.connections.add(conn)
await conn.send(self.last_status)
try:
await conn.wait_closed()
finally:
self.connections.remove(conn)
async def update(self, song: Song | None) -> None:
if song is None:
self.last_status = MSGPACK_NULL
else:
self.last_status = ormsgpack.packb(song, default=default)
broadcast(self.connections, self.last_status)