mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 13:38:13 -08:00
feat: Playback rate keyboard shortcuts (#484)
This commit is contained in:
commit
d6863fe504
8 changed files with 132 additions and 3 deletions
|
|
@ -1303,6 +1303,8 @@
|
|||
"mute": "Mute",
|
||||
"volumeUp": "Volume Up",
|
||||
"volumeDown": "Volume Down",
|
||||
"speedUp": "Speed Up",
|
||||
"speedDown": "Speed Down",
|
||||
"nextVideo": "Next Video",
|
||||
"prevVideo": "Previous Video",
|
||||
"nextChapter": "Next Chapter",
|
||||
|
|
@ -1326,5 +1328,13 @@
|
|||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"speedIndicator": "Playback rate: {speed}",
|
||||
"@speedIndicator": {
|
||||
"placeholders": {
|
||||
"speed": {
|
||||
"type": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ enum VideoHotKeys {
|
|||
mute,
|
||||
volumeUp,
|
||||
volumeDown,
|
||||
speedUp,
|
||||
speedDown,
|
||||
nextVideo,
|
||||
prevVideo,
|
||||
nextChapter,
|
||||
|
|
@ -38,6 +40,8 @@ enum VideoHotKeys {
|
|||
VideoHotKeys.mute => context.localized.mute,
|
||||
VideoHotKeys.volumeUp => context.localized.volumeUp,
|
||||
VideoHotKeys.volumeDown => context.localized.volumeDown,
|
||||
VideoHotKeys.speedUp => context.localized.speedUp,
|
||||
VideoHotKeys.speedDown => context.localized.speedDown,
|
||||
VideoHotKeys.nextVideo => context.localized.nextVideo,
|
||||
VideoHotKeys.prevVideo => context.localized.prevVideo,
|
||||
VideoHotKeys.nextChapter => context.localized.nextChapter,
|
||||
|
|
@ -180,6 +184,8 @@ Map<VideoHotKeys, KeyCombination> get _defaultVideoHotKeys => {
|
|||
VideoHotKeys.mute => KeyCombination(key: LogicalKeyboardKey.keyM),
|
||||
VideoHotKeys.volumeUp => KeyCombination(key: LogicalKeyboardKey.arrowUp),
|
||||
VideoHotKeys.volumeDown => KeyCombination(key: LogicalKeyboardKey.arrowDown),
|
||||
VideoHotKeys.speedUp => KeyCombination(key: LogicalKeyboardKey.arrowUp, modifier: LogicalKeyboardKey.controlLeft),
|
||||
VideoHotKeys.speedDown => KeyCombination(key: LogicalKeyboardKey.arrowDown, modifier: LogicalKeyboardKey.controlLeft),
|
||||
VideoHotKeys.prevVideo =>
|
||||
KeyCombination(key: LogicalKeyboardKey.keyP, modifier: LogicalKeyboardKey.shiftLeft),
|
||||
VideoHotKeys.nextVideo =>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ const _$VideoHotKeysEnumMap = {
|
|||
VideoHotKeys.mute: 'mute',
|
||||
VideoHotKeys.volumeUp: 'volumeUp',
|
||||
VideoHotKeys.volumeDown: 'volumeDown',
|
||||
VideoHotKeys.speedUp: 'speedUp',
|
||||
VideoHotKeys.speedDown: 'speedDown',
|
||||
VideoHotKeys.nextVideo: 'nextVideo',
|
||||
VideoHotKeys.prevVideo: 'prevVideo',
|
||||
VideoHotKeys.nextChapter: 'nextChapter',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ final videoPlayerSettingsProvider =
|
|||
return VideoPlayerSettingsProviderNotifier(ref);
|
||||
});
|
||||
|
||||
final playbackRateProvider = StateProvider<double>((ref) => 1.0);
|
||||
|
||||
class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSettingsModel> {
|
||||
VideoPlayerSettingsProviderNotifier(this.ref) : super(VideoPlayerSettingsModel());
|
||||
|
||||
|
|
@ -67,11 +69,24 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
|
|||
ref.read(videoPlayerProvider).setVolume(value);
|
||||
}
|
||||
|
||||
void steppedSpeed(double i) {
|
||||
var value = double.parse(
|
||||
((ref.read(playbackRateProvider) + i).clamp(0.25, 3)).toStringAsFixed(2),
|
||||
);
|
||||
|
||||
if ((value - 1.0).abs() <= 0.06) {
|
||||
value = 1.0;
|
||||
}
|
||||
|
||||
ref.read(playbackRateProvider.notifier).state = value;
|
||||
ref.read(videoPlayerProvider).setSpeed(value);
|
||||
}
|
||||
|
||||
void toggleOrientation(Set<DeviceOrientation>? orientation) =>
|
||||
state = state.copyWith(allowedOrientations: orientation);
|
||||
|
||||
void setShortcuts(MapEntry<VideoHotKeys, KeyCombination> newEntry) {
|
||||
state = state.copyWith(hotKeys: state.hotKeys.setOrRemove(newEntry, state.defaultShortCuts));
|
||||
state = state.copyWith(hotKeys: state.hotKeys.setOrRemove(newEntry, state.defaultShortCuts));
|
||||
}
|
||||
|
||||
void nextChapter() {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import 'package:fladder/widgets/shared/fladder_slider.dart';
|
|||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
import 'package:fladder/widgets/shared/spaced_list_tile.dart';
|
||||
|
||||
final playbackRateProvider = StateProvider<double>((ref) => 1.0);
|
||||
|
||||
Future<void> showVideoPlayerOptions(BuildContext context, Function() minimizePlayer) {
|
||||
return showBottomSheetPill(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
class VideoPlayerSpeedIndicator extends ConsumerStatefulWidget {
|
||||
const VideoPlayerSpeedIndicator({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _VideoPlayerSpeedIndicatorState();
|
||||
}
|
||||
|
||||
class _VideoPlayerSpeedIndicatorState extends ConsumerState<VideoPlayerSpeedIndicator> {
|
||||
late double currentSpeed = ref.read(playbackRateProvider);
|
||||
|
||||
bool showIndicator = false;
|
||||
late final timer = RestartableTimer(const Duration(seconds: 1), () {
|
||||
setState(() {
|
||||
showIndicator = false;
|
||||
});
|
||||
});
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
showIndicator = false;
|
||||
timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(
|
||||
playbackRateProvider,
|
||||
(previous, next) {
|
||||
setState(() {
|
||||
showIndicator = true;
|
||||
currentSpeed = next;
|
||||
});
|
||||
timer.reset();
|
||||
},
|
||||
);
|
||||
return IgnorePointer(
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
opacity: showIndicator ? 1 : 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.85),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: currentSpeed < 1 ? pi : 0,
|
||||
child: Icon(speedIcon(currentSpeed)),
|
||||
),
|
||||
Text(context.localized.speedIndicator(currentSpeed)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IconData speedIcon(double value) {
|
||||
if (value < 1) {
|
||||
return IconsaxPlusBroken.flash;
|
||||
}
|
||||
if (value == 1) {
|
||||
return IconsaxPlusLinear.flash_slash;
|
||||
}
|
||||
return IconsaxPlusLinear.flash;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import 'package:fladder/screens/video_player/components/video_player_controls_ex
|
|||
import 'package:fladder/screens/video_player/components/video_player_options_sheet.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_quality_controls.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_seek_indicator.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_speed_indicator.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_volume_indicator.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_progress_bar.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_volume_slider.dart';
|
||||
|
|
@ -125,6 +126,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
),
|
||||
const VideoPlayerSeekIndicator(),
|
||||
const VideoPlayerVolumeIndicator(),
|
||||
const VideoPlayerSpeedIndicator(),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final position = ref.watch(mediaPlaybackProvider.select((value) => value.position));
|
||||
|
|
@ -697,6 +699,14 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
resetTimer();
|
||||
ref.read(videoPlayerSettingsProvider.notifier).steppedVolume(-5);
|
||||
return true;
|
||||
case VideoHotKeys.speedUp:
|
||||
resetTimer();
|
||||
ref.read(videoPlayerSettingsProvider.notifier).steppedSpeed(0.1);
|
||||
return true;
|
||||
case VideoHotKeys.speedDown:
|
||||
resetTimer();
|
||||
ref.read(videoPlayerSettingsProvider.notifier).steppedSpeed(-0.1);
|
||||
return true;
|
||||
case VideoHotKeys.fullScreen:
|
||||
fullScreenHelper.toggleFullScreen(ref);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class MediaControlsWrapper extends BaseAudioHandler {
|
|||
Future<int> setSubtitleTrack(SubStreamModel? model, PlaybackModel playbackModel) async =>
|
||||
await _player?.setSubtitleTrack(model, playbackModel) ?? -1;
|
||||
|
||||
Future<void> setVolume(double speed) async => _player?.setVolume(speed);
|
||||
Future<void> setVolume(double volume) async => _player?.setVolume(volume);
|
||||
|
||||
@override
|
||||
Future<void> seek(Duration position) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue