Jellyfin end to end: per-viewer playback, progress, episodes, swap browser
- Host/swap send refs only; pasted Jellyfin links become refs; the host hands the relay its own app-obtained credential. - Each viewer resolves with their own account (token in a header) or via the relay, else a clear blocked card with Link account. - Per-account progress reporting (Playing/Progress/Stopped), 401 marks the account expired. - Previous/next episode in the controls (< > / Shift+P/N), floating Next episode pill near the end, auto-advance countdown (one designated swapper), track carry-over per series. - Swap panel uses the shared MediaPicker; browser location remembered per server and opens on the current series. - Fix: states are applied one at a time (a stale pause could land after a newer play). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
773a04369e
commit
ae4edc29e8
33 files changed
+3541
-175
No files matched your search
@@ -14,7 +14,11 @@ import 'sync_server.dart';
|
||||
/// `{'t':'error','message':m}`, and forwards log records as
|
||||
/// `{'t':'log','level','logger','message'[,'error']}`. Sending
|
||||
/// `{'t':'stop'}` to the `stop` port stops the server; the returned future
|
||||
/// completes once it is down. Without [events] a start failure throws.
|
||||
/// completes once it is down. `{'t':'file','path':p,'reply':SendPort}` makes
|
||||
/// a file on this machine the hosted file (the hosting app's in-session file
|
||||
/// swap; a following `swap` with its name and size puts it up) and answers
|
||||
/// `{'ok':true,'name':n,'size':s}` or `{'ok':false}`. Without [events] a
|
||||
/// start failure throws.
|
||||
Future<void> serve(Map<String, Object?> config, {SendPort? events}) async {
|
||||
StreamSubscription<pkg_logging.LogRecord>? logs;
|
||||
if (events != null) {
|
||||
@@ -39,7 +43,20 @@ Future<void> serve(Map<String, Object?> config, {SendPort? events}) async {
|
||||
final control = ReceivePort('serve control');
|
||||
events?.send({'t': 'ready', 'port': server.boundPort, 'stop': control.sendPort});
|
||||
await for (final msg in control) {
|
||||
if (msg is Map && msg['t'] == 'stop') break;
|
||||
if (msg is! Map) continue;
|
||||
if (msg['t'] == 'stop') break;
|
||||
if (msg['t'] == 'file' && msg['path'] is String && msg['reply'] is SendPort) {
|
||||
final reply = msg['reply'] as SendPort;
|
||||
try {
|
||||
final ok = await server.setLocalFile(msg['path'] as String);
|
||||
final media = server.session.media;
|
||||
reply.send(ok && media != null
|
||||
? {'ok': true, 'name': media.name, 'size': media.size}
|
||||
: {'ok': false});
|
||||
} catch (_) {
|
||||
reply.send({'ok': false});
|
||||
}
|
||||
}
|
||||
}
|
||||
control.close();
|
||||
await server.stop();
|
||||
|
||||
Reference in new issue
Block a user