Skip to content

Commit 9c7a8a1

Browse files
authored
Merge pull request #876 from Ortes/keyboard-controls
Add keyboard controls seek forward and backward and fullscreen escape on desktop
2 parents d8af8fe + 6cab13d commit 9c7a8a1

File tree

1 file changed

+89
-32
lines changed

1 file changed

+89
-32
lines changed

lib/src/material/material_desktop_controls.dart

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:chewie/src/models/option_item.dart';
1212
import 'package:chewie/src/models/subtitle_model.dart';
1313
import 'package:chewie/src/notifiers/index.dart';
1414
import 'package:flutter/material.dart';
15+
import 'package:flutter/services.dart';
1516
import 'package:provider/provider.dart';
1617
import 'package:video_player/video_player.dart';
1718

@@ -49,16 +50,36 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
4950

5051
late VideoPlayerController controller;
5152
ChewieController? _chewieController;
53+
late final FocusNode _focusNode;
5254

5355
// We know that _chewieController is set in didChangeDependencies
5456
ChewieController get chewieController => _chewieController!;
5557

5658
@override
5759
void initState() {
5860
super.initState();
61+
_focusNode = FocusNode();
62+
_focusNode.requestFocus();
5963
notifier = Provider.of<PlayerNotifier>(context, listen: false);
6064
}
6165

66+
void _handleKeyPress(event) {
67+
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.space) {
68+
_playPause();
69+
} else if (event is KeyDownEvent &&
70+
event.logicalKey == LogicalKeyboardKey.arrowRight) {
71+
_seekForward();
72+
} else if (event is KeyDownEvent &&
73+
event.logicalKey == LogicalKeyboardKey.arrowLeft) {
74+
_seekBackward();
75+
} else if (event is KeyDownEvent &&
76+
event.logicalKey == LogicalKeyboardKey.escape) {
77+
if (chewieController.isFullScreen) {
78+
_onExpandCollapse();
79+
}
80+
}
81+
}
82+
6283
@override
6384
Widget build(BuildContext context) {
6485
if (_latestValue.hasError) {
@@ -75,39 +96,44 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
7596
);
7697
}
7798

78-
return MouseRegion(
79-
onHover: (_) {
80-
_cancelAndRestartTimer();
81-
},
82-
child: GestureDetector(
83-
onTap: () => _cancelAndRestartTimer(),
84-
child: AbsorbPointer(
85-
absorbing: notifier.hideStuff,
86-
child: Stack(
87-
children: [
88-
if (_displayBufferingIndicator)
89-
_chewieController?.bufferingBuilder?.call(context) ??
90-
const Center(
91-
child: CircularProgressIndicator(),
92-
)
93-
else
94-
_buildHitArea(),
95-
Column(
96-
mainAxisAlignment: MainAxisAlignment.end,
97-
children: <Widget>[
98-
if (_subtitleOn)
99-
Transform.translate(
100-
offset: Offset(
101-
0.0,
102-
notifier.hideStuff ? barHeight * 0.8 : 0.0,
99+
return KeyboardListener(
100+
focusNode: _focusNode,
101+
onKeyEvent: _handleKeyPress,
102+
child: MouseRegion(
103+
onHover: (_) {
104+
_focusNode.requestFocus();
105+
_cancelAndRestartTimer();
106+
},
107+
child: GestureDetector(
108+
onTap: () => _cancelAndRestartTimer(),
109+
child: AbsorbPointer(
110+
absorbing: notifier.hideStuff,
111+
child: Stack(
112+
children: [
113+
if (_displayBufferingIndicator)
114+
_chewieController?.bufferingBuilder?.call(context) ??
115+
const Center(
116+
child: CircularProgressIndicator(),
117+
)
118+
else
119+
_buildHitArea(),
120+
Column(
121+
mainAxisAlignment: MainAxisAlignment.end,
122+
children: <Widget>[
123+
if (_subtitleOn)
124+
Transform.translate(
125+
offset: Offset(
126+
0.0,
127+
notifier.hideStuff ? barHeight * 0.8 : 0.0,
128+
),
129+
child: _buildSubtitles(
130+
context, chewieController.subtitle!),
103131
),
104-
child:
105-
_buildSubtitles(context, chewieController.subtitle!),
106-
),
107-
_buildBottomBar(context),
108-
],
109-
),
110-
],
132+
_buildBottomBar(context),
133+
],
134+
),
135+
],
136+
),
111137
),
112138
),
113139
),
@@ -117,6 +143,7 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
117143
@override
118144
void dispose() {
119145
_dispose();
146+
_focusNode.dispose();
120147
super.dispose();
121148
}
122149

@@ -565,6 +592,36 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
565592
});
566593
}
567594

595+
void _seekBackward() {
596+
_seekRelative(
597+
const Duration(
598+
seconds: -10,
599+
),
600+
);
601+
}
602+
603+
void _seekForward() {
604+
_seekRelative(
605+
const Duration(
606+
seconds: 10,
607+
),
608+
);
609+
}
610+
611+
void _seekRelative(Duration relativeSeek) {
612+
_cancelAndRestartTimer();
613+
final position = _latestValue.position + relativeSeek;
614+
final duration = _latestValue.duration;
615+
616+
if (position < Duration.zero) {
617+
controller.seekTo(Duration.zero);
618+
} else if (position > duration) {
619+
controller.seekTo(duration);
620+
} else {
621+
controller.seekTo(position);
622+
}
623+
}
624+
568625
Widget _buildProgressBar() {
569626
return Expanded(
570627
child: MaterialVideoProgressBar(

0 commit comments

Comments
 (0)