feat(player): 保持屏幕常亮并自动恢复播放

This commit is contained in:
Zhou Weidong committed 2026-10-22 10:17:43 +08:00
1 parent 5bf10221a8
commit a25c500371
7 files changed
+273 -1

No files matched your search

+166
View File
@@ -732,6 +732,172 @@ void main() {
});
group('lifecycle', () {
test(
'should_auto_resume_previously_paused_playback_when_app_returns',
() async {
final factory = _RecordingFactory();
final playback = FakePlaybackRepository(fresh: false);
final drama = _drama('signal');
final logic = _logic(FakeWatchHistory(), playback: playback);
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(
factory: factory.create,
coordinator: PlaybackCoordinator(),
);
await _drain(logic);
final player = factory.created.first;
final requestsBeforeResume = playback.requests.length;
logic
..trackedEpisode = drama.episodes.first
..trackedDrama = drama;
logic.session.setUserWantsPlay(false);
await _drain(logic);
expect(logic.session.userWantsPlay, isFalse);
expect(player.plays, 1);
logic.onLifecycle(AppLifecycleState.paused);
await _drain(logic);
expect(logic.trackedEpisode, isNotNull);
expect(logic.ticketError, isNull);
expect(logic.blockedEpisodeId, isNull);
expect(logic.unlockingEpisodeId, isNull);
expect(logic.session.isLocked, isFalse);
expect(logic.session.error, isNull);
expect(logic.session.canResumeAfterBackground, isTrue);
logic.onLifecycle(AppLifecycleState.resumed);
await _drain(logic);
expect(logic.session.userWantsPlay, isTrue);
expect(player.plays, 2);
expect(playback.requests.length, greaterThan(requestsBeforeResume));
logic.dispose();
await _drain(logic);
},
);
test('should_not_auto_resume_locked_episode_when_app_returns', () async {
final factory = _RecordingFactory();
final drama = _drama('locked', isFree: false);
final logic = _logic(
FakeWatchHistory(),
playback: FakePlaybackRepository(
ticket: const PlayTicket(
episodeId: 'locked-1',
accessible: false,
accessType: PlayTicket.coinRequired,
unlockCoin: 30,
),
),
);
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(
factory: factory.create,
coordinator: PlaybackCoordinator(),
);
await _drain(logic);
logic.trackedEpisode = drama.episodes.first;
logic.session.setUserWantsPlay(false);
logic.onLifecycle(AppLifecycleState.paused);
await _drain(logic);
logic.onLifecycle(AppLifecycleState.resumed);
await _drain(logic);
expect(logic.session.userWantsPlay, isFalse);
expect(factory.created, isEmpty);
logic.dispose();
await _drain(logic);
});
test(
'should_not_auto_resume_when_ticket_failure_or_overlay_is_active',
() async {
final factory = _RecordingFactory();
final drama = _drama('signal');
final logic = _logic(FakeWatchHistory());
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(
factory: factory.create,
coordinator: PlaybackCoordinator(),
);
await _drain(logic);
final player = factory.created.first;
logic.session.setUserWantsPlay(false);
await _drain(logic);
logic.ticketError = 'ticket failed';
logic.onLifecycle(AppLifecycleState.paused);
await _drain(logic);
logic.onLifecycle(AppLifecycleState.resumed);
await _drain(logic);
expect(logic.session.userWantsPlay, isFalse);
expect(player.plays, 1);
logic.ticketError = null;
logic.session.setOverlayOpen(true);
logic.onLifecycle(AppLifecycleState.paused);
await _drain(logic);
logic.onLifecycle(AppLifecycleState.resumed);
await _drain(logic);
expect(logic.session.userWantsPlay, isFalse);
expect(player.plays, 1);
logic.session.setOverlayOpen(false);
logic.dispose();
await _drain(logic);
},
);
test(
'should_save_progress_and_pause_when_app_enters_background',
() async {
final factory = _RecordingFactory();
final drama = _drama('signal');
final history = FakeWatchHistory();
final logic = _logic(history);
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(
factory: factory.create,
coordinator: PlaybackCoordinator(),
);
await _drain(logic);
final player = factory.created.first;
final tracked = VideoPlayerPort(drama.episodes.first);
tracked.controller.value = const VideoPlayerValue(
duration: Duration(minutes: 3),
position: Duration(seconds: 5),
isInitialized: true,
);
logic
..trackedPlayer = tracked
..trackedEpisode = drama.episodes.first
..trackedDrama = drama;
logic.onLifecycle(AppLifecycleState.paused);
await _drain(logic);
await logic.pendingWrite;
expect(player.pauses, 1);
expect(history.writes, ['signal-1|5000|false']);
logic.dispose();
await _drain(logic);
await tracked.dispose();
},
);
test('should_pause_active_player_when_backgrounded_and_play_it_when_resumed', () async {
final factory = _RecordingFactory();
final drama = _drama('signal');