Sync fixes: clean seeks, clock offset, end of file, 2.5 min read-ahead (proto 6)

- User seeks are intercepted at the player (SyncPlayer), previewed
  locally, sent once 300 ms after the drag ends; own echoes are matched
  by seekSeq and never re-applied; the landed check can't reapply a
  stale target. 1 s position poll removed.
- ping/pong clock offset (min-RTT ClockSync); positions use server time.
- Reports carry duration; buffer gate is min(4 s, remaining - 2 s);
  new finished transport instead of a HELD stall at the end.
- cache/readahead 150 s, 256 MiB demuxer cache, pins read back.
- Resumed reports put a stale ready member back in the set; hello.resume
  takes over the old member after an unclean drop (no ghost).
- Docs: roadmap rendering findings (HW path works; shaders verified).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-07 01:20:18 +03:00
1 parent 0669ec0fc2
commit d8d487c189
28 files changed
+1592 -237

No files matched your search

+26 -8
View File
@@ -19,9 +19,13 @@ class RateAction extends PlayerAction {
const RateAction(this.rate);
}
double serverPositionAt(StateBroadcast state, DateTime now) {
/// Server position at [serverNow], which must be on the server's clock
/// (`SyncClient.serverNow()`, local time corrected by the clock offset):
/// `state.serverTime` is stamped by the server.
double serverPositionAt(StateBroadcast state, DateTime serverNow) {
if (state.transport != 'playing') return state.position;
return state.position + now.difference(state.serverTime).inMilliseconds / 1000.0;
return state.position +
serverNow.difference(state.serverTime).inMicroseconds / 1e6;
}
bool seekLanded({required double ownPosition, required double target}) {
@@ -39,12 +43,6 @@ bool localPlayAllowed(StateBroadcast state, String memberId) {
return false;
}
/// Self is ready but out of the set and fresh again: the server's stale
/// sweep dropped it from the set during a signal gap and nothing puts it
/// back. Re-sending ready restores it without the user pressing Space.
bool shouldReassertReady(MemberView? self) =>
self != null && self.ready && !self.inSet && !self.stale;
class StateDiff {
static const debounceWindow = Duration(seconds: 2);
@@ -57,6 +55,7 @@ class StateDiff {
bool get slowing => _slowing;
/// [now] is server time (see [serverPositionAt]).
List<PlayerAction> diff(StateBroadcast state, double ownPosition, DateTime now) {
final out = <PlayerAction>[];
final serverPos = serverPositionAt(state, now);
@@ -73,6 +72,25 @@ class StateDiff {
return out;
}
// End of media: everyone stops where they are (at or just before the
// end). No seek to the very end and no drift correction.
if (state.transport == 'finished') {
final first = _lastEventId == -1;
final changed = first ||
state.eventId != _lastEventId ||
state.transport != _lastTransport;
_lastEventId = state.eventId;
_lastTransport = state.transport;
_lastMediaId = state.mediaId;
_slowing = false;
_deviatingSince = null;
if (!changed) return out;
out.add(const RateAction(1.0));
if (first) out.add(SeekAction(serverPos > 1 ? serverPos - 1 : 0));
out.add(const TransportAction(false));
return out;
}
if (_lastEventId == -1) {
_lastEventId = state.eventId;
_lastTransport = state.transport;