Files
plezy/test/utils/player_utils_test.dart
T

190 lines
6.6 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/mpv/mpv.dart' show BufferRange, Player, PlayerState;
import 'package:plezy/utils/player_utils.dart';
void main() {
group('shouldRestartBeforePreviousItem', () {
test('keeps previous item behavior within the restart threshold', () {
expect(shouldRestartBeforePreviousItem(Duration.zero), isFalse);
expect(shouldRestartBeforePreviousItem(const Duration(seconds: 3)), isFalse);
});
test('restarts the current item after the threshold', () {
expect(shouldRestartBeforePreviousItem(const Duration(milliseconds: 3001)), isTrue);
});
});
group('clampSeekPosition', () {
test('clamps negative positions to zero', () {
final player = _FakePlayer(duration: const Duration(minutes: 5));
expect(clampSeekPosition(player, const Duration(seconds: -10)), Duration.zero);
});
test('clamps positions beyond a known duration', () {
final player = _FakePlayer(duration: const Duration(minutes: 5));
expect(clampSeekPosition(player, const Duration(minutes: 6)), const Duration(minutes: 5));
});
test('does not upper-clamp when duration is unknown', () {
final player = _FakePlayer(duration: Duration.zero);
expect(clampSeekPosition(player, const Duration(minutes: 6)), const Duration(minutes: 6));
});
});
group('resolvePlexTranscodeSeekAction', () {
test('uses native seek for backward targets inside a local buffer range', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 20),
bufferRanges: const [BufferRange(start: Duration(seconds: 10), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.nativeSeek,
);
});
test('restarts for backward targets outside local buffer ranges', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 20),
bufferRanges: const [BufferRange(start: Duration(seconds: 25), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('uses native seek for tiny backward or no-op seeks inside the deadband', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(milliseconds: 29500),
bufferRanges: const [],
),
PlexTranscodeSeekAction.nativeSeek,
);
});
test('uses native seek when the target is inside the local buffer range', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 40),
bufferRanges: const [BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.nativeSeek,
);
});
test('restarts when buffered native seeks are disabled for the active backend', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 40),
bufferRanges: const [BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 50))],
allowBufferedNativeSeek: false,
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('uses native seek near the start of a buffer range', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(milliseconds: 29500),
bufferRanges: const [BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.nativeSeek,
);
});
test('restarts near the tail of a buffer range to avoid optimistic cache edges', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(milliseconds: 49600),
bufferRanges: const [BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('restarts when a forward target is outside local buffer ranges', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 58),
bufferRanges: const [BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 50))],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('uses native seek when the target is inside a later cached range', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 5),
target: const Duration(seconds: 33),
bufferRanges: const [
BufferRange(start: Duration(seconds: 0), end: Duration(seconds: 10)),
BufferRange(start: Duration(seconds: 30), end: Duration(seconds: 35)),
],
),
PlexTranscodeSeekAction.nativeSeek,
);
});
test('restarts for gaps far beyond the active range even when a later range exists', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 5),
target: const Duration(seconds: 25),
bufferRanges: const [
BufferRange(start: Duration(seconds: 0), end: Duration(seconds: 10)),
BufferRange(start: Duration(seconds: 40), end: Duration(seconds: 50)),
],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('does not treat a flat buffer end as a local seekable range', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 45),
bufferRanges: const [],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
test('restarts large seeks when no buffer information exists', () {
expect(
resolvePlexTranscodeSeekAction(
currentPosition: const Duration(seconds: 30),
target: const Duration(seconds: 35),
bufferRanges: const [],
),
PlexTranscodeSeekAction.restartTranscode,
);
});
});
}
class _FakePlayer implements Player {
_FakePlayer({required Duration duration}) : _state = PlayerState(duration: duration);
final PlayerState _state;
@override
PlayerState get state => _state;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}