Up next: a server-side queue anyone edits by item id (add at end or next, move, remove, play now, skip); each item says who added it and how it plays. At the real end the first item swaps in after the end card's countdown, put up by whoever can (its adder for through-me links and files, someone with access for Jellyfin, else anyone); items that can't play are skipped with the reason. The next item's link is resolved ahead. Greyed "Continues with" episodes below. A chip with the controls opens it as a popover (leaf_widgets, local path dependency for now); "Add to queue" next to Put it up everywhere. Delivery: the server measures the source's rate, the media's bitrate and each member's read rate; the members card shows a source line (who passes it on, through what, keeping up or not, source left) and each member's route and buffer. A slow source is told once per media. A failed server fetch is announced to everyone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
3.4 KiB
Dart
76 lines
3.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:sync_relay_core/core.dart';
|
|
import 'package:watch_together/src/queue/queue_logic.dart';
|
|
|
|
MemberView member(String id,
|
|
{bool stale = false, bool out = false, Map<String, bool> jf = const {}, String name = ''}) =>
|
|
MemberView(
|
|
id: id, name: name.isEmpty ? id : name, ready: false, inSet: true, buffered: 0, stale: stale, out: out, jf: jf);
|
|
|
|
QueueItem item(QueueHow how, {String by = 'm2', String byName = 'Bob'}) => QueueItem(
|
|
id: 'q1',
|
|
media: how == QueueHow.jellyfin
|
|
? const HelloMedia(name: 'ep', size: 0, duration: 0, jellyfin: JellyfinRef(serverId: 'S', itemId: 'i'))
|
|
: const HelloMedia(name: 'x', size: 0, duration: 0, url: 'https://x/y'),
|
|
how: how,
|
|
title: 'x',
|
|
by: by,
|
|
byName: byName,
|
|
);
|
|
|
|
void main() {
|
|
group('queuePerformer', () {
|
|
test('a link passed on or a file: only its adder; gone means skipped', () {
|
|
for (final how in [QueueHow.through, QueueHow.file]) {
|
|
expect(queuePerformer(item(how), [member('m1'), member('m2', out: true)]).id, 'm2');
|
|
final gone = queuePerformer(item(how), [member('m1'), member('m3')]);
|
|
expect(gone.id, isNull);
|
|
expect(gone.why, 'Bob left');
|
|
}
|
|
});
|
|
|
|
test('Jellyfin: the adder with access, else the lowest member with access, else skipped', () {
|
|
final access = {'S': true};
|
|
expect(queuePerformer(item(QueueHow.jellyfin), [member('m1', jf: access), member('m2', jf: access)]).id, 'm2');
|
|
expect(
|
|
queuePerformer(item(QueueHow.jellyfin), [member('m10', jf: access), member('m3', jf: access), member('m2')])
|
|
.id,
|
|
'm3');
|
|
expect(queuePerformer(item(QueueHow.jellyfin), [member('m3', jf: access, out: true), member('m2')]).why,
|
|
isNotNull);
|
|
});
|
|
|
|
test('a link everyone opens or the server fetches: the adder, else the lowest active member', () {
|
|
for (final how in [QueueHow.direct, QueueHow.server]) {
|
|
expect(queuePerformer(item(how), [member('m1'), member('m2')]).id, 'm2');
|
|
expect(queuePerformer(item(how), [member('m4'), member('m2', stale: true), member('m3')]).id, 'm3');
|
|
final nobody = queuePerformer(item(how), [member('m2', out: true)]);
|
|
expect((nobody.id, nobody.why), (null, null), reason: 'nobody around is not a skip');
|
|
}
|
|
});
|
|
});
|
|
|
|
test('the janitor is the lowest member that is not stale', () {
|
|
expect(queueJanitor([member('m9'), member('m1', stale: true), member('m2')]), 'm2');
|
|
});
|
|
|
|
test('warming starts in the last 90 s, once buffered to the end, or at the end', () {
|
|
bool due(double pos, {double buffered = 0, String t = 'playing'}) =>
|
|
queueWarmDue(transport: t, position: pos, duration: 1000, bufferedTo: pos + buffered);
|
|
expect(due(100), isFalse);
|
|
expect(due(911), isTrue);
|
|
expect(due(100, buffered: 900), isTrue);
|
|
expect(due(0, t: 'finished'), isTrue);
|
|
expect(queueWarmDue(transport: 'playing', position: 5, duration: 0, bufferedTo: 5), isFalse);
|
|
});
|
|
|
|
test('labels', () {
|
|
expect(queueHowLabel(item(QueueHow.through), selfId: 'm2'), 'through you');
|
|
expect(queueHowLabel(item(QueueHow.through), selfId: 'm1'), 'through Bob');
|
|
expect(queueHowLabel(item(QueueHow.file), selfId: 'm1'), "Bob's file");
|
|
expect(queueClock(3725), '1:02:05');
|
|
expect(queueClock(65), '1:05');
|
|
expect(queueClock(0), '');
|
|
});
|
|
}
|