/
/
1"""Normalized models shared between the Spotify Connect provider and its backends."""
2
3from __future__ import annotations
4
5from collections.abc import Awaitable, Callable
6from dataclasses import dataclass
7from enum import StrEnum
8
9
10class BackendEventType(StrEnum):
11 """Type discriminator for the normalized events a backend emits to the provider."""
12
13 # session lifecycle: this device became / stopped being the active Spotify device
14 SESSION_ACTIVE = "session_active"
15 SESSION_INACTIVE = "session_inactive"
16 # playback state reported by the backend (BUFFERING is informational: reserved
17 # for backends that report it, the provider does not act on it)
18 PLAYING = "playing"
19 PAUSED = "paused"
20 STOPPED = "stopped"
21 BUFFERING = "buffering"
22 # track metadata and playback position updates
23 METADATA = "metadata"
24 POSITION = "position"
25 # Spotify-side volume change (normalized to a 0-100 percentage)
26 VOLUME = "volume"
27 # the backend lost its Spotify connection (e.g. daemon exit) and will recover
28 # on its own; any session/playback state is gone until a new SESSION_ACTIVE
29 CONNECTION_LOST = "connection_lost"
30 # the backend failed permanently and the provider must unload with an error
31 FATAL_ERROR = "fatal_error"
32 # any other backend activity; carries at most refreshed context/track uris
33 OTHER = "other"
34
35
36@dataclass(slots=True)
37class BackendTrackMetadata:
38 """
39 Normalized track metadata carried by a METADATA event.
40
41 ``duration`` and ``position`` are in seconds. A None ``title`` means the
42 backend did not report one (the provider keeps the previous title).
43 """
44
45 track_uri: str | None = None
46 title: str | None = None
47 artist: str | None = None
48 album: str | None = None
49 image_url: str | None = None
50 duration: int | None = None
51 position: int = 0
52
53
54@dataclass(slots=True)
55class BackendEvent:
56 """
57 A single normalized event emitted by a backend to the provider.
58
59 ``context_uri`` / ``track_uri`` piggyback on every event type: they carry
60 the latest context/track seen by the backend so the provider can take
61 playback back after the user moved the active device away. ``position`` is
62 the elapsed time in seconds (POSITION events), ``volume`` a 0-100
63 percentage (VOLUME events) and ``error`` the failure description
64 (FATAL_ERROR events).
65 """
66
67 type: BackendEventType
68 context_uri: str | None = None
69 track_uri: str | None = None
70 metadata: BackendTrackMetadata | None = None
71 position: int | None = None
72 volume: int | None = None
73 error: str | None = None
74
75
76# Awaited by the backend for every normalized event, in emit order.
77BackendEventCallback = Callable[[BackendEvent], Awaitable[None]]
78
79# Reads the next chunk of decoded PCM; returns b"" once the audio pipe closes.
80AudioChunkReader = Callable[[], Awaitable[bytes]]
81