Restructure step 6: what's next and watch tracking
- What's next is the server's: state.advance counts down (10 s) when the media finishes with an Up next item first, holds while its performer or warm source is gone, and sets queuePlay at its end; cancel is shared, a seek or swap ends it; the server skips items nobody can put up. Shared rules in core whats_next.dart; the app's janitor, per-screen countdown and end-of-episode card are gone. - The continuation (next Jellyfin episode, next file of a local show with its gaps) is an auto item under "Continues with", prepared like Up next's first item. - Watch tracking by time In (D3): only time played while this viewer is in counts; away, paused, held, jumps and stalls never do (P7, P9). WatchTracker + device WatchHistory (files, links); Jellyfin progress reads the same watch; records are the AniList seam. - A server restart no longer loses what played: apps hold their media and one offers it back (swap.restore, first offer wins), Up next is re-added, the source re-feeds, and the watch carries on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a87ee98faa
commit
741b258ac1
55 files changed
+2724
-964
No files matched your search
@@ -446,6 +446,37 @@ class MediaPipeline {
|
||||
await playback.stop();
|
||||
}
|
||||
|
||||
/// The same media came back as [r.mediaId] (a restarted server put back
|
||||
/// what played, `swap.restore`) and the player keeps it open: what this
|
||||
/// member passes on is served again (its file, its link, the Jellyfin
|
||||
/// item played with its own account: [ownJellyfin]).
|
||||
void resumeAs(StateBroadcast r, {({String url, Map<String, String> headers})? ownJellyfin}) {
|
||||
renumber(r.mediaId);
|
||||
settledMediaId.value = r.mediaId;
|
||||
final source = plan(r);
|
||||
final name = (r.media?['name'] as String?) ?? '';
|
||||
final queueId = stateMediaRelay(r)?.queueId ?? '';
|
||||
final duration = (r.media?['duration'] as num?)?.toDouble() ?? 0;
|
||||
switch (source) {
|
||||
case OwnFile(:final path):
|
||||
feeder.serve(r.mediaId, RelayMedia.single(FileRelayBytes(File(path))),
|
||||
queueId: queueId, name: name, duration: duration);
|
||||
case OwnLink(:final url, :final origin):
|
||||
feeder.serve(r.mediaId, LinkRelayMedia(Uri.parse(url), ytDlp: origin.ytDlp, probe: origin.probe),
|
||||
queueId: queueId, name: name, duration: duration);
|
||||
case JellyfinSource(:final ref, :final origin):
|
||||
if (origin == null || ownJellyfin == null) return;
|
||||
final media = RelayMedia.single(HttpRelayBytes(Uri.parse(ownJellyfin.url), headers: ownJellyfin.headers));
|
||||
if (jellyfinRelayNeeded(r, ref, session.selfId())) {
|
||||
feeder.serve(r.mediaId, media, queueId: queueId, name: name, duration: duration);
|
||||
} else {
|
||||
_jellyfinFeed = (mediaId: r.mediaId, ref: ref, media: media, queueId: queueId, name: name, duration: duration);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// The media was settled here (opened, failed, nothing to open).
|
||||
void settled(int mediaId) {
|
||||
if (_mediaId == mediaId) settledMediaId.value = mediaId;
|
||||
|
||||
Reference in new issue
Block a user