fix(player): 记住拖动后的实际播放位置

This commit is contained in:
Zhou Weidong committed 2026-10-22 10:41:01 +08:00
1 parent 47ca21406a
commit 5268adc999
8 files changed
+352 -67

No files matched your search

+81
View File
@@ -218,6 +218,75 @@ void main() {
logic.dispose();
await _drain(logic);
});
testWidgets(
'should_cache_actual_position_after_seek_completes',
(tester) async {
late BuildContext context;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (pageContext) {
context = pageContext;
return const Scaffold(body: SizedBox.expand());
},
),
),
);
final cache = PlaybackPositionCache();
final history = FakeWatchHistory();
final factory = _VideoRecordingFactory();
late PlayerLogic logic;
await tester.runAsync(() async {
logic = _logic(history, positionCache: cache);
logic.attach(
PlayerPage(drama: _drama('signal'), initialIndex: 0),
isMounted: () => true,
);
logic.start(
factory: factory.create,
coordinator: PlaybackCoordinator(),
);
await _drain(logic);
});
final player = logic.session.active! as _VideoRecordingPlayer;
logic.beginScrub(player);
await tester.runAsync(
() => logic.seekTo(
context,
player,
const Duration(seconds: 42),
),
);
await tester.runAsync(() => logic.pendingWrite);
expect(
cache.read('signal-1', dramaId: 'signal')?.position,
const Duration(seconds: 42),
);
expect(history.writes.last, 'signal-1|42000|false');
logic.beginScrub(player);
await tester.runAsync(
() => logic.seekTo(
context,
player,
const Duration(minutes: 2),
),
);
await tester.runAsync(() => logic.pendingWrite);
expect(player.controller.value.position, const Duration(minutes: 1));
expect(
cache.read('signal-1', dramaId: 'signal')?.position,
const Duration(minutes: 1),
);
expect(history.writes.last, 'signal-1|60000|false');
logic.dispose();
await tester.pump();
},
);
});
test('should_play_local_file_without_ticket_when_episode_is_downloaded',
@@ -1257,6 +1326,18 @@ class _VideoRecordingPlayer extends VideoPlayerPort {
@override
Future<void> pause() async {}
@override
Future<void> seekTo(Duration position) async {
final current = controller.value;
controller.value = VideoPlayerValue(
duration: current.duration,
size: current.size,
position: position,
isInitialized: current.isInitialized,
isPlaying: current.isPlaying,
);
}
@override
Future<void> dispose() async {
if (disposed) return;
+68 -29
View File
@@ -48,7 +48,7 @@ void main() {
channel: const MethodChannel('test/player_open_detail/pip'),
),
);
final factory = _RecordingFactory();
final factory = _VideoRecordingFactory();
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
@@ -59,15 +59,8 @@ void main() {
final active = logic.session.active;
final index = logic.session.currentIndex;
final player = factory.created.first;
final playsBefore = player.plays;
final tracked = VideoPlayerPort(drama.episodes.first);
tracked.controller.value = const VideoPlayerValue(
duration: Duration(minutes: 5),
position: Duration(seconds: 37),
isInitialized: true,
);
logic
..trackedPlayer = tracked
..trackedPlayer = player
..trackedEpisode = drama.episodes.first
..trackedDrama = drama;
@@ -96,22 +89,31 @@ void main() {
),
);
logic.beginScrub(player);
await logic.seekTo(
pageContext,
player,
const Duration(minutes: 1, seconds: 22),
);
unawaited(logic.openDetail(pageContext));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
await _pumpSession(tester);
await tester.runAsync(() => logic.session.playbackSettled);
expect(find.byType(DramaDetailPage), findsOneWidget);
expect(logic.session.active, same(active));
expect(logic.session.currentIndex, index);
expect(logic.session.userWantsPlay, isTrue);
expect(player.pauses, 1);
expect(player.plays, playsBefore);
expect(tracked.controller.value.position, const Duration(seconds: 37));
expect(history.writes, ['signal-1|37000|false']);
expect(
player.controller.value.position,
const Duration(minutes: 1, seconds: 22),
);
expect(history.writes, ['signal-1|82000|false', 'signal-1|82000|false']);
expect(
positionCache.read('signal-1', dramaId: 'signal')?.position,
const Duration(seconds: 37),
const Duration(minutes: 1, seconds: 22),
);
await tester.pageBack();
@@ -123,11 +125,14 @@ void main() {
expect(logic.session.active, same(active));
expect(logic.session.currentIndex, index);
expect(logic.session.userWantsPlay, isTrue);
expect(player.plays, playsBefore + 1);
expect(tracked.controller.value.position, const Duration(seconds: 37));
expect(
player.controller.value.position,
const Duration(minutes: 1, seconds: 22),
);
expect(history.writes, [
'signal-1|37000|false',
'signal-1|37000|false',
'signal-1|82000|false',
'signal-1|82000|false',
'signal-1|82000|false',
]);
logic
@@ -136,7 +141,6 @@ void main() {
..trackedDrama = null
..dispose();
await _pumpSession(tester);
await tracked.dispose();
},
);
}
@@ -164,29 +168,64 @@ Future<void> _pumpSession(WidgetTester tester) async {
}
}
class _RecordingFactory {
final List<_RecordingPlayer> created = [];
class _VideoRecordingFactory {
final List<_VideoRecordingPlayer> created = [];
PlayerPort create(Episode episode) {
final player = _RecordingPlayer();
final player = _VideoRecordingPlayer(episode);
created.add(player);
return player;
}
}
class _RecordingPlayer implements PlayerPort {
int plays = 0;
int pauses = 0;
class _VideoRecordingPlayer extends VideoPlayerPort {
_VideoRecordingPlayer(super.episode);
@override
Future<void> initialize() async {}
Future<void> initialize() async {
controller.value = const VideoPlayerValue(
duration: Duration(minutes: 5),
size: Size(1280, 720),
position: Duration(seconds: 1),
isInitialized: true,
);
}
@override
Future<void> play() async => plays++;
Future<void> play() async {
_setPlaying(true);
}
@override
Future<void> pause() async => pauses++;
Future<void> pause() async {
_setPlaying(false);
}
@override
Future<void> dispose() async {}
Future<void> seekTo(Duration position) async {
final current = controller.value;
controller.value = VideoPlayerValue(
duration: current.duration,
size: current.size,
position: position,
isInitialized: current.isInitialized,
isPlaying: current.isPlaying,
);
}
void _setPlaying(bool playing) {
final current = controller.value;
controller.value = VideoPlayerValue(
duration: current.duration,
size: current.size,
position: current.position,
isInitialized: current.isInitialized,
isPlaying: playing,
);
}
@override
Future<void> dispose() async {
await controller.dispose();
}
}