mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 07:28:14 -07:00
feature: Video quality options (#234)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
957ad6c991
commit
935d6fe176
25 changed files with 644 additions and 232 deletions
|
|
@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/providers/connectivity_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||
|
|
@ -17,6 +18,7 @@ import 'package:fladder/screens/settings/widgets/subtitle_editor.dart';
|
|||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_options_sheet.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/bitrate_helper.dart';
|
||||
import 'package:fladder/util/box_fit_extension.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/option_dialogue.dart';
|
||||
|
|
@ -37,6 +39,9 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
final provider = ref.read(videoPlayerSettingsProvider.notifier);
|
||||
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||
|
||||
final connectionState = ref.watch(connectivityStatusProvider);
|
||||
|
||||
return Card(
|
||||
elevation: showBackground ? 2 : 0,
|
||||
child: SettingsScaffold(
|
||||
|
|
@ -80,6 +85,50 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: _StatusIndicator(
|
||||
homeInternet: connectionState.homeInternet,
|
||||
label: Text(context.localized.homeStreamingQualityTitle),
|
||||
),
|
||||
subLabel: Text(context.localized.homeStreamingQualityDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
videoPlayerSettingsProvider.select((value) => value.maxHomeBitrate.label(context)),
|
||||
),
|
||||
itemBuilder: (context) => Bitrate.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () => ref.read(videoPlayerSettingsProvider.notifier).state =
|
||||
ref.read(videoPlayerSettingsProvider).copyWith(maxHomeBitrate: entry),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: _StatusIndicator(
|
||||
homeInternet: !connectionState.homeInternet,
|
||||
label: Text(context.localized.internetStreamingQualityTitle),
|
||||
),
|
||||
subLabel: Text(context.localized.internetStreamingQualityDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
videoPlayerSettingsProvider.select((value) => value.maxInternetBitrate.label(context)),
|
||||
),
|
||||
itemBuilder: (context) => Bitrate.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () => ref.read(videoPlayerSettingsProvider.notifier).state =
|
||||
ref.read(videoPlayerSettingsProvider).copyWith(maxInternetBitrate: entry),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
SettingsLabelDivider(label: context.localized.advanced),
|
||||
if (PlayerOptions.available.length != 1)
|
||||
|
|
@ -205,3 +254,30 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatusIndicator extends StatelessWidget {
|
||||
final bool homeInternet;
|
||||
final Widget label;
|
||||
const _StatusIndicator({required this.homeInternet, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (homeInternet) ...[
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.greenAccent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
label,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ import 'package:fladder/providers/video_player_provider.dart';
|
|||
import 'package:fladder/screens/collections/add_to_collection.dart';
|
||||
import 'package:fladder/screens/metadata/info_screen.dart';
|
||||
import 'package:fladder/screens/playlists/add_to_playlists.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_quality_controls.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_queue.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_subtitle_controls.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/device_orientation_extension.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/map_bool_helper.dart';
|
||||
import 'package:fladder/util/refresh_state.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||
|
|
@ -63,6 +65,7 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
final currentItem = ref.watch(playBackModel.select((value) => value?.item));
|
||||
final videoSettings = ref.watch(videoPlayerSettingsProvider);
|
||||
final currentMediaStreams = ref.watch(playBackModel.select((value) => value?.mediaStreams));
|
||||
final bitRateOptions = ref.watch(playBackModel.select((value) => value?.bitRateOptions));
|
||||
|
||||
Widget mainPage() {
|
||||
return ListView(
|
||||
|
|
@ -202,6 +205,24 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (bitRateOptions?.isNotEmpty == true)
|
||||
ListTile(
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(context.localized.qualityOptionsTitle),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(bitRateOptions?.enabledFirst.keys.firstOrNull?.label(context) ?? "")
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
openQualityOptions(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
Future<void> openQualityOptions(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => const _QualityOptionsDialogue(),
|
||||
);
|
||||
}
|
||||
|
||||
class _QualityOptionsDialogue extends ConsumerWidget {
|
||||
const _QualityOptionsDialogue();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final playbackModel = ref.watch(playBackModel);
|
||||
final qualityOptions = playbackModel?.bitRateOptions;
|
||||
|
||||
return Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
context.localized.qualityOptionsTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Divider(),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: qualityOptions?.entries
|
||||
.map(
|
||||
(entry) => RadioListTile(
|
||||
value: entry.value,
|
||||
groupValue: true,
|
||||
onChanged: (value) async {
|
||||
final newModel = await playbackModel?.setQualityOption(
|
||||
qualityOptions.map(
|
||||
(key, value) => MapEntry(key, key == entry.key ? true : false),
|
||||
),
|
||||
);
|
||||
ref.read(playBackModel.notifier).update((state) => newModel);
|
||||
if (newModel != null) {
|
||||
await ref.read(playbackModelHelper).shouldReload(newModel);
|
||||
}
|
||||
context.router.maybePop();
|
||||
},
|
||||
title: Text(entry.key.label(context)),
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import 'package:fladder/screens/shared/default_title_bar.dart';
|
|||
import 'package:fladder/screens/video_player/components/video_playback_information.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_player_controls_extras.dart';
|
||||
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_progress_bar.dart';
|
||||
import 'package:fladder/screens/video_player/components/video_volume_slider.dart';
|
||||
|
|
@ -270,6 +271,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
Widget bottomButtons(BuildContext context) {
|
||||
return Consumer(builder: (context, ref, child) {
|
||||
final mediaPlayback = ref.watch(mediaPlaybackProvider);
|
||||
final bitRateOptions = ref.watch(playBackModel.select((value) => value?.bitRateOptions));
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
|
|
@ -370,13 +372,16 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
child: IconButton(
|
||||
onPressed: () => closePlayer(), icon: const Icon(IconsaxOutline.close_square))),
|
||||
const Spacer(),
|
||||
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer &&
|
||||
if (AdaptiveLayout.viewSizeOf(context) >= ViewSize.desktop &&
|
||||
ref.read(videoPlayerProvider).hasPlayer) ...{
|
||||
// OpenQueueButton(x),
|
||||
// ChapterButton(
|
||||
// position: position,
|
||||
// player: ref.read(videoPlayerProvider).player!,
|
||||
// ),
|
||||
if (bitRateOptions?.isNotEmpty == true)
|
||||
Tooltip(
|
||||
message: context.localized.qualityOptionsTitle,
|
||||
child: IconButton(
|
||||
onPressed: () => openQualityOptions(context),
|
||||
icon: const Icon(IconsaxOutline.speedometer),
|
||||
),
|
||||
),
|
||||
Listener(
|
||||
onPointerSignal: (event) {
|
||||
if (event is PointerScrollEvent) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue