@@ -291,6 +291,9 @@ extension _VideoPlayerBuildMethods on VideoPlayerScreenState {
|
||||
onPlayPauseRequested: () => _playOrPauseWithPlaybackIntent(player!),
|
||||
onSeekCompleted: _notifyWatchTogetherSeek,
|
||||
onBack: _handleBackButton,
|
||||
onDismissPrompt: (_showPlayNextDialog || _showStillWatchingPrompt)
|
||||
? _dismissPlaybackPromptForBack
|
||||
: null,
|
||||
onReachedEnd: ({skipAutoPlayCountdown = false}) =>
|
||||
_onVideoCompleted(true, skipAutoPlayCountdown: skipAutoPlayCountdown),
|
||||
canControl: canControl,
|
||||
|
||||
@@ -109,6 +109,16 @@ extension _VideoPlayerPlaybackPromptMethods on VideoPlayerScreenState {
|
||||
});
|
||||
}
|
||||
|
||||
void _dismissPlaybackPromptForBack() {
|
||||
if (_showPlayNextDialog) {
|
||||
_cancelAutoPlay();
|
||||
return;
|
||||
}
|
||||
if (_showStillWatchingPrompt) {
|
||||
_dismissStillWatching();
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-arm the end-of-video latch so Play Next can fire again. Callers
|
||||
/// decide *when* it is safe to re-arm (media reloaded, or playback moved
|
||||
/// back out of the end region); the latch itself refuses while a prompt
|
||||
|
||||
@@ -982,6 +982,10 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
/// Handle back button press
|
||||
/// For non-host participants in Watch Together, shows leave session confirmation
|
||||
Future<void> _handleBackButton() async {
|
||||
if (_showPlayNextDialog || _showStillWatchingPrompt) {
|
||||
_dismissPlaybackPromptForBack();
|
||||
return;
|
||||
}
|
||||
if (_isHandlingBack) return;
|
||||
_isHandlingBack = true;
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,8 @@ extension _PlexVideoControlsKeyEventMethods on _PlexVideoControlsState {
|
||||
if (PlatformDetector.isTV() && event is KeyDownEvent) {
|
||||
BackKeyCoordinator.markHandled();
|
||||
}
|
||||
final promptBackResult = handlePromptDismissBackKey(event, widget.onDismissPrompt);
|
||||
if (promptBackResult != KeyEventResult.ignored) return true;
|
||||
final backResult = handleBackKeyAction(event, () {
|
||||
if (PlatformDetector.isTV()) {
|
||||
if (_showControls) {
|
||||
@@ -193,6 +195,10 @@ extension _PlexVideoControlsKeyEventMethods on _PlexVideoControlsState {
|
||||
if (PlatformDetector.isTV() && event.logicalKey.isBackKey && event is KeyDownEvent) {
|
||||
BackKeyCoordinator.markHandled();
|
||||
}
|
||||
final promptBackResult = handlePromptDismissBackKey(event, widget.onDismissPrompt);
|
||||
if (promptBackResult != KeyEventResult.ignored) {
|
||||
return promptBackResult;
|
||||
}
|
||||
final backResult = handleBackKeyAction(event, () {
|
||||
if (PlatformDetector.isTV()) {
|
||||
if (_showControls) {
|
||||
|
||||
@@ -159,6 +159,12 @@ bool shouldShowSkipMarkerButton({
|
||||
return hasFirstFrame && hasMarker && !hasPlayNextPrompt && (!skipButtonDismissed || controlsVisible);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
KeyEventResult handlePromptDismissBackKey(KeyEvent event, VoidCallback? onDismissPrompt) {
|
||||
if (onDismissPrompt == null || !event.logicalKey.isBackKey) return KeyEventResult.ignored;
|
||||
return handleBackKeyAction(event, onDismissPrompt);
|
||||
}
|
||||
|
||||
typedef PlaybackSourceChangeCallback =
|
||||
Future<void> Function({
|
||||
int? newMediaIndex,
|
||||
@@ -214,6 +220,10 @@ class PlexVideoControls extends StatefulWidget {
|
||||
/// Called when back button is pressed (for Watch Together session leave confirmation)
|
||||
final VoidCallback? onBack;
|
||||
|
||||
/// Called when Back should dismiss a visible playback prompt before normal
|
||||
/// player back handling.
|
||||
final VoidCallback? onDismissPrompt;
|
||||
|
||||
/// Called when the video has effectively reached the end (e.g. credits extend
|
||||
/// to EOF and can't be seeked past). Parent should route this into its normal
|
||||
/// completion flow so the auto-play-next setting is honored.
|
||||
@@ -314,6 +324,7 @@ class PlexVideoControls extends StatefulWidget {
|
||||
this.onPlayPauseRequested,
|
||||
this.onSeekCompleted,
|
||||
this.onBack,
|
||||
this.onDismissPrompt,
|
||||
this.onReachedEnd,
|
||||
this.canControl = true,
|
||||
this.hasFirstFrame,
|
||||
|
||||
@@ -186,6 +186,38 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('handlePromptDismissBackKey', () {
|
||||
test('ignores back keys when no prompt is visible', () {
|
||||
var dismissCount = 0;
|
||||
|
||||
final result = handlePromptDismissBackKey(_keyUp(LogicalKeyboardKey.goBack), null);
|
||||
|
||||
expect(result, KeyEventResult.ignored);
|
||||
expect(dismissCount, 0);
|
||||
});
|
||||
|
||||
test('consumes key down and dismisses on key up', () {
|
||||
var dismissCount = 0;
|
||||
void dismissPrompt() => dismissCount++;
|
||||
|
||||
final downResult = handlePromptDismissBackKey(_keyDown(LogicalKeyboardKey.goBack), dismissPrompt);
|
||||
final upResult = handlePromptDismissBackKey(_keyUp(LogicalKeyboardKey.goBack), dismissPrompt);
|
||||
|
||||
expect(downResult, KeyEventResult.handled);
|
||||
expect(upResult, KeyEventResult.handled);
|
||||
expect(dismissCount, 1);
|
||||
});
|
||||
|
||||
test('ignores non-back keys', () {
|
||||
var dismissCount = 0;
|
||||
|
||||
final result = handlePromptDismissBackKey(_keyDown(LogicalKeyboardKey.arrowLeft), () => dismissCount++);
|
||||
|
||||
expect(result, KeyEventResult.ignored);
|
||||
expect(dismissCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('SkipMarkerButton', () {
|
||||
testWidgets('tap cancels active auto-skip and performs skip', (tester) async {
|
||||
final focusNode = FocusNode();
|
||||
@@ -723,6 +755,14 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
KeyDownEvent _keyDown(LogicalKeyboardKey key) {
|
||||
return KeyDownEvent(physicalKey: PhysicalKeyboardKey.escape, logicalKey: key, timeStamp: Duration.zero);
|
||||
}
|
||||
|
||||
KeyUpEvent _keyUp(LogicalKeyboardKey key) {
|
||||
return KeyUpEvent(physicalKey: PhysicalKeyboardKey.escape, logicalKey: key, timeStamp: Duration.zero);
|
||||
}
|
||||
|
||||
Future<void> _pumpSkipMarkerButton(
|
||||
WidgetTester tester, {
|
||||
required FocusNode focusNode,
|
||||
|
||||
Reference in New Issue
Block a user