Solo watching and a clearer offline start

Putting anything up offline starts a solo session: an in-app server bound
to 127.0.0.1 only, joined like hosting, so tracking, next episode and the
player all work. Invite friends re-hosts on the Host card's port and
password with the same media and position; Stop needs no confirmation.
Offline, the Library opens with a welcome band (Join a friend, Host, Watch
solo); without a Jellyfin account it leads with Files and Link, Jellyfin
is an optional card. Play buttons work offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-09 23:08:11 +03:00
1 parent 8186fbb5d3
commit e0db5cc655
28 files changed
+1013 -345

No files matched your search

+3 -2
View File
@@ -18,7 +18,8 @@ import 'sync_server.dart';
/// 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.
/// start failure throws. `'loopbackOnly': true` in [config] (next to the
/// [AppConfig] keys) binds 127.0.0.1 only: the app's solo session.
Future<void> serve(Map<String, Object?> config, {SendPort? events}) async {
StreamSubscription<pkg_logging.LogRecord>? logs;
if (events != null) {
@@ -32,7 +33,7 @@ Future<void> serve(Map<String, Object?> config, {SendPort? events}) async {
}
final SyncServer server;
try {
server = SyncServer(AppConfig.fromMap(config));
server = SyncServer(AppConfig.fromMap(config), loopbackOnly: config['loopbackOnly'] == true);
await server.start();
} catch (e) {
await logs?.cancel();
+6 -2
View File
@@ -48,8 +48,12 @@ class SyncServer {
/// Upstream connections of the Jellyfin relay; created on first use.
HttpClient? _relayClient;
/// Listen on 127.0.0.1 only (the app's solo session), not on every
/// IPv4 interface.
final bool loopbackOnly;
/// [ytDlp] replaces the server's own (tests).
SyncServer(this.config, {YtDlp? ytDlp}) {
SyncServer(this.config, {YtDlp? ytDlp, this.loopbackOnly = false}) {
hub = ServerHub(sessions, config.secret,
hostKey: config.hostKey,
jellyfin: config.jellyfin,
@@ -131,7 +135,7 @@ class SyncServer {
await _resolveJellyfinServerId();
_mountRoutes();
_server = await _app.serve(
address: InternetAddress.anyIPv4,
address: loopbackOnly ? InternetAddress.loopbackIPv4 : InternetAddress.anyIPv4,
port: config.port,
// A killed isolate never closes its listening socket, so the port stays
// bound in this process. Shared lets the next server in the same