fix(player): 观看全集进入后自动播放
This commit is contained in:
1 parent
350d16db75
commit
37353dac15
5 files changed
+126
-6
No files matched your search
@@ -13,8 +13,18 @@ class PlaybackCoordinator {
|
||||
Future<bool> _acquire(PlaybackSession session, bool Function() valid) {
|
||||
final result = _handoff.then((_) async {
|
||||
if (!valid()) return false;
|
||||
final previous = _owner;
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-coordinator] phase=acquire '
|
||||
'session=${identityHashCode(session)} owner=${session.owner.name} '
|
||||
'previous=${previous == null ? '-' : identityHashCode(previous)} '
|
||||
'previousOwner=${previous?.owner.name ?? '-'} '
|
||||
'previousLive=${previous?.livePlayerCount ?? 0} '
|
||||
'same=${identical(previous, session)}',
|
||||
);
|
||||
}
|
||||
if (_owner != session) {
|
||||
final previous = _owner;
|
||||
if (previous != null && previous.owner != session.owner) {
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
@@ -31,6 +41,12 @@ class PlaybackCoordinator {
|
||||
if (!valid()) return false;
|
||||
_owner = session;
|
||||
}
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-coordinator] phase=acquired '
|
||||
'session=${identityHashCode(session)} live=${session.livePlayerCount}',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
_handoff = result.then<void>((_) {});
|
||||
|
||||
@@ -84,6 +84,11 @@ class PlaybackSession extends ChangeNotifier {
|
||||
bool get canAutoAdvance => _mayPlay && !_loading && active != null;
|
||||
bool get isLocked => !canPlay(currentEpisode);
|
||||
String? get error => _error;
|
||||
bool get debugAppResumed => _appResumed;
|
||||
bool get debugRouteVisible => _routeVisible;
|
||||
bool get debugOverlayOpen => _overlayOpen;
|
||||
bool get debugVisible => _visible;
|
||||
bool get debugMayPlay => _mayPlay;
|
||||
// Includes initialization and asynchronous disposal, until native release ends.
|
||||
int get livePlayerCount => _live.length;
|
||||
bool get activeSlotActivated => _active?.activated ?? false;
|
||||
@@ -254,6 +259,16 @@ class PlaybackSession extends ChangeNotifier {
|
||||
bool? userWantsPlay,
|
||||
}) async {
|
||||
if (_closed || index < 0 || index >= episodes.length) return;
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-session] phase=select-enter session=${identityHashCode(this)} '
|
||||
'owner=${owner.name} index=$index user=$_userWantsPlay '
|
||||
'selected=$_selected current=$_currentIndex '
|
||||
'appResumed=$_appResumed routeVisible=$_routeVisible '
|
||||
'overlayOpen=$_overlayOpen visible=$_visible mayPlay=$_mayPlay '
|
||||
'live=${_live.length}',
|
||||
);
|
||||
}
|
||||
// 切换到不同剧集时恢复播放;仍停留在同一集时保留用户暂停。
|
||||
if (userWantsPlay != null) {
|
||||
_userWantsPlay = userWantsPlay;
|
||||
@@ -465,6 +480,22 @@ class PlaybackSession extends ChangeNotifier {
|
||||
Future<void> _applyPlayback(_PlayerSlot slot) async {
|
||||
if (_closed || _active != slot || !slot.activated) return;
|
||||
final shouldPlay = _mayPlay && slot.index == _currentIndex && !isLocked;
|
||||
final video = slot.player is VideoPlayerPort
|
||||
? slot.player as VideoPlayerPort
|
||||
: null;
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-session] phase=apply-playback '
|
||||
'session=${identityHashCode(this)} owner=${owner.name} '
|
||||
'index=${slot.index} shouldPlay=$shouldPlay user=$_userWantsPlay '
|
||||
'appResumed=$_appResumed routeVisible=$_routeVisible '
|
||||
'overlayOpen=$_overlayOpen locked=$isLocked '
|
||||
'player=${identityHashCode(slot.player)} '
|
||||
'controller=${video == null ? '-' : identityHashCode(video.controller)} '
|
||||
'isPlaying=${video?.controller.value.isPlaying ?? '-'} '
|
||||
'slotPlaying=${slot.playing}',
|
||||
);
|
||||
}
|
||||
if (shouldPlay == slot.playing) return;
|
||||
if (shouldPlay) {
|
||||
await slot.player.play();
|
||||
@@ -472,6 +503,15 @@ class PlaybackSession extends ChangeNotifier {
|
||||
await slot.player.pause();
|
||||
}
|
||||
slot.playing = shouldPlay;
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-session] phase=apply-playback-result '
|
||||
'session=${identityHashCode(this)} index=${slot.index} '
|
||||
'shouldPlay=$shouldPlay '
|
||||
'isPlaying=${video?.controller.value.isPlaying ?? '-'} '
|
||||
'slotPlaying=${slot.playing}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncPlayback() {
|
||||
|
||||
@@ -249,6 +249,8 @@ class PlayerLogic extends ChangeNotifier {
|
||||
'[feed-session] phase=$phase session=${identityHashCode(session)} '
|
||||
'index=${session.currentIndex} episode=${session.currentEpisode.id} '
|
||||
'controller=${video == null ? '-' : identityHashCode(video.controller)} '
|
||||
'user=${session.userWantsPlay} routeVisible=${session.debugRouteVisible} '
|
||||
'appResumed=${session.debugAppResumed} overlayOpen=${session.debugOverlayOpen} '
|
||||
'live=${session.livePlayerCount}',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
bool _loadingRemoved = true;
|
||||
bool _loadingFadeScheduled = false;
|
||||
bool _loggedLoadingUiVisible = false;
|
||||
bool _loggedStartConditions = false;
|
||||
final PlayerHeartOverlayController _heartOverlayController =
|
||||
PlayerHeartOverlayController();
|
||||
final Set<String> _precachedPosterUrls = {};
|
||||
@@ -160,6 +161,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
final handoff = widget.handoff;
|
||||
final initialUserWantsPlay = handoff?.sourceUserWantsPlay ?? true;
|
||||
logic = PlayerBinding().create(
|
||||
history: context.read<WatchHistoryRepository>(),
|
||||
playback: context.read<PlaybackRepository>(),
|
||||
@@ -179,8 +181,21 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
coordinator: widget.embedded
|
||||
? context.read<PlaybackCoordinator>()
|
||||
: PlaybackCoordinator(),
|
||||
initialUserWantsPlay: handoff?.sourceUserWantsPlay ?? true,
|
||||
initialUserWantsPlay: initialUserWantsPlay,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-start] phase=initialized '
|
||||
'session=${identityHashCode(logic.session)} '
|
||||
'initialUserWantsPlay=$initialUserWantsPlay '
|
||||
'sessionUserWantsPlay=${logic.session.userWantsPlay} '
|
||||
'active=${logic.active} embedded=${widget.embedded} '
|
||||
'appResumed=${logic.session.debugAppResumed} '
|
||||
'routeVisible=${logic.session.debugRouteVisible} '
|
||||
'overlayOpen=${logic.session.debugOverlayOpen} '
|
||||
'live=${logic.session.livePlayerCount}',
|
||||
);
|
||||
}
|
||||
_pageController = logic.pages;
|
||||
if (!widget.embedded &&
|
||||
!logic.isEpisodeLocked(logic.session.currentEpisode)) {
|
||||
@@ -575,6 +590,19 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
Widget _buildPlayer(BuildContext context) {
|
||||
final interactions = context.watch<InteractionController>();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
if (kDebugMode && !_loggedStartConditions) {
|
||||
_loggedStartConditions = true;
|
||||
debugPrint(
|
||||
'[player-start] phase=first-build '
|
||||
'session=${identityHashCode(logic.session)} '
|
||||
'tickerMode=${TickerMode.valuesOf(context).enabled} '
|
||||
'routeVisible=${logic.session.debugRouteVisible} '
|
||||
'appResumed=${logic.session.debugAppResumed} '
|
||||
'overlayOpen=${logic.session.debugOverlayOpen} '
|
||||
'visible=${logic.session.debugVisible} '
|
||||
'mayPlay=${logic.session.debugMayPlay}',
|
||||
);
|
||||
}
|
||||
final indices = logic.visibleIndices;
|
||||
final activePort = logic.session.active;
|
||||
final activeVideo = activePort is VideoPlayerPort ? activePort : null;
|
||||
@@ -1341,8 +1369,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
seriesEpisodes.insert(0, sourceEpisode);
|
||||
episodeIndex = 0;
|
||||
}
|
||||
final sourceUserWantsPlay = logic.session.userWantsPlay;
|
||||
final handoff = PlayerPageHandoff(sourceUserWantsPlay: sourceUserWantsPlay);
|
||||
final handoff = PlayerPageHandoff(sourceUserWantsPlay: true);
|
||||
_logFullPlayerRoute('present');
|
||||
setState(() => _presentationActive = true);
|
||||
final route = Navigator.of(context).push<void>(
|
||||
|
||||
@@ -229,7 +229,7 @@ void main() {
|
||||
expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_one_paused_overlay_when_entering_full_player', (
|
||||
testWidgets('should_autoplay_full_player_and_restore_paused_feed', (
|
||||
tester,
|
||||
) async {
|
||||
final factory = _RouteVideoFactory();
|
||||
@@ -245,7 +245,9 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(_fullPlayerFinder(), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-play-toggle')), findsOneWidget);
|
||||
final fullLogic = _pageLogic(tester, _fullPlayerFinder());
|
||||
expect(fullLogic.session.userWantsPlay, isTrue);
|
||||
expect(find.byKey(const Key('player-play-toggle')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const Key('home-feed-center-play')).hitTestable(),
|
||||
findsNothing,
|
||||
@@ -519,6 +521,37 @@ void main() {
|
||||
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_auto_play_full_player_when_feed_is_paused', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpApp(tester, playerFactory: (_) => _RecordingPlayer());
|
||||
await tester.tap(find.byKey(const Key('tab-recommend')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final feedPage = find.byKey(const Key('recommend-feed-player'));
|
||||
final feedLogic = _pageLogic(tester, feedPage);
|
||||
await feedLogic.session.preloadSettled;
|
||||
feedLogic.session.setUserWantsPlay(false);
|
||||
await feedLogic.session.playbackSettled;
|
||||
await tester.pump();
|
||||
expect(feedLogic.session.userWantsPlay, isFalse);
|
||||
|
||||
await tester.tap(find.byKey(const Key('home-feed-full-series')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final fullLogic = _pageLogic(tester, _fullPlayerFinder());
|
||||
final fullPlayer = fullLogic.session.active! as _RecordingPlayer;
|
||||
expect(fullLogic.session, isNot(same(feedLogic.session)));
|
||||
expect(fullLogic.session.userWantsPlay, isTrue);
|
||||
expect(fullPlayer.playCalls, greaterThan(0));
|
||||
expect(fullPlayer.playing, isTrue);
|
||||
|
||||
await tester.tap(find.byKey(const Key('player-back')).hitTestable());
|
||||
await tester.pumpAndSettle();
|
||||
expect(feedLogic.session.userWantsPlay, isFalse);
|
||||
expect(feedLogic.session.activeSlotPlaying, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('should_only_show_progress_buffer_after_video_first_frame', (
|
||||
tester,
|
||||
) async {
|
||||
@@ -2103,12 +2136,14 @@ class _RouteVideoPlayer extends VideoPlayerPort {
|
||||
|
||||
class _RecordingPlayer implements PlayerPort {
|
||||
bool playing = false;
|
||||
int playCalls = 0;
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
playCalls++;
|
||||
playing = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user