Errors: refuse links the probe can't play (with Put it up anyway), plain error texts, and others see who can't play

report.error / state.members[].error (optional, no protocol bump): an
errored member is out of the ready gate until its player opens the media
or the media changes; others see "Bob can't play this: ..." and a mark
on the avatar; your own pill offers Retry (reload).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-10 21:29:41 +03:00
1 parent 1b04e002c2
commit d3be13ecda
21 files changed
+445 -72

No files matched your search

+5 -2
View File
@@ -202,8 +202,11 @@ void main() {
final page = await previewLink('https://example.com/a',
probe: (url) async => LinkProbe.page(Uri.parse(url), pageTitle: 'Example', ytDlpError: 'Unsupported URL'));
expect((page!.title, page.subtitle),
('Example', "example.com · Not a video (and yt-dlp couldn't read it: Unsupported URL)"));
expect((page!.title, page.subtitle, page.problem),
('Example', 'example.com · Not a video page yt-dlp knows', 'Not a video page yt-dlp knows'));
final refused = await previewLink('https://cdn.example/b.mp4',
probe: (url) async => LinkProbe.page(Uri.parse(url), ytDlpError: 'odd', status: 403));
expect((refused!.title, refused.problem), ('b.mp4', 'Not allowed (403)'));
expect(await previewLink('not a link'), isNull);
});
+15 -1
View File
@@ -110,7 +110,9 @@ Future<Harness> pumpLibrary(
now: () => fixtureNow,
probe: (url) async => url.endsWith('.mkv')
? LinkProbe.file(Uri.parse(url), 1)
: LinkProbe.page(Uri.parse(url), info: const YtDlpInfo(title: 'A video', uploader: 'Chan')),
: url.contains('bilibili')
? LinkProbe.page(Uri.parse(url), ytDlpError: 'HTTP Error 412: Precondition Failed')
: LinkProbe.page(Uri.parse(url), info: const YtDlpInfo(title: 'A video', uploader: 'Chan')),
filesBuilder: (context, {required solo, required relayed, required onPlay, nowPlayingPath}) =>
FilesStub(solo: solo, relayed: relayed, onPlay: onPlay),
onPutUp: (m) async {
@@ -296,6 +298,18 @@ void main() {
await settle(tester, 10);
expect(h.controller.link.text, url);
// A link the probe cannot open: the reason, not put up; "anyway" is.
const bili = 'https://www.bilibili.com/video/BV1';
await tester.enterText(find.byKey(const ValueKey('link-field')), bili);
await settle(tester, 10);
await tester.tap(find.byKey(const ValueKey('link-put-up')));
await settle(tester, 10);
expect(find.text(needsCookiesText), findsOneWidget);
expect(h.put, hasLength(1));
await tester.tap(find.byKey(const ValueKey('link-put-up-anyway')));
await settle(tester, 10);
expect((h.put.last as LinkMedia).anyway, isTrue);
expect(tester.takeException(), isNull);
});
+13
View File
@@ -112,6 +112,19 @@ void main() {
expect(w.who.map((m) => m.name), ['Bob', 'Mika']);
});
test("can't play: yours with Retry (reload); others' instead of waiting", () {
var v = playerStatusFor(health: ok, state: held([me]), self: me, playerError: 'Not allowed (403)')!;
expect((v.kind, v.text, v.action), (StatusKind.problem, "Can't play this: Not allowed (403)", StatusAction.reload));
final bob = MemberView(id: 'b', name: 'Bob', ready: false, inSet: false, buffered: 0, stale: false,
error: 'Not allowed (403)');
v = playerStatusFor(health: ok, state: held([me, bob, mv('m', 'Mika', ready: true)]), self: me)!;
expect((v.kind, v.text, v.action), (StatusKind.cannotPlay, "Bob can't play this: Not allowed (403)", null));
expect(v.who.single.id, 'b');
final notMe = mv('me', 'Me');
v = playerStatusFor(health: ok, state: held([notMe, bob]), self: notMe)!;
expect((v.kind, v.action), (StatusKind.cannotPlay, StatusAction.ready));
});
test('names read as a sentence', () {
expect(nameList(['Bob']), 'Bob');
expect(nameList(['Bob', 'Mika']), 'Bob and Mika');