This commit is contained in:
2026-09-08 15:20:27 +02:00
parent 4b9e6c5651
commit 2eb9a9590e
15 changed files with 1023 additions and 127 deletions
+51 -2
View File
@@ -50,13 +50,62 @@ The central server is the heart of the application. Its main features are:
allowlist for playback agents.}
]
The browser is a thin remote control, not a second player. It polls a complete
JSON state snapshot once per second and sends commands to the central server;
The browser is a thin remote control, not a second player. It polls player
status once per second and sends commands to the central server;
playlists, preferences and playback state are kept there. The server exposes
the browser application under the root URL and its JSON API under @tt{/api}.
The API includes state, discovery, playback commands, preferences, artwork and
the agent registration, polling and media routes.
@subsection{Playlist synchronization}
State responses include an opaque @tt{playlistVersion} for the selected
playlist. The browser sends its last received version as the
@tt{playlistVersion} query parameter on @tt{/api/state}, @tt{/api/discover}
and @tt{/api/command/}@italic{command}. When that version still matches,
@tt{tracks} is JSON @tt{null}; otherwise the response includes the complete
track array. Requests without a version continue to receive all tracks.
The version and track data are returned together in one consistent snapshot.
The server caches serialized tracks, count and total duration per playlist.
Changing the track list invalidates that snapshot and generates a fresh version
when it is next requested. Renaming a tab or updating playback position does
not invalidate the tracks. Versions are renewed after a server restart and
are distinct between playlists and users. Changes from another browser are
picked up by the next poll.
The browser keeps the active playlist in memory and serializes state-producing
requests through @tt{PlayerStateClient}, so a delayed response cannot replace
newer state. Unchanged playlist rows are reused; changing the interface language
still rebuilds their translated labels.
@subsection{Saved playlists in the library}
The library has @italic{Folders} and @italic{Playlists} tabs. The playlist
toolbar's @italic{Save playlist} button names and saves the current tab in
the user's library. The library row's @tt{} button opens that playlist as
its own tab, or selects its existing tab. @tt{▶} does the same and starts
playback. These actions never append tracks to or replace another tab.
A saved playlist and its open tab share one UUID and one track list. Track
edits and renaming therefore update the saved playlist automatically. Closing
the tab keeps it in the library. Closing the last saved tab leaves a new empty
default tab. Existing tabs remain open and appear in the library only after
the user explicitly saves them.
The keystore retains @tt{playlists-for-}@italic{username} for the ordered open
tabs and adds @tt{saved-playlists-for-}@italic{username} for library playlists.
Each UUID has one stored value, shared by both indexes. The private storage
procedure @tt{load-user-playlists} reads the open index by default and the saved
index with @tt{#:saved? #t}. @tt{save-user-playlists!} accepts the saved collection
through @tt{#:saved} and updates both indexes atomically. Values absent from
both indexes are removed. Legacy stores without a saved index need no migration.
Browser state includes @tt{savedPlaylists} summaries without track arrays and
a @tt{saved} flag on each tab. @tt{playlist-save} takes an open tab's @tt{id}
and a @tt{name}; @tt{playlist-open} and @tt{playlist-play} take a saved playlist's
@tt{id}. All three commands operate within the requesting user's collection.
@subsection{Player agents}
A player agent is an additional renderer implemented by this package. It runs