Protocol 14: a warm relay preloads relayed Up next items for everyone
The server keeps one small relay (32 MiB ahead, 8 behind) for Up next's first relayed item, keyed by its queue id at a URL that stays the same after the swap: fetched by the server for server-fetch links, fed by the adder's app for through-me links and files, by a member with an account for member-passed Jellyfin items. Every app preloads from it; the swap promotes it with its buffer. Dropped when the item stops being first or its adder leaves. Measured switch: ~1.6 s cold, 20-40 ms preloaded. ROADMAP gains the research item on server-side fetching of member-resolved URLs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e52a7633a0
commit
d8585fb78e
17 files changed
+821
-151
No files matched your search
+39
-3
@@ -595,6 +595,32 @@ How bytes move:
|
||||
- Cleanup: the buffer is memory only. Any swap disposes it (its URLs then
|
||||
404, the source is closed with 4001 and stops), and so does server
|
||||
stop. A crash frees it with the process.
|
||||
- Warm relay (`ServerHub.syncWarm`, run on every change; wire in
|
||||
[PROTOCOL.md](PROTOCOL.md) "Relay"): a second, small `ActiveRelay` for
|
||||
Up next's first item while it waits, keyed by its queue id, so viewers
|
||||
preload it (see "Getting ready" under "Up next"). A server-fetched
|
||||
link starts its fetch then; a link passed on or a file is fed by its
|
||||
adder's app (a second channel, `/r/.../source?q=<queueId>`), only
|
||||
while someone else is in the server and not for the host's own file;
|
||||
a Jellyfin item someone present cannot play (and the server's login
|
||||
does not cover) by the first member with an account that dials in.
|
||||
Its budget is 32 MiB ahead and 8 MiB behind its viewers
|
||||
(`relayWarmAheadBytes`, split by track size with the usual 8 MiB
|
||||
floor) plus the pins: about what a preload player reads, so the
|
||||
server holds at most that much on top of the current relay (there is
|
||||
no global relay budget; each relay is bounded by its own). Like any
|
||||
buffer it pulls nothing while nobody preloads. The swap that puts the
|
||||
item up (`swap.queueId`, from its source, or anyone for a fetch, with
|
||||
the same tracks) promotes it (`ServerHub.takeWarm`): the same buffer,
|
||||
feed and source channel become the media's relay with the full
|
||||
budget, `media.relay.queueId` names the item and the media keeps the
|
||||
item's name, so its `/r/.../<queueId>/...` URLs (and the preloads of
|
||||
them) stay valid. Any other swap of it, the item no longer first (a
|
||||
removal, a reorder, Play now of another), its adder leaving, or server
|
||||
stop drops it (its URLs 404, its source gets 4001); a restart starts
|
||||
with none. A manual swap to something else keeps it (the item is
|
||||
still first). A failed warm fetch is not promoted: the swap fetches
|
||||
again and reports the reason.
|
||||
- Source away: viewers keep playing from the buffer; a request waits up
|
||||
to 60 s for missing bytes. The source reconnects with backoff (same
|
||||
token after a resume); a different size (or piece count and roles)
|
||||
@@ -917,10 +943,17 @@ every `state` and feed), the pure rules in `queue_logic.dart`, the UI in
|
||||
"Jellyfin relay"). A link everyone opens is that link; the app that
|
||||
puts it up also probes it (`AppYtDlp.probe` with `keep`: kept 35
|
||||
minutes instead of 10, a refresh replaces it only once it is in; swaps
|
||||
read it from there). A link passed on "through me" and a file only on
|
||||
read it from there). A link passed on "through me" and a file on
|
||||
their adder's app (the link, the path; not the host's own file, which
|
||||
plays from `/m`). A server-fetched link and others' relayed items have
|
||||
nothing to get ready (the relay starts with the swap; see ROADMAP). It
|
||||
plays from `/m`), which also feeds the server's warm relay of it
|
||||
(`QueueController._feedWarm`, `SessionCoordinator.feedWarm`; a
|
||||
Jellyfin item's from the member who puts it up, with its resolved
|
||||
stream, when the server asks for one); everyone else, and everyone
|
||||
for a server-fetched link, preloads that warm relay's URL
|
||||
(`SessionCoordinator.warmRelayUrl`) once `queue[0].media.relay.live`
|
||||
says bytes can flow (see "Relay"). At the swap the adder's app skips
|
||||
re-reading the link's sizes (its warm source has them) and that
|
||||
source carries on as the media's. It
|
||||
is resolved as soon as it is first there, again every 30 minutes, and
|
||||
dropped when it stops being first.
|
||||
**Preload**: once resolved, the stream is opened in a second, hidden
|
||||
@@ -952,6 +985,9 @@ every `state` and feed), the pure rules in `queue_logic.dart`, the UI in
|
||||
account holders and a `/j` guest): ⏭ to the server playing 59 to
|
||||
86 ms for the next episode and 35 ms for a direct link, against about
|
||||
1.5 s cold; at 300 ms and 12 Mbit/s 52 and 103 ms against about 2.9 s.
|
||||
Relayed, from the warm relay (same setup, loopback server): a link
|
||||
passed on 21 to 22 ms, a server-fetched link 35 to 37 ms, against
|
||||
about 1.6 s cold.
|
||||
`SwitchTrace` (env `SYNC_RELAY_TRACE_SWITCH=1` prints it) times each
|
||||
phase of a media change: state, resolved, open, player (a fresh
|
||||
player is up; see "Player lifecycle" under "Media"), opened or
|
||||
|
||||
Reference in new issue
Block a user