Protocol 12: Up next (a shared queue) and where the video comes from

Up next: a server-side queue anyone edits by item id (add at end or
next, move, remove, play now, skip); each item says who added it and
how it plays. At the real end the first item swaps in after the end
card's countdown, put up by whoever can (its adder for through-me links
and files, someone with access for Jellyfin, else anyone); items that
can't play are skipped with the reason. The next item's link is
resolved ahead. Greyed "Continues with" episodes below. A chip with the
controls opens it as a popover (leaf_widgets, local path dependency for
now); "Add to queue" next to Put it up everywhere.

Delivery: the server measures the source's rate, the media's bitrate
and each member's read rate; the members card shows a source line
(who passes it on, through what, keeping up or not, source left) and
each member's route and buffer. A slow source is told once per media.
A failed server fetch is announced to everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-11 06:40:26 +03:00
1 parent 95cd668cfb
commit 6b10cae4e6
50 files changed
+4239 -157

No files matched your search

+14 -1
View File
@@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:sync_relay_core/core.dart';
import 'relay_stats.dart';
final _log = createLogger('relay');
/// One track's buffer on the server: a byte stream ([RelayBuffer]) or
@@ -16,6 +18,9 @@ abstract interface class TrackBuffer {
int get cachedBytes;
int get bytesFromFeed;
int get bytesToViewers;
/// Runs while this buffer has a pull in flight; the relay gives its
/// tracks one, for the source's rate.
abstract FeedClock clock;
/// Waits for the feed's first connect, up to the wait limit.
Future<bool> ready();
void detach(RelayFeed feed);
@@ -162,7 +167,15 @@ class RelayBuffer implements TrackBuffer {
final _waiters = <int, List<Completer<void>>>{};
final _blocked = <int, int>{}; // slice -> wait start (us)
final _viewers = <String, _Viewer>{};
_Pull? _pull;
@override
FeedClock clock = FeedClock();
_Pull? _pullNow;
_Pull? get _pull => _pullNow;
set _pull(_Pull? p) {
if ((_pullNow == null) != (p == null)) p == null ? clock.stop() : clock.start();
_pullNow = p;
}
var _reqSeq = 0;
var _disposed = false;
Completer<void> _kick = Completer();