Sessions leave redacted logs; viewers wait for a member relay's source before opening it

Logs: one redaction pass (query strings to names, member tokens,
passwords, cookies, auth headers) for every line; daily files with size
and age caps in the app (hosted server included) and the dedicated
server (--log-dir); swaps, relay channels, per-member /r /m /j outcomes,
server fetches, mpv load errors, yt-dlp runs. Settings > About: Copy
diagnostics, Open logs folder.

Bilibili through someone else: the viewer opened /r/ before the source
dialled in; the server holds that request and mpv gives up after 5 s
("could not be opened"). Viewers now open a member relay once it is
live, the putter starts its source before its own player, and track
sizes are read in parallel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-12 19:05:32 +03:00
1 parent 572104f49e
commit 4ab37c55cf
25 files changed
+1124 -108

No files matched your search

@@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart' show JellyfinStreamType;
import 'package:sync_relay_core/core.dart' show JellyfinStreamType, LogThrottle, createLogger;
import '../jellyfin/track_prefs.dart' show MpvTrack;
import '../shaders/shader_assets.dart';
@@ -361,6 +361,7 @@ class PlaybackController {
// an error there while shaders are on is a shader that failed to load
// or compile (mpv then renders without it).
void _onLog(PlayerLog log) {
_toAppLog(log);
if (log.prefix == 'ytdl_hook' && (log.level == 'error' || log.level == 'fatal')) {
final text = log.text.trim().replaceFirst(RegExp(r'^ERROR:\s*'), '');
if (text.isNotEmpty) {
@@ -380,6 +381,8 @@ class PlaybackController {
if (!_loadErrorSent && _loadFailed(log, duration.peek())) {
_loadErrorSent = true;
final http = _httpError;
_mpvLog.warning('cannot open the media: [${log.prefix}] ${log.text.trim()}'
'${http == null ? ' (no HTTP error logged; a request that timed out after 5 s looks like this)' : ' ($http)'}');
onLoadError?.call(http == null ? 'the video stream could not be opened' : 'the video server answered $http');
return;
}
@@ -402,6 +405,23 @@ class PlaybackController {
}
}
// mpv's warnings and errors (the session player's; ffmpeg's through
// whichever player logs them) go to the app log, a few per kind at a
// time: a refused request makes ffmpeg retry and warn on every try.
static final _mpvLog = createLogger('mpv');
static final _mpvThrottle = LogThrottle(burst: 5);
static void _toAppLog(PlayerLog log, {String player = ''}) {
final level = log.level;
if (level != 'warn' && level != 'error' && level != 'fatal') return;
final text = log.text.trim();
if (text.isEmpty) return;
final pass = _mpvThrottle.allow('$player${log.prefix} $level ${text.length > 40 ? text.substring(0, 40) : text}');
if (pass == null) return;
final line = '$pass$player[${log.prefix}] $text';
level == 'warn' ? _mpvLog.info(line) : _mpvLog.warning(line);
}
/// [log] says the media could not be opened: before a [duration] is
/// known, mpv's fatal "nothing to play" or a demuxer error, or a yt-dlp
/// hook error.
@@ -438,6 +458,19 @@ class PlaybackController {
}
}
/// `mpv-version` and `ffmpeg-version` of the session player, for
/// diagnostics; null when there is no player.
Future<String?> mpvVersion() async {
final p = _player;
if (p == null) return null;
try {
final dynamic platform = p.platform;
return '${await platform?.getProperty('mpv-version')}, ffmpeg ${await platform?.getProperty('ffmpeg-version')}';
} catch (_) {
return null;
}
}
/// Opens [url] paused. [headers] go with every request for it (mpv
/// `http-header-fields`): a Jellyfin token travels there, not in the URL.
/// Each media gets its own player: when [preload] holds the same [url]
@@ -708,6 +741,7 @@ class PlaybackController {
// that is gone (a promotion disposes it) the next one created, which
// can be this preload. Its HTTP errors still name the session's.
if (log.prefix == 'ffmpeg') return _onLog(log);
_toAppLog(log, player: 'preload ');
if (_loadFailed(log, p.state.duration)) pre.fail();
});
try {