fix: Web and input device switching in video player

This commit is contained in:
PartyDonut 2025-10-27 20:20:26 +01:00
parent 1eeecdc18f
commit 3b4eec6c4f
5 changed files with 110 additions and 63 deletions

View file

@ -13,6 +13,7 @@ import 'package:fladder/screens/video_player/components/video_player_next_wrappe
import 'package:fladder/screens/video_player/video_player_controls.dart'; import 'package:fladder/screens/video_player/video_player_controls.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/themes_data.dart'; import 'package:fladder/util/themes_data.dart';
import 'package:fladder/widgets/shared/back_intent_dpad.dart';
class VideoPlayer extends ConsumerStatefulWidget { class VideoPlayer extends ConsumerStatefulWidget {
const VideoPlayer({super.key}); const VideoPlayer({super.key});
@ -83,38 +84,40 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
}, },
); );
return Material( return BackIntentDpad(
color: Colors.black, child: Material(
child: Theme( color: Colors.black,
data: ThemesData.of(context).dark, child: Theme(
child: Container( data: ThemesData.of(context).dark,
color: Colors.black, child: Container(
child: GestureDetector( color: Colors.black,
onScaleUpdate: (details) { child: GestureDetector(
lastScale = details.scale; onScaleUpdate: (details) {
}, lastScale = details.scale;
onScaleEnd: (details) { },
if (lastScale < 1.0) { onScaleEnd: (details) {
ref.read(videoPlayerSettingsProvider.notifier).setFillScreen(false, context: context); if (lastScale < 1.0) {
} else if (lastScale > 1.0) { ref.read(videoPlayerSettingsProvider.notifier).setFillScreen(false, context: context);
ref.read(videoPlayerSettingsProvider.notifier).setFillScreen(true, context: context); } else if (lastScale > 1.0) {
} ref.read(videoPlayerSettingsProvider.notifier).setFillScreen(true, context: context);
lastScale = 0.0; }
}, lastScale = 0.0;
child: VideoPlayerNextWrapper( },
video: Padding( child: VideoPlayerNextWrapper(
padding: fillScreen ? EdgeInsets.zero : EdgeInsets.only(left: padding.left, right: padding.right), video: Padding(
child: playerController.videoWidget( padding: fillScreen ? EdgeInsets.zero : EdgeInsets.only(left: padding.left, right: padding.right),
const Key("VideoPlayer"), child: playerController.videoWidget(
fillScreen const Key("VideoPlayer"),
? (MediaQuery.of(context).orientation == Orientation.portrait ? videoFit : BoxFit.cover) fillScreen
: videoFit, ? (MediaQuery.of(context).orientation == Orientation.portrait ? videoFit : BoxFit.cover)
: videoFit,
),
), ),
controls: const DesktopControls(),
overlays: [
if (errorPlaying) const _VideoErrorWidget(),
],
), ),
controls: const DesktopControls(),
overlays: [
if (errorPlaying) const _VideoErrorWidget(),
],
), ),
), ),
), ),

View file

@ -48,6 +48,8 @@ class DesktopControls extends ConsumerStatefulWidget {
class _DesktopControlsState extends ConsumerState<DesktopControls> { class _DesktopControlsState extends ConsumerState<DesktopControls> {
final GlobalKey _bottomControlsKey = GlobalKey(); final GlobalKey _bottomControlsKey = GlobalKey();
late final initInputDevice = AdaptiveLayout.inputDeviceOf(context);
late RestartableTimer timer = RestartableTimer( late RestartableTimer timer = RestartableTimer(
const Duration(seconds: 5), const Duration(seconds: 5),
() => mounted ? toggleOverlay(value: false) : null, () => mounted ? toggleOverlay(value: false) : null,
@ -95,12 +97,9 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
children: [ children: [
Positioned.fill( Positioned.fill(
child: GestureDetector( child: GestureDetector(
onTap: AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer onTap: initInputDevice == InputDevice.pointer ? () => player.playOrPause() : () => toggleOverlay(),
? () => player.playOrPause() onDoubleTap:
: () => toggleOverlay(), initInputDevice == InputDevice.pointer ? () => fullScreenHelper.toggleFullScreen(ref) : null,
onDoubleTap: AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer
? () => fullScreenHelper.toggleFullScreen(ref)
: null,
), ),
), ),
if (subtitleWidget != null) subtitleWidget, if (subtitleWidget != null) subtitleWidget,
@ -245,7 +244,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
], ],
), ),
), ),
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch) if (initInputDevice == InputDevice.touch)
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Tooltip( child: Tooltip(
@ -362,7 +361,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
if (AdaptiveLayout.isDesktop(context)) if (initInputDevice == InputDevice.pointer)
Tooltip( Tooltip(
message: context.localized.stop, message: context.localized.stop,
child: IconButton( child: IconButton(
@ -379,7 +378,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
), ),
), ),
}, },
if (AdaptiveLayout.isDesktop(context) && if (initInputDevice == InputDevice.pointer &&
AdaptiveLayout.viewSizeOf(context) > ViewSize.phone) ...[ AdaptiveLayout.viewSizeOf(context) > ViewSize.phone) ...[
VideoVolumeSlider( VideoVolumeSlider(
onChanged: () => resetTimer(), onChanged: () => resetTimer(),
@ -651,7 +650,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
Future<void> clearOverlaySettings() async { Future<void> clearOverlaySettings() async {
toggleOverlay(value: true); toggleOverlay(value: true);
if (AdaptiveLayout.inputDeviceOf(context) != InputDevice.pointer) { if (initInputDevice != InputDevice.pointer) {
ScreenBrightness().resetApplicationScreenBrightness(); ScreenBrightness().resetApplicationScreenBrightness();
} else { } else {
disableFullScreen(); disableFullScreen();
@ -717,7 +716,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
return true; return true;
case VideoHotKeys.exit: case VideoHotKeys.exit:
disableFullScreen(); disableFullScreen();
return true; return false;
case VideoHotKeys.mute: case VideoHotKeys.mute:
if (volume != 0) { if (volume != 0) {
previousVolume = volume; previousVolume = volume;

View file

@ -10,6 +10,7 @@ import 'package:fladder/routes/auto_router.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart'; import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
import 'package:fladder/widgets/navigation_scaffold/components/side_navigation_bar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/side_navigation_bar.dart';
import 'package:fladder/widgets/shared/back_intent_dpad.dart';
class NavigationBody extends ConsumerStatefulWidget { class NavigationBody extends ConsumerStatefulWidget {
final BuildContext parentContext; final BuildContext parentContext;
@ -64,27 +65,29 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
child: widget.child, child: widget.child,
); );
return FocusTraversalGroup( return BackIntentDpad(
policy: GlobalFallbackTraversalPolicy(fallbackNode: navBarNode), child: FocusTraversalGroup(
child: switch (AdaptiveLayout.layoutOf(context)) { policy: GlobalFallbackTraversalPolicy(fallbackNode: navBarNode),
ViewSize.phone => paddedChild(), child: switch (AdaptiveLayout.layoutOf(context)) {
ViewSize.tablet => hasOverlay ViewSize.phone => paddedChild(),
? SideNavigationBar( ViewSize.tablet => hasOverlay
currentIndex: widget.currentIndex, ? SideNavigationBar(
destinations: widget.destinations, currentIndex: widget.currentIndex,
currentLocation: widget.currentLocation, destinations: widget.destinations,
child: paddedChild(), currentLocation: widget.currentLocation,
scaffoldKey: widget.drawerKey, child: paddedChild(),
) scaffoldKey: widget.drawerKey,
: paddedChild(), )
ViewSize.desktop || ViewSize.television => SideNavigationBar( : paddedChild(),
currentIndex: widget.currentIndex, ViewSize.desktop || ViewSize.television => SideNavigationBar(
destinations: widget.destinations, currentIndex: widget.currentIndex,
currentLocation: widget.currentLocation, destinations: widget.destinations,
child: paddedChild(), currentLocation: widget.currentLocation,
scaffoldKey: widget.drawerKey, child: paddedChild(),
) scaffoldKey: widget.drawerKey,
}, )
},
),
); );
} }

View file

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:auto_route/auto_route.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
class BackIntentDpad extends StatelessWidget {
final Widget child;
const BackIntentDpad({required this.child, super.key});
@override
Widget build(BuildContext context) {
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch) {
return child;
}
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.backspace): const BackIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
BackIntent: CallbackAction<BackIntent>(
onInvoke: (intent) async {
final navigator = await context.maybePop();
if (navigator) {
return true;
} else {
return false;
}
},
),
},
child: child,
),
);
}
}
class BackIntent extends Intent {
const BackIntent();
}

View file

@ -56,7 +56,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
Future<void> init() async { Future<void> init() async {
if (!initializedWrapper) { if (!initializedWrapper) {
initializedWrapper = true; initializedWrapper = true;
if (!kIsWeb || Platform.isAndroid) { if (!kIsWeb && Platform.isAndroid) {
VideoPlayerControlsCallback.setUp(this); VideoPlayerControlsCallback.setUp(this);
} }
await AudioService.init( await AudioService.init(