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

+41
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
@@ -169,6 +170,46 @@ void main() {
}
});
test('a source that dials in after mpv would have given up (5 s): viewers wait for it, then play',
timeout: const Timeout(Duration(minutes: 2)), () async {
final file = await _media();
final alice = await member('alice');
// Bob is a bare client: he puts a file up and starts passing it on
// only 7 s later (a slow first upstream read, a slow own player).
final ws = await WebSocket.connect('ws://127.0.0.1:${server.boundPort}/sync');
final welcome = Completer<Map>();
ws.listen((f) {
final j = jsonDecode('$f') as Map;
if (j['t'] == 'welcome' && !welcome.isCompleted) welcome.complete(j);
}, onError: (_) {});
ws.add(jsonEncode(const HelloMessage(
proto: RelayConstants.protoVersion,
secret: 'pw',
user: HelloUser(name: 'bob'),
media: HelloMedia(name: '', size: 0, duration: 0))
.toJson()));
final token = (await welcome.future.timeout(const Duration(seconds: 5)))['token'] as String;
final size = await File(file).length();
ws.add(jsonEncode({
't': 'swap',
'media': {'name': 'late.mkv', 'size': size, 'duration': 0, 'relay': <String, Object?>{}},
}));
await _poll(() => stateMediaRelay(alice.serverState.peek()!)?.source.isNotEmpty ?? false, 'relay state');
await Future.delayed(const Duration(seconds: 7));
expect(alice.playerError.peek(), isEmpty, reason: 'no "could not be opened" while the source is on its way');
expect(alice.health.peek().message, contains('waiting for bob'));
final src = RelaySource(Uri.parse('ws://127.0.0.1:${server.boundPort}/r/default/$token/source'),
RelayMedia.single(FileRelayBytes(File(file))));
await src.start();
try {
await _poll(() => alice.playback.durationSecs > 19, 'alice opened it once the source was up');
expect(alice.playerError.peek(), isEmpty);
} finally {
await src.close();
await ws.close();
}
});
test('the source drops uncleanly and resumes: it keeps its copy and keeps serving',
timeout: const Timeout(Duration(minutes: 2)), () async {
final file = await _media();