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) {
|
Future<bool> _acquire(PlaybackSession session, bool Function() valid) {
|
||||||
final result = _handoff.then((_) async {
|
final result = _handoff.then((_) async {
|
||||||
if (!valid()) return false;
|
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) {
|
if (_owner != session) {
|
||||||
final previous = _owner;
|
|
||||||
if (previous != null && previous.owner != session.owner) {
|
if (previous != null && previous.owner != session.owner) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@@ -31,6 +41,12 @@ class PlaybackCoordinator {
|
|||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
_owner = session;
|
_owner = session;
|
||||||
}
|
}
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[player-coordinator] phase=acquired '
|
||||||
|
'session=${identityHashCode(session)} live=${session.livePlayerCount}',
|
||||||
|
);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
_handoff = result.then<void>((_) {});
|
_handoff = result.then<void>((_) {});
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
bool get canAutoAdvance => _mayPlay && !_loading && active != null;
|
bool get canAutoAdvance => _mayPlay && !_loading && active != null;
|
||||||
bool get isLocked => !canPlay(currentEpisode);
|
bool get isLocked => !canPlay(currentEpisode);
|
||||||
String? get error => _error;
|
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.
|
// Includes initialization and asynchronous disposal, until native release ends.
|
||||||
int get livePlayerCount => _live.length;
|
int get livePlayerCount => _live.length;
|
||||||
bool get activeSlotActivated => _active?.activated ?? false;
|
bool get activeSlotActivated => _active?.activated ?? false;
|
||||||
@@ -254,6 +259,16 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
bool? userWantsPlay,
|
bool? userWantsPlay,
|
||||||
}) async {
|
}) async {
|
||||||
if (_closed || index < 0 || index >= episodes.length) return;
|
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) {
|
if (userWantsPlay != null) {
|
||||||
_userWantsPlay = userWantsPlay;
|
_userWantsPlay = userWantsPlay;
|
||||||
@@ -465,6 +480,22 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
Future<void> _applyPlayback(_PlayerSlot slot) async {
|
Future<void> _applyPlayback(_PlayerSlot slot) async {
|
||||||
if (_closed || _active != slot || !slot.activated) return;
|
if (_closed || _active != slot || !slot.activated) return;
|
||||||
final shouldPlay = _mayPlay && slot.index == _currentIndex && !isLocked;
|
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 == slot.playing) return;
|
||||||
if (shouldPlay) {
|
if (shouldPlay) {
|
||||||
await slot.player.play();
|
await slot.player.play();
|
||||||
@@ -472,6 +503,15 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
await slot.player.pause();
|
await slot.player.pause();
|
||||||
}
|
}
|
||||||
slot.playing = shouldPlay;
|
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() {
|
void _syncPlayback() {
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
'[feed-session] phase=$phase session=${identityHashCode(session)} '
|
'[feed-session] phase=$phase session=${identityHashCode(session)} '
|
||||||
'index=${session.currentIndex} episode=${session.currentEpisode.id} '
|
'index=${session.currentIndex} episode=${session.currentEpisode.id} '
|
||||||
'controller=${video == null ? '-' : identityHashCode(video.controller)} '
|
'controller=${video == null ? '-' : identityHashCode(video.controller)} '
|
||||||
|
'user=${session.userWantsPlay} routeVisible=${session.debugRouteVisible} '
|
||||||
|
'appResumed=${session.debugAppResumed} overlayOpen=${session.debugOverlayOpen} '
|
||||||
'live=${session.livePlayerCount}',
|
'live=${session.livePlayerCount}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
bool _loadingRemoved = true;
|
bool _loadingRemoved = true;
|
||||||
bool _loadingFadeScheduled = false;
|
bool _loadingFadeScheduled = false;
|
||||||
bool _loggedLoadingUiVisible = false;
|
bool _loggedLoadingUiVisible = false;
|
||||||
|
bool _loggedStartConditions = false;
|
||||||
final PlayerHeartOverlayController _heartOverlayController =
|
final PlayerHeartOverlayController _heartOverlayController =
|
||||||
PlayerHeartOverlayController();
|
PlayerHeartOverlayController();
|
||||||
final Set<String> _precachedPosterUrls = {};
|
final Set<String> _precachedPosterUrls = {};
|
||||||
@@ -160,6 +161,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final handoff = widget.handoff;
|
final handoff = widget.handoff;
|
||||||
|
final initialUserWantsPlay = handoff?.sourceUserWantsPlay ?? true;
|
||||||
logic = PlayerBinding().create(
|
logic = PlayerBinding().create(
|
||||||
history: context.read<WatchHistoryRepository>(),
|
history: context.read<WatchHistoryRepository>(),
|
||||||
playback: context.read<PlaybackRepository>(),
|
playback: context.read<PlaybackRepository>(),
|
||||||
@@ -179,8 +181,21 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
coordinator: widget.embedded
|
coordinator: widget.embedded
|
||||||
? context.read<PlaybackCoordinator>()
|
? context.read<PlaybackCoordinator>()
|
||||||
: 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;
|
_pageController = logic.pages;
|
||||||
if (!widget.embedded &&
|
if (!widget.embedded &&
|
||||||
!logic.isEpisodeLocked(logic.session.currentEpisode)) {
|
!logic.isEpisodeLocked(logic.session.currentEpisode)) {
|
||||||
@@ -575,6 +590,19 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
Widget _buildPlayer(BuildContext context) {
|
Widget _buildPlayer(BuildContext context) {
|
||||||
final interactions = context.watch<InteractionController>();
|
final interactions = context.watch<InteractionController>();
|
||||||
final l10n = AppLocalizations.of(context)!;
|
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 indices = logic.visibleIndices;
|
||||||
final activePort = logic.session.active;
|
final activePort = logic.session.active;
|
||||||
final activeVideo = activePort is VideoPlayerPort ? activePort : null;
|
final activeVideo = activePort is VideoPlayerPort ? activePort : null;
|
||||||
@@ -1341,8 +1369,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
seriesEpisodes.insert(0, sourceEpisode);
|
seriesEpisodes.insert(0, sourceEpisode);
|
||||||
episodeIndex = 0;
|
episodeIndex = 0;
|
||||||
}
|
}
|
||||||
final sourceUserWantsPlay = logic.session.userWantsPlay;
|
final handoff = PlayerPageHandoff(sourceUserWantsPlay: true);
|
||||||
final handoff = PlayerPageHandoff(sourceUserWantsPlay: sourceUserWantsPlay);
|
|
||||||
_logFullPlayerRoute('present');
|
_logFullPlayerRoute('present');
|
||||||
setState(() => _presentationActive = true);
|
setState(() => _presentationActive = true);
|
||||||
final route = Navigator.of(context).push<void>(
|
final route = Navigator.of(context).push<void>(
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ void main() {
|
|||||||
expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget);
|
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,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
final factory = _RouteVideoFactory();
|
final factory = _RouteVideoFactory();
|
||||||
@@ -245,7 +245,9 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(_fullPlayerFinder(), findsOneWidget);
|
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(
|
expect(
|
||||||
find.byKey(const Key('home-feed-center-play')).hitTestable(),
|
find.byKey(const Key('home-feed-center-play')).hitTestable(),
|
||||||
findsNothing,
|
findsNothing,
|
||||||
@@ -519,6 +521,37 @@ void main() {
|
|||||||
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
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', (
|
testWidgets('should_only_show_progress_buffer_after_video_first_frame', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
@@ -2103,12 +2136,14 @@ class _RouteVideoPlayer extends VideoPlayerPort {
|
|||||||
|
|
||||||
class _RecordingPlayer implements PlayerPort {
|
class _RecordingPlayer implements PlayerPort {
|
||||||
bool playing = false;
|
bool playing = false;
|
||||||
|
int playCalls = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initialize() async {}
|
Future<void> initialize() async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> play() async {
|
Future<void> play() async {
|
||||||
|
playCalls++;
|
||||||
playing = true;
|
playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user