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
+34 -7
View File
@@ -139,7 +139,14 @@ volume, and repeat mode.
Selecting, deleting, or creating a tab stops playback. Tracks are de-duplicated
by normalized source path when they are appended. Every playlist mutation is
written in one `keystore` transaction. `playlists-for-<username>` contains the
ordered playlist GUIDs; each GUID key contains that playlist's name and tracks.
ordered open playlist GUIDs; `saved-playlists-for-<username>` contains the saved
library playlist GUIDs. Each GUID key contains one playlist's name and tracks.
Both indexes are persisted atomically; a value is deleted only when neither
index refers to it. Closing a saved tab retains its library entry.
`restore-playlist-context` reuses the same in-memory object for a saved playlist
and its open tab, so track edits and renaming update both views. Old stores
without a saved index restore their original tabs without automatically saving
them in the library.
Loading validates every stored track independently against all configured
library roots, so one playlist can safely combine multiple libraries.
`language-for-<username>` stores the user's selected interface language in the
@@ -224,7 +231,7 @@ serves static assets from [`public/`](public/) and exposes these API endpoints:
| Method | Route | Responsibility |
| --- | --- | --- |
| `GET` | `/api/state` | Return the complete current state; also refresh network-renderer information. |
| `GET` | `/api/state` | Return current state, omitting unchanged tracks when `playlistVersion` matches; also refresh network-renderer information. |
| `POST` | `/api/discover` | Start asynchronous discovery and return the current state. |
| `POST` | `/api/command/:command` | Execute a command with its JSON request body and return the updated state. |
| `GET` | `/api/preferences` | Return the current user's durable UI preferences. |
@@ -241,13 +248,32 @@ header. Command failures are returned as HTTP 400 JSON responses with an
[`public/app.js`](public/app.js) implements a framework-free client. It:
- fetches a full state snapshot once per second;
- polls status once per second, receiving tracks only when the playlist version changes;
- temporarily suppresses polling while a browser-initiated command is active;
- immediately renders the state returned by successful commands;
- renders library navigation, playlists, tabs, transport status, and output
selection;
- implements keyboard actions and playlist drag-and-drop in the browser.
[`public/playlist-library.js`](public/playlist-library.js) renders the library's
Folders/Playlists tabs and saved playlist summaries. `playlist-save` adds an
open tab to the library by UUID. `playlist-open` reveals its own tab, reusing an
already open tab, and `playlist-play` also starts playback. Neither action copies
tracks into an unrelated tab. Only summary metadata is sent in `savedPlaylists`.
[`public/player-state.js`](public/player-state.js) keeps the active track array
in memory and queues state, command and discovery requests. Each request sends
the last received `playlistVersion` as a query parameter. A matching response
contains `tracks: null`; the client supplies its cached tracks to the renderers.
Requests without a version still receive the full list. Queueing prevents
responses from being applied out of order.
The player caches track JSON, count, duration and an opaque version per tab.
`save-current-tab!` invalidates this snapshot when the track list changes.
Renaming a tab leaves its track snapshot intact. Versions are unique across
tabs, users and server restarts. `renderPlaylist` uses the version to decide
when to rebuild rows instead of comparing all track metadata on every poll.
[`public/translate.js`](public/translate.js) follows the key-based translation
model used by rktplayer. It supports Dutch, English, German, French, Spanish,
Italian, Swedish, Norwegian, Finnish and Icelandic with English fallback.
@@ -314,9 +340,9 @@ sequenceDiagram
participant D as DLNA renderer
loop Browser state polling
B->>H: GET /api/state
B->>H: GET /api/state?playlistVersion=known-version
H->>P: player-state->jsexpr
P-->>H: Full state snapshot
P-->>H: State and version; tracks only if changed
H-->>B: JSON response
end
@@ -429,8 +455,9 @@ The main extension points are:
- **Per-user pipelines, shared outputs:** playlist and transport state are
isolated by username. Distinct outputs run concurrently; selecting an
occupied output explicitly stops its previous owner and transfers it.
- **Full-state snapshots:** a small and predictable client protocol, at the cost
of repeatedly transferring all tracks and browser entries.
- **Versioned playlists:** cached tracks are transferred only when the selected
playlist version changes. Other status fields, including browser entries,
remain part of each response.
- **One-second polling:** robust and dependency-free, but introduces periodic
traffic and up to one second of display latency.
- **Lazy filesystem and backend initialization:** fast startup and low idle