feature: Added settings to force the player into certain orientations (#108)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2024-11-02 18:44:18 +01:00 committed by GitHub
parent 691293648b
commit c32f71b368
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 170 additions and 5 deletions

View file

@ -1099,5 +1099,11 @@
"deleteFilterConfirmation": "Are you sure you want to delete this filter?",
"libraryFiltersLimitReached" : "Filter limit reached (10) remove some filters",
"libraryFiltersRemoveAll": "Remove all filters",
"libraryFiltersRemoveAllConfirm": "This will delete all saved filters for every library"
"libraryFiltersRemoveAllConfirm": "This will delete all saved filters for every library",
"playerSettingsOrientationTitle": "Player orientation",
"playerSettingsOrientationDesc": "Force the video player into certain orientations",
"deviceOrientationPortraitUp": "Portrait Up",
"deviceOrientationPortraitDown": "Portrait Down",
"deviceOrientationLandscapeLeft": "Landscape Left",
"deviceOrientationLandscapeRight": "Landscape Right"
}

View file

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -20,6 +21,7 @@ class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel {
@Default(true) bool hardwareAccel,
@Default(false) bool useLibass,
@Default(100) double internalVolume,
Set<DeviceOrientation>? allowedOrientations,
@Default(AutoNextType.static) AutoNextType nextVideoType,
String? audioDevice,
}) = _VideoPlayerSettingsModel;

View file

@ -27,6 +27,8 @@ mixin _$VideoPlayerSettingsModel {
bool get hardwareAccel => throw _privateConstructorUsedError;
bool get useLibass => throw _privateConstructorUsedError;
double get internalVolume => throw _privateConstructorUsedError;
Set<DeviceOrientation>? get allowedOrientations =>
throw _privateConstructorUsedError;
AutoNextType get nextVideoType => throw _privateConstructorUsedError;
String? get audioDevice => throw _privateConstructorUsedError;
@ -53,6 +55,7 @@ abstract class $VideoPlayerSettingsModelCopyWith<$Res> {
bool hardwareAccel,
bool useLibass,
double internalVolume,
Set<DeviceOrientation>? allowedOrientations,
AutoNextType nextVideoType,
String? audioDevice});
}
@ -79,6 +82,7 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res,
Object? hardwareAccel = null,
Object? useLibass = null,
Object? internalVolume = null,
Object? allowedOrientations = freezed,
Object? nextVideoType = null,
Object? audioDevice = freezed,
}) {
@ -107,6 +111,10 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res,
? _value.internalVolume
: internalVolume // ignore: cast_nullable_to_non_nullable
as double,
allowedOrientations: freezed == allowedOrientations
? _value.allowedOrientations
: allowedOrientations // ignore: cast_nullable_to_non_nullable
as Set<DeviceOrientation>?,
nextVideoType: null == nextVideoType
? _value.nextVideoType
: nextVideoType // ignore: cast_nullable_to_non_nullable
@ -135,6 +143,7 @@ abstract class _$$VideoPlayerSettingsModelImplCopyWith<$Res>
bool hardwareAccel,
bool useLibass,
double internalVolume,
Set<DeviceOrientation>? allowedOrientations,
AutoNextType nextVideoType,
String? audioDevice});
}
@ -160,6 +169,7 @@ class __$$VideoPlayerSettingsModelImplCopyWithImpl<$Res>
Object? hardwareAccel = null,
Object? useLibass = null,
Object? internalVolume = null,
Object? allowedOrientations = freezed,
Object? nextVideoType = null,
Object? audioDevice = freezed,
}) {
@ -188,6 +198,10 @@ class __$$VideoPlayerSettingsModelImplCopyWithImpl<$Res>
? _value.internalVolume
: internalVolume // ignore: cast_nullable_to_non_nullable
as double,
allowedOrientations: freezed == allowedOrientations
? _value._allowedOrientations
: allowedOrientations // ignore: cast_nullable_to_non_nullable
as Set<DeviceOrientation>?,
nextVideoType: null == nextVideoType
? _value.nextVideoType
: nextVideoType // ignore: cast_nullable_to_non_nullable
@ -211,9 +225,11 @@ class _$VideoPlayerSettingsModelImpl extends _VideoPlayerSettingsModel
this.hardwareAccel = true,
this.useLibass = false,
this.internalVolume = 100,
final Set<DeviceOrientation>? allowedOrientations,
this.nextVideoType = AutoNextType.static,
this.audioDevice})
: super._();
: _allowedOrientations = allowedOrientations,
super._();
factory _$VideoPlayerSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
_$$VideoPlayerSettingsModelImplFromJson(json);
@ -235,6 +251,17 @@ class _$VideoPlayerSettingsModelImpl extends _VideoPlayerSettingsModel
@override
@JsonKey()
final double internalVolume;
final Set<DeviceOrientation>? _allowedOrientations;
@override
Set<DeviceOrientation>? get allowedOrientations {
final value = _allowedOrientations;
if (value == null) return null;
if (_allowedOrientations is EqualUnmodifiableSetView)
return _allowedOrientations;
// ignore: implicit_dynamic_type
return EqualUnmodifiableSetView(value);
}
@override
@JsonKey()
final AutoNextType nextVideoType;
@ -243,7 +270,7 @@ class _$VideoPlayerSettingsModelImpl extends _VideoPlayerSettingsModel
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, internalVolume: $internalVolume, nextVideoType: $nextVideoType, audioDevice: $audioDevice)';
return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, audioDevice: $audioDevice)';
}
@override
@ -257,6 +284,7 @@ class _$VideoPlayerSettingsModelImpl extends _VideoPlayerSettingsModel
..add(DiagnosticsProperty('hardwareAccel', hardwareAccel))
..add(DiagnosticsProperty('useLibass', useLibass))
..add(DiagnosticsProperty('internalVolume', internalVolume))
..add(DiagnosticsProperty('allowedOrientations', allowedOrientations))
..add(DiagnosticsProperty('nextVideoType', nextVideoType))
..add(DiagnosticsProperty('audioDevice', audioDevice));
}
@ -286,6 +314,7 @@ abstract class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel {
final bool hardwareAccel,
final bool useLibass,
final double internalVolume,
final Set<DeviceOrientation>? allowedOrientations,
final AutoNextType nextVideoType,
final String? audioDevice}) = _$VideoPlayerSettingsModelImpl;
_VideoPlayerSettingsModel._() : super._();
@ -306,6 +335,8 @@ abstract class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel {
@override
double get internalVolume;
@override
Set<DeviceOrientation>? get allowedOrientations;
@override
AutoNextType get nextVideoType;
@override
String? get audioDevice;

View file

@ -16,6 +16,9 @@ _$VideoPlayerSettingsModelImpl _$$VideoPlayerSettingsModelImplFromJson(
hardwareAccel: json['hardwareAccel'] as bool? ?? true,
useLibass: json['useLibass'] as bool? ?? false,
internalVolume: (json['internalVolume'] as num?)?.toDouble() ?? 100,
allowedOrientations: (json['allowedOrientations'] as List<dynamic>?)
?.map((e) => $enumDecode(_$DeviceOrientationEnumMap, e))
.toSet(),
nextVideoType:
$enumDecodeNullable(_$AutoNextTypeEnumMap, json['nextVideoType']) ??
AutoNextType.static,
@ -31,6 +34,9 @@ Map<String, dynamic> _$$VideoPlayerSettingsModelImplToJson(
'hardwareAccel': instance.hardwareAccel,
'useLibass': instance.useLibass,
'internalVolume': instance.internalVolume,
'allowedOrientations': instance.allowedOrientations
?.map((e) => _$DeviceOrientationEnumMap[e]!)
.toList(),
'nextVideoType': _$AutoNextTypeEnumMap[instance.nextVideoType]!,
'audioDevice': instance.audioDevice,
};
@ -45,6 +51,13 @@ const _$BoxFitEnumMap = {
BoxFit.scaleDown: 'scaleDown',
};
const _$DeviceOrientationEnumMap = {
DeviceOrientation.portraitUp: 'portraitUp',
DeviceOrientation.landscapeLeft: 'landscapeLeft',
DeviceOrientation.portraitDown: 'portraitDown',
DeviceOrientation.landscapeRight: 'landscapeRight',
};
const _$AutoNextTypeEnumMap = {
AutoNextType.off: 'off',
AutoNextType.smart: 'smart',

View file

@ -6,7 +6,7 @@ part of 'library_filters_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$libraryFiltersHash() => r'7b4661651df7e0c019dca5bb7eb6433bcd8b3ebb';
String _$libraryFiltersHash() => r'fd98699d8d7c1db6daefa6e53d5d90f989a8f776';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:screen_brightness/screen_brightness.dart';
@ -63,4 +64,7 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
state = state.copyWith(internalVolume: value);
ref.read(videoPlayerProvider).setVolume(value);
}
void toggleOrientation(Set<DeviceOrientation>? orientation) =>
state = state.copyWith(allowedOrientations: orientation);
}

View file

@ -22,7 +22,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
);
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
String _$userHash() => r'418b3d4ade830479db9f48c7793ac5b646778b82';
String _$userHash() => r'e83369c0d569d5a862aa1b92f3f0a45a9d1fe446';
/// See also [User].
@ProviderFor(User)

View file

@ -14,6 +14,7 @@ import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
import 'package:fladder/screens/settings/widgets/settings_message_box.dart';
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/box_fit_extension.dart';
import 'package:fladder/util/localization_helper.dart';
@ -151,6 +152,12 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
);
},
),
if (!AdaptiveLayout.of(context).isDesktop && !kIsWeb)
SettingsListTile(
label: Text(context.localized.playerSettingsOrientationTitle),
subLabel: Text(context.localized.playerSettingsOrientationDesc),
onTap: () => showOrientationOptions(context, ref),
),
],
),
);

View file

@ -1,4 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:collection/collection.dart';
import 'package:ficonsax/ficonsax.dart';
@ -19,6 +21,7 @@ import 'package:fladder/screens/playlists/add_to_playlists.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/refresh_state.dart';
@ -176,6 +179,11 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
],
),
),
if (!AdaptiveLayout.of(context).isDesktop && !kIsWeb)
SpacedListTile(
title: Text(context.localized.playerSettingsOrientationTitle),
onTap: () => showOrientationOptions(context, ref),
),
ListTile(
onTap: () {
Navigator.of(context).pop();
@ -492,3 +500,70 @@ Future<void> showPlaybackSpeed(BuildContext context) {
},
);
}
Future<void> showOrientationOptions(BuildContext context, WidgetRef ref) async {
Set<DeviceOrientation> orientations = ref
.read(videoPlayerSettingsProvider
.select((value) => value.allowedOrientations ?? Set.from(DeviceOrientation.values)))
.toSet();
void toggleOrientation(DeviceOrientation orientation) {
if (orientations.contains(orientation) && orientations.length > 1) {
orientations.remove(orientation);
} else {
orientations.add(orientation);
}
}
await showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, state) {
return SimpleDialog(
contentPadding: const EdgeInsets.only(top: 8, bottom: 24),
title: Row(children: [Text(context.localized.playbackRate)]),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12).copyWith(top: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Divider(),
...DeviceOrientation.values.map(
(orientation) => CheckboxListTile.adaptive(
title: Text(orientation.label(context)),
value: orientations.contains(orientation),
onChanged: (value) {
state(() => toggleOrientation(orientation));
},
),
),
const Divider(),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(context.localized.cancel),
),
FilledButton(
onPressed: () {
Navigator.of(context).pop();
ref.read(videoPlayerSettingsProvider.notifier).toggleOrientation(orientations);
},
child: Text(context.localized.save),
),
].addInBetween(const SizedBox(width: 8)),
)
].addInBetween(const SizedBox(width: 8)),
),
)
],
);
});
},
);
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit_video/media_kit_video.dart';
@ -49,6 +50,7 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
super.dispose();
}
@ -58,6 +60,9 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
WidgetsBinding.instance.addObserver(this);
Future.microtask(() {
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.fullScreen));
final orientations = ref.read(videoPlayerSettingsProvider.select((value) => value.allowedOrientations));
SystemChrome.setPreferredOrientations(
orientations?.isNotEmpty == true ? orientations!.toList() : DeviceOrientation.values);
return ref.read(videoPlayerSettingsProvider.notifier).setSavedBrightness();
});
}
@ -70,6 +75,15 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
final playerController = ref.watch(videoPlayerProvider.select((value) => value.controller));
ref.listen(
videoPlayerSettingsProvider.select((value) => value.allowedOrientations),
(previous, next) {
if (previous != next) {
SystemChrome.setPreferredOrientations(next?.isNotEmpty == true ? next!.toList() : DeviceOrientation.values);
}
},
);
return Material(
color: Colors.black,
child: Theme(

View file

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fladder/util/localization_helper.dart';
extension DeviceOrientationExtension on DeviceOrientation {
String label(BuildContext context) => switch (this) {
DeviceOrientation.portraitUp => context.localized.deviceOrientationPortraitUp,
DeviceOrientation.landscapeLeft => context.localized.deviceOrientationLandscapeLeft,
DeviceOrientation.portraitDown => context.localized.deviceOrientationPortraitDown,
DeviceOrientation.landscapeRight => context.localized.deviceOrientationLandscapeRight,
};
}