diff --git a/android/app/src/main/kotlin/nl/jknaapen/fladder/api/PlayerSettingsHelper.g.kt b/android/app/src/main/kotlin/nl/jknaapen/fladder/api/PlayerSettingsHelper.g.kt index ffbf3fa..ca1f068 100644 --- a/android/app/src/main/kotlin/nl/jknaapen/fladder/api/PlayerSettingsHelper.g.kt +++ b/android/app/src/main/kotlin/nl/jknaapen/fladder/api/PlayerSettingsHelper.g.kt @@ -93,6 +93,7 @@ enum class SegmentSkip(val raw: Int) { /** Generated class from Pigeon that represents data sent in messages. */ data class PlayerSettings ( + val enableTunneling: Boolean, val skipTypes: Map, val skipForward: Long, val skipBackward: Long @@ -100,14 +101,16 @@ data class PlayerSettings ( { companion object { fun fromList(pigeonVar_list: List): PlayerSettings { - val skipTypes = pigeonVar_list[0] as Map - val skipForward = pigeonVar_list[1] as Long - val skipBackward = pigeonVar_list[2] as Long - return PlayerSettings(skipTypes, skipForward, skipBackward) + val enableTunneling = pigeonVar_list[0] as Boolean + val skipTypes = pigeonVar_list[1] as Map + val skipForward = pigeonVar_list[2] as Long + val skipBackward = pigeonVar_list[3] as Long + return PlayerSettings(enableTunneling, skipTypes, skipForward, skipBackward) } } fun toList(): List { return listOf( + enableTunneling, skipTypes, skipForward, skipBackward, diff --git a/android/app/src/main/kotlin/nl/jknaapen/fladder/composables/controls/ItemHeader.kt b/android/app/src/main/kotlin/nl/jknaapen/fladder/composables/controls/ItemHeader.kt index ca82efd..7d9a56d 100644 --- a/android/app/src/main/kotlin/nl/jknaapen/fladder/composables/controls/ItemHeader.kt +++ b/android/app/src/main/kotlin/nl/jknaapen/fladder/composables/controls/ItemHeader.kt @@ -32,8 +32,8 @@ fun ItemHeader(state: PlayableData?) { contentDescription = title ?: "logo", alignment = Alignment.CenterStart, modifier = Modifier - .fillMaxHeight(0.25f) - .fillMaxWidth(0.5f) + .fillMaxHeight(0.20f) + .fillMaxWidth(0.45f) ) } else { title?.let { diff --git a/android/app/src/main/kotlin/nl/jknaapen/fladder/player/ExoPlayer.kt b/android/app/src/main/kotlin/nl/jknaapen/fladder/player/ExoPlayer.kt index 1cbb755..58adb09 100644 --- a/android/app/src/main/kotlin/nl/jknaapen/fladder/player/ExoPlayer.kt +++ b/android/app/src/main/kotlin/nl/jknaapen/fladder/player/ExoPlayer.kt @@ -21,7 +21,6 @@ import androidx.core.content.getSystemService import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.Player -import androidx.media3.common.TrackSelectionParameters import androidx.media3.common.Tracks import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.DataSource @@ -41,6 +40,7 @@ import androidx.media3.ui.PlayerView import io.github.peerless2012.ass.media.kt.buildWithAssSupport import io.github.peerless2012.ass.media.type.AssRenderType import nl.jknaapen.fladder.messengers.properlySetSubAndAudioTracks +import nl.jknaapen.fladder.objects.PlayerSettingsObject import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.utility.getAudioTracks import nl.jknaapen.fladder.utility.getSubtitleTracks @@ -76,41 +76,37 @@ internal fun ExoPlayer( val audioAttributes = AudioAttributes.Builder() .setUsage(C.USAGE_MEDIA) - .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) .build() val renderersFactory = DefaultRenderersFactory(context) .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) .setEnableDecoderFallback(true) + val trackSelector = DefaultTrackSelector(context).apply { + setParameters(buildUponParameters().apply { + setTunnelingEnabled(PlayerSettingsObject.settings.value?.enableTunneling ?: false) + setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true) + }) + } + val exoPlayer = remember { ExoPlayer.Builder(context, renderersFactory) - .setTrackSelector(DefaultTrackSelector(context).apply { - setParameters(buildUponParameters().apply { - setAudioOffloadPreferences( - TrackSelectionParameters.AudioOffloadPreferences.DEFAULT.buildUpon().apply { - setAudioOffloadMode(TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) - }.build() - ) - setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true) - setTunnelingEnabled(true) - }) - }) - .setMediaSourceFactory( - DefaultMediaSourceFactory( - videoCache, - extractorsFactory - ), - ) + .setTrackSelector(trackSelector) + .setMediaSourceFactory(DefaultMediaSourceFactory(videoCache, extractorsFactory)) .setAudioAttributes(audioAttributes, true) .setHandleAudioBecomingNoisy(true) .setPauseAtEndOfMediaItems(true) .setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT) - .buildWithAssSupport(context, AssRenderType.LEGACY) + .buildWithAssSupport( + context, + renderersFactory = renderersFactory, + renderType = AssRenderType.LEGACY + ) } DisposableEffect(exoPlayer) { val listener = object : Player.Listener { + override fun onPlaybackStateChanged(playbackState: Int) { videoHost.setPlaybackState( PlaybackState( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7384219..c20482a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1344,5 +1344,7 @@ "quickConnectEnterCodeDescription": "Enter the code below to login", "showMore": "Show more", "itemColorsTitle": "Item colors", - "itemColorsDesc": "Use item's primary color to theme the details page" + "itemColorsDesc": "Use item's primary color to theme the details page", + "mediaTunnelingTitle": "Media tunneling", + "mediaTunnelingDesc": "Enable media tunneling for native player" } \ No newline at end of file diff --git a/lib/models/settings/arguments_model.dart b/lib/models/settings/arguments_model.dart index 7dcaac2..897df65 100644 --- a/lib/models/settings/arguments_model.dart +++ b/lib/models/settings/arguments_model.dart @@ -2,6 +2,9 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'arguments_model.freezed.dart'; +/// Prefer using the arguments provider over this boolean +bool leanBackMode = false; + @freezed abstract class ArgumentsModel with _$ArgumentsModel { const ArgumentsModel._(); @@ -13,6 +16,7 @@ abstract class ArgumentsModel with _$ArgumentsModel { factory ArgumentsModel.fromArguments(List arguments, bool leanBackEnabled) { arguments = arguments.map((e) => e.trim()).toList(); + leanBackMode = leanBackEnabled; return ArgumentsModel( htpcMode: arguments.contains('--htpc') || leanBackEnabled, leanBackMode: leanBackEnabled, diff --git a/lib/models/settings/video_player_settings.dart b/lib/models/settings/video_player_settings.dart index 50ff903..470f257 100644 --- a/lib/models/settings/video_player_settings.dart +++ b/lib/models/settings/video_player_settings.dart @@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fladder/models/items/media_segments_model.dart'; +import 'package:fladder/models/settings/arguments_model.dart'; import 'package:fladder/models/settings/key_combinations.dart'; import 'package:fladder/util/bitrate_helper.dart'; import 'package:fladder/util/localization_helper.dart'; @@ -63,6 +64,7 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { @Default(false) bool fillScreen, @Default(true) bool hardwareAccel, @Default(false) bool useLibass, + @Default(false) bool enableTunneling, @Default(32) int bufferSize, PlayerOptions? playerOptions, @Default(100) double internalVolume, @@ -82,7 +84,8 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { factory VideoPlayerSettingsModel.fromJson(Map json) => _$VideoPlayerSettingsModelFromJson(json); - PlayerOptions get wantedPlayer => playerOptions ?? PlayerOptions.platformDefaults; + PlayerOptions get wantedPlayer => + leanBackMode ? PlayerOptions.nativePlayer : playerOptions ?? PlayerOptions.platformDefaults; Map get currentShortcuts => _defaultVideoHotKeys.map((key, value) => MapEntry(key, hotKeys[key] ?? value)); @@ -91,6 +94,7 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { bool playerSame(VideoPlayerSettingsModel other) { return other.hardwareAccel == hardwareAccel && + other.enableTunneling == enableTunneling && other.useLibass == useLibass && other.bufferSize == bufferSize && other.wantedPlayer == wantedPlayer; @@ -106,6 +110,7 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { other.fillScreen == fillScreen && other.hardwareAccel == hardwareAccel && other.useLibass == useLibass && + other.enableTunneling == enableTunneling && other.bufferSize == bufferSize && other.internalVolume == internalVolume && other.playerOptions == playerOptions && @@ -119,6 +124,7 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { fillScreen.hashCode ^ hardwareAccel.hashCode ^ useLibass.hashCode ^ + enableTunneling.hashCode ^ bufferSize.hashCode ^ internalVolume.hashCode ^ audioDevice.hashCode; @@ -132,14 +138,17 @@ enum PlayerOptions { const PlayerOptions(); - static Iterable get available => kIsWeb - ? {PlayerOptions.libMPV} - : switch (defaultTargetPlatform) { - TargetPlatform.android => PlayerOptions.values, - _ => {PlayerOptions.libMDK, PlayerOptions.libMPV}, - }; + static Iterable get available => leanBackMode + ? {PlayerOptions.nativePlayer, PlayerOptions.libMPV} + : kIsWeb + ? {PlayerOptions.libMPV} + : switch (defaultTargetPlatform) { + TargetPlatform.android => PlayerOptions.values, + _ => {PlayerOptions.libMDK, PlayerOptions.libMPV}, + }; static PlayerOptions get platformDefaults { + if (leanBackMode) return PlayerOptions.nativePlayer; if (kIsWeb) return PlayerOptions.libMPV; return switch (defaultTargetPlatform) { _ => PlayerOptions.libMPV, @@ -191,8 +200,10 @@ Map 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.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 => diff --git a/lib/models/settings/video_player_settings.freezed.dart b/lib/models/settings/video_player_settings.freezed.dart index ffb488f..8419df9 100644 --- a/lib/models/settings/video_player_settings.freezed.dart +++ b/lib/models/settings/video_player_settings.freezed.dart @@ -19,6 +19,7 @@ mixin _$VideoPlayerSettingsModel implements DiagnosticableTreeMixin { bool get fillScreen; bool get hardwareAccel; bool get useLibass; + bool get enableTunneling; int get bufferSize; PlayerOptions? get playerOptions; double get internalVolume; @@ -50,6 +51,7 @@ mixin _$VideoPlayerSettingsModel implements DiagnosticableTreeMixin { ..add(DiagnosticsProperty('fillScreen', fillScreen)) ..add(DiagnosticsProperty('hardwareAccel', hardwareAccel)) ..add(DiagnosticsProperty('useLibass', useLibass)) + ..add(DiagnosticsProperty('enableTunneling', enableTunneling)) ..add(DiagnosticsProperty('bufferSize', bufferSize)) ..add(DiagnosticsProperty('playerOptions', playerOptions)) ..add(DiagnosticsProperty('internalVolume', internalVolume)) @@ -64,7 +66,7 @@ mixin _$VideoPlayerSettingsModel implements DiagnosticableTreeMixin { @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys)'; + return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys)'; } } @@ -80,6 +82,7 @@ abstract mixin class $VideoPlayerSettingsModelCopyWith<$Res> { bool fillScreen, bool hardwareAccel, bool useLibass, + bool enableTunneling, int bufferSize, PlayerOptions? playerOptions, double internalVolume, @@ -110,6 +113,7 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res> Object? fillScreen = null, Object? hardwareAccel = null, Object? useLibass = null, + Object? enableTunneling = null, Object? bufferSize = null, Object? playerOptions = freezed, Object? internalVolume = null, @@ -142,6 +146,10 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res> ? _self.useLibass : useLibass // ignore: cast_nullable_to_non_nullable as bool, + enableTunneling: null == enableTunneling + ? _self.enableTunneling + : enableTunneling // ignore: cast_nullable_to_non_nullable + as bool, bufferSize: null == bufferSize ? _self.bufferSize : bufferSize // ignore: cast_nullable_to_non_nullable @@ -285,6 +293,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool fillScreen, bool hardwareAccel, bool useLibass, + bool enableTunneling, int bufferSize, PlayerOptions? playerOptions, double internalVolume, @@ -307,6 +316,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.fillScreen, _that.hardwareAccel, _that.useLibass, + _that.enableTunneling, _that.bufferSize, _that.playerOptions, _that.internalVolume, @@ -343,6 +353,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool fillScreen, bool hardwareAccel, bool useLibass, + bool enableTunneling, int bufferSize, PlayerOptions? playerOptions, double internalVolume, @@ -364,6 +375,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.fillScreen, _that.hardwareAccel, _that.useLibass, + _that.enableTunneling, _that.bufferSize, _that.playerOptions, _that.internalVolume, @@ -399,6 +411,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool fillScreen, bool hardwareAccel, bool useLibass, + bool enableTunneling, int bufferSize, PlayerOptions? playerOptions, double internalVolume, @@ -420,6 +433,7 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.fillScreen, _that.hardwareAccel, _that.useLibass, + _that.enableTunneling, _that.bufferSize, _that.playerOptions, _that.internalVolume, @@ -446,6 +460,7 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel this.fillScreen = false, this.hardwareAccel = true, this.useLibass = false, + this.enableTunneling = false, this.bufferSize = 32, this.playerOptions, this.internalVolume = 100, @@ -480,6 +495,9 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel final bool useLibass; @override @JsonKey() + final bool enableTunneling; + @override + @JsonKey() final int bufferSize; @override final PlayerOptions? playerOptions; @@ -552,6 +570,7 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel ..add(DiagnosticsProperty('fillScreen', fillScreen)) ..add(DiagnosticsProperty('hardwareAccel', hardwareAccel)) ..add(DiagnosticsProperty('useLibass', useLibass)) + ..add(DiagnosticsProperty('enableTunneling', enableTunneling)) ..add(DiagnosticsProperty('bufferSize', bufferSize)) ..add(DiagnosticsProperty('playerOptions', playerOptions)) ..add(DiagnosticsProperty('internalVolume', internalVolume)) @@ -566,7 +585,7 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys)'; + return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys)'; } } @@ -584,6 +603,7 @@ abstract mixin class _$VideoPlayerSettingsModelCopyWith<$Res> bool fillScreen, bool hardwareAccel, bool useLibass, + bool enableTunneling, int bufferSize, PlayerOptions? playerOptions, double internalVolume, @@ -614,6 +634,7 @@ class __$VideoPlayerSettingsModelCopyWithImpl<$Res> Object? fillScreen = null, Object? hardwareAccel = null, Object? useLibass = null, + Object? enableTunneling = null, Object? bufferSize = null, Object? playerOptions = freezed, Object? internalVolume = null, @@ -646,6 +667,10 @@ class __$VideoPlayerSettingsModelCopyWithImpl<$Res> ? _self.useLibass : useLibass // ignore: cast_nullable_to_non_nullable as bool, + enableTunneling: null == enableTunneling + ? _self.enableTunneling + : enableTunneling // ignore: cast_nullable_to_non_nullable + as bool, bufferSize: null == bufferSize ? _self.bufferSize : bufferSize // ignore: cast_nullable_to_non_nullable diff --git a/lib/models/settings/video_player_settings.g.dart b/lib/models/settings/video_player_settings.g.dart index ec6da4c..76198ae 100644 --- a/lib/models/settings/video_player_settings.g.dart +++ b/lib/models/settings/video_player_settings.g.dart @@ -15,6 +15,7 @@ _VideoPlayerSettingsModel _$VideoPlayerSettingsModelFromJson( fillScreen: json['fillScreen'] as bool? ?? false, hardwareAccel: json['hardwareAccel'] as bool? ?? true, useLibass: json['useLibass'] as bool? ?? false, + enableTunneling: json['enableTunneling'] as bool? ?? false, bufferSize: (json['bufferSize'] as num?)?.toInt() ?? 32, playerOptions: $enumDecodeNullable(_$PlayerOptionsEnumMap, json['playerOptions']), @@ -53,6 +54,7 @@ Map _$VideoPlayerSettingsModelToJson( 'fillScreen': instance.fillScreen, 'hardwareAccel': instance.hardwareAccel, 'useLibass': instance.useLibass, + 'enableTunneling': instance.enableTunneling, 'bufferSize': instance.bufferSize, 'playerOptions': _$PlayerOptionsEnumMap[instance.playerOptions], 'internalVolume': instance.internalVolume, diff --git a/lib/providers/settings/video_player_settings_provider.dart b/lib/providers/settings/video_player_settings_provider.dart index 33aa724..875e850 100644 --- a/lib/providers/settings/video_player_settings_provider.dart +++ b/lib/providers/settings/video_player_settings_provider.dart @@ -36,6 +36,7 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier MapEntry( switch (key) { @@ -82,6 +83,7 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier state = state.copyWith(hardwareAccel: value ?? true); void setUseLibass(bool? value) => state = state.copyWith(useLibass: value ?? false); + void setMediaTunneling(bool? value) => state = state.copyWith(enableTunneling: value ?? false); void setBufferSize(int? value) => state = state.copyWith(bufferSize: value ?? 32); void setFitType(BoxFit? value) => state = state.copyWith(videoFit: value ?? BoxFit.contain); diff --git a/lib/screens/details_screens/components/overview_header.dart b/lib/screens/details_screens/components/overview_header.dart index 6c9458e..ac979fe 100644 --- a/lib/screens/details_screens/components/overview_header.dart +++ b/lib/screens/details_screens/components/overview_header.dart @@ -171,7 +171,7 @@ class OverviewHeader extends ConsumerWidget { ].addInBetween(const SizedBox(height: 10)), ), ), - if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone) ...[ + if (AdaptiveLayout.viewSizeOf(context) <= ViewSize.phone) ...[ if (playButton != null) playButton!, if (centerButtons != null) centerButtons!, ] else diff --git a/lib/screens/photo_viewer/simple_video_player.dart b/lib/screens/photo_viewer/simple_video_player.dart index 210e757..d50d566 100644 --- a/lib/screens/photo_viewer/simple_video_player.dart +++ b/lib/screens/photo_viewer/simple_video_player.dart @@ -32,7 +32,7 @@ class SimpleVideoPlayer extends ConsumerStatefulWidget { } class _SimpleVideoPlayerState extends ConsumerState with WindowListener, WidgetsBindingObserver { - late final BasePlayer player = switch (ref.read(videoPlayerSettingsProvider.select((value) => value.wantedPlayer))) { + late final BasePlayer player = switch (ref.read(videoPlayerSettingsProvider).wantedPlayer) { PlayerOptions.libMDK => LibMDK(), PlayerOptions.libMPV => LibMPV(), _ => LibMDK(), diff --git a/lib/screens/settings/player_settings_page.dart b/lib/screens/settings/player_settings_page.dart index c2326b3..c9effad 100644 --- a/lib/screens/settings/player_settings_page.dart +++ b/lib/screens/settings/player_settings_page.dart @@ -209,30 +209,31 @@ class _PlayerSettingsPageState extends ConsumerState { context.localized.keyboardShortCuts, style: Theme.of(context).textTheme.titleLarge, ), - children: VideoHotKeys.values.map( - (entry) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - Expanded( - child: Text( - entry.label(context), - style: Theme.of(context).textTheme.titleMedium, - ), + children: VideoHotKeys.values + .map( + (entry) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Expanded( + child: Text( + entry.label(context), + style: Theme.of(context).textTheme.titleMedium, + ), + ), + Flexible( + child: KeyCombinationWidget( + currentKey: videoSettings.hotKeys[entry], + defaultKey: videoSettings.defaultShortCuts[entry]!, + onChanged: (value) => + ref.read(videoPlayerSettingsProvider.notifier).setShortcuts(MapEntry(entry, value)), + ), + ), + ], ), - Flexible( - child: KeyCombinationWidget( - currentKey: videoSettings.hotKeys[entry], - defaultKey: videoSettings.defaultShortCuts[entry]!, - onChanged: (value) => ref - .read(videoPlayerSettingsProvider.notifier) - .setShortcuts(MapEntry(entry, value)), - ), - ), - ], - ), - ), - ).toList(), + ), + ) + .toList(), ), ], ), @@ -266,106 +267,116 @@ class _PlayerSettingsPageState extends ConsumerState { context, SettingsLabelDivider(label: context.localized.advanced), [ - if (!ref.read(argumentsStateProvider).leanBackMode) ...[ - if (PlayerOptions.available.length != 1) - SettingsListTile( - label: Text(context.localized.playerSettingsBackendTitle), - subLabel: Text(context.localized.playerSettingsBackendDesc), - trailing: Builder(builder: (context) { - final wantedPlayer = videoSettings.wantedPlayer; - final currentPlayer = videoSettings.playerOptions; - return EnumBox( - current: currentPlayer == null - ? "${context.localized.defaultLabel} (${PlayerOptions.platformDefaults.label(context)})" - : wantedPlayer.label(context), - itemBuilder: (context) => [ - ItemActionButton( - label: Text( - "${context.localized.defaultLabel} (${PlayerOptions.platformDefaults.label(context)})"), + if (PlayerOptions.available.length != 1) + SettingsListTile( + label: Text(context.localized.playerSettingsBackendTitle), + subLabel: Text(context.localized.playerSettingsBackendDesc), + trailing: Builder(builder: (context) { + final wantedPlayer = videoSettings.wantedPlayer; + final currentPlayer = videoSettings.playerOptions; + return EnumBox( + current: currentPlayer == null + ? "${context.localized.defaultLabel} (${PlayerOptions.platformDefaults.label(context)})" + : wantedPlayer.label(context), + itemBuilder: (context) => [ + ItemActionButton( + label: Text( + "${context.localized.defaultLabel} (${PlayerOptions.platformDefaults.label(context)})"), + action: () => ref.read(videoPlayerSettingsProvider.notifier).state = + videoSettings.copyWith(playerOptions: null), + ), + ...PlayerOptions.available.map( + (entry) => ItemActionButton( + label: Text(entry.label(context)), action: () => ref.read(videoPlayerSettingsProvider.notifier).state = - videoSettings.copyWith(playerOptions: null), + videoSettings.copyWith(playerOptions: entry), ), - ...PlayerOptions.available.map( - (entry) => ItemActionButton( - label: Text(entry.label(context)), - action: () => ref.read(videoPlayerSettingsProvider.notifier).state = - videoSettings.copyWith(playerOptions: entry), - ), - ) - ], - ); - }), - ), - AnimatedFadeSize( - child: switch (videoSettings.wantedPlayer) { - PlayerOptions.libMPV => Column( - children: [ - SettingsListTile( - label: Text(context.localized.settingsPlayerVideoHWAccelTitle), - subLabel: Text(context.localized.settingsPlayerVideoHWAccelDesc), - onTap: () => provider.setHardwareAccel(!videoSettings.hardwareAccel), - trailing: Switch( - value: videoSettings.hardwareAccel, - onChanged: (value) => provider.setHardwareAccel(value), - ), - ), - if (!kIsWeb) - SettingsListTile( - label: Text(context.localized.settingsPlayerNativeLibassAccelTitle), - subLabel: Text(context.localized.settingsPlayerNativeLibassAccelDesc), - onTap: () => provider.setUseLibass(!videoSettings.useLibass), - trailing: Switch( - value: videoSettings.useLibass, - onChanged: (value) => provider.setUseLibass(value), - ), - ), - if (!videoSettings.useLibass) - SettingsListTile( - label: Text(context.localized.settingsPlayerCustomSubtitlesTitle), - subLabel: Text(context.localized.settingsPlayerCustomSubtitlesDesc), - onTap: videoSettings.useLibass - ? null - : () { - showDialog( - context: context, - barrierDismissible: true, - useSafeArea: false, - builder: (context) => const SubtitleEditor(), - ); - }, - ), - AnimatedFadeSize( - child: videoSettings.useLibass && videoSettings.hardwareAccel && Platform.isAndroid - ? SettingsMessageBox( - context.localized.settingsPlayerMobileWarning, - messageType: MessageType.warning, - ) - : Container(), - ), - SettingsListTile( - label: Text(context.localized.settingsPlayerBufferSizeTitle), - subLabel: Text(context.localized.settingsPlayerBufferSizeDesc), - trailing: SizedBox( - width: 70, - child: IntInputField( - suffix: 'MB', - controller: TextEditingController(text: videoSettings.bufferSize.toString()), - onSubmitted: (value) { - if (value != null) { - provider.setBufferSize(value); - } - }, - )), - ), - ], - ), - PlayerOptions.libMDK => SettingsMessageBox( - messageType: MessageType.info, - "${context.localized.noVideoPlayerOptions}\n${context.localized.mdkExperimental}"), - _ => const SizedBox.shrink() - }, + ) + ], + ); + }), ), - ], + AnimatedFadeSize( + child: switch (videoSettings.wantedPlayer) { + PlayerOptions.libMPV => Column( + children: [ + SettingsListTile( + label: Text(context.localized.settingsPlayerVideoHWAccelTitle), + subLabel: Text(context.localized.settingsPlayerVideoHWAccelDesc), + onTap: () => provider.setHardwareAccel(!videoSettings.hardwareAccel), + trailing: Switch( + value: videoSettings.hardwareAccel, + onChanged: (value) => provider.setHardwareAccel(value), + ), + ), + if (!kIsWeb) + SettingsListTile( + label: Text(context.localized.settingsPlayerNativeLibassAccelTitle), + subLabel: Text(context.localized.settingsPlayerNativeLibassAccelDesc), + onTap: () => provider.setUseLibass(!videoSettings.useLibass), + trailing: Switch( + value: videoSettings.useLibass, + onChanged: (value) => provider.setUseLibass(value), + ), + ), + if (!videoSettings.useLibass) + SettingsListTile( + label: Text(context.localized.settingsPlayerCustomSubtitlesTitle), + subLabel: Text(context.localized.settingsPlayerCustomSubtitlesDesc), + onTap: videoSettings.useLibass + ? null + : () { + showDialog( + context: context, + barrierDismissible: true, + useSafeArea: false, + builder: (context) => const SubtitleEditor(), + ); + }, + ), + AnimatedFadeSize( + child: videoSettings.useLibass && videoSettings.hardwareAccel && Platform.isAndroid + ? SettingsMessageBox( + context.localized.settingsPlayerMobileWarning, + messageType: MessageType.warning, + ) + : Container(), + ), + SettingsListTile( + label: Text(context.localized.settingsPlayerBufferSizeTitle), + subLabel: Text(context.localized.settingsPlayerBufferSizeDesc), + trailing: SizedBox( + width: 70, + child: IntInputField( + suffix: 'MB', + controller: TextEditingController(text: videoSettings.bufferSize.toString()), + onSubmitted: (value) { + if (value != null) { + provider.setBufferSize(value); + } + }, + )), + ), + ], + ), + PlayerOptions.nativePlayer => Column( + children: [ + SettingsListTile( + label: Text(context.localized.mediaTunnelingTitle), + subLabel: Text(context.localized.mediaTunnelingDesc), + onTap: () => provider.setMediaTunneling(!videoSettings.enableTunneling), + trailing: Switch( + value: videoSettings.enableTunneling, + onChanged: (value) => provider.setMediaTunneling(value), + ), + ), + ], + ), + PlayerOptions.libMDK => SettingsMessageBox( + messageType: MessageType.info, + "${context.localized.noVideoPlayerOptions}\n${context.localized.mdkExperimental}"), + }, + ), if (videoSettings.wantedPlayer != PlayerOptions.nativePlayer) ...[ Column( children: [ diff --git a/lib/screens/shared/media/components/media_play_button.dart b/lib/screens/shared/media/components/media_play_button.dart index 7d98674..a0c2ee3 100644 --- a/lib/screens/shared/media/components/media_play_button.dart +++ b/lib/screens/shared/media/components/media_play_button.dart @@ -62,7 +62,7 @@ class MediaPlayButton extends ConsumerWidget { child: onPressed == null ? const SizedBox.shrink(key: ValueKey('empty')) : Row( - spacing: 2, + spacing: 4, children: [ FocusButton( onTap: () => onPressed?.call(false), @@ -76,38 +76,35 @@ class MediaPlayButton extends ConsumerWidget { ); } }, - child: Padding( - padding: EdgeInsets.all(padding), - child: Stack( - alignment: Alignment.center, - children: [ - // Progress background - Positioned.fill( + child: Stack( + alignment: Alignment.center, + children: [ + // Progress background + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: radius, + ), + ), + ), + // Button content + buttonTitle(theme.colorScheme.onPrimaryContainer), + Positioned.fill( + child: ClipRect( + clipper: _ProgressClipper( + progress, + ), child: DecoratedBox( decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, + color: theme.colorScheme.primary, borderRadius: radius, ), + child: buttonTitle(theme.colorScheme.onPrimary), ), ), - // Button content - buttonTitle(theme.colorScheme.onPrimaryContainer), - Positioned.fill( - child: ClipRect( - clipper: _ProgressClipper( - progress, - ), - child: DecoratedBox( - decoration: BoxDecoration( - color: theme.colorScheme.primary, - borderRadius: radius, - ), - child: buttonTitle(theme.colorScheme.onPrimary), - ), - ), - ), - ], - ), + ), + ], ), ), if (progress != 0) diff --git a/lib/src/player_settings_helper.g.dart b/lib/src/player_settings_helper.g.dart index 3bbbfcf..42e1d23 100644 --- a/lib/src/player_settings_helper.g.dart +++ b/lib/src/player_settings_helper.g.dart @@ -45,11 +45,14 @@ enum SegmentSkip { class PlayerSettings { PlayerSettings({ + required this.enableTunneling, required this.skipTypes, required this.skipForward, required this.skipBackward, }); + bool enableTunneling; + Map skipTypes; int skipForward; @@ -58,6 +61,7 @@ class PlayerSettings { List _toList() { return [ + enableTunneling, skipTypes, skipForward, skipBackward, @@ -70,9 +74,10 @@ class PlayerSettings { static PlayerSettings decode(Object result) { result as List; return PlayerSettings( - skipTypes: (result[0] as Map?)!.cast(), - skipForward: result[1]! as int, - skipBackward: result[2]! as int, + enableTunneling: result[0]! as bool, + skipTypes: (result[1] as Map?)!.cast(), + skipForward: result[2]! as int, + skipBackward: result[3]! as int, ); } diff --git a/lib/wrappers/media_control_wrapper.dart b/lib/wrappers/media_control_wrapper.dart index 603e11e..0afa2a8 100644 --- a/lib/wrappers/media_control_wrapper.dart +++ b/lib/wrappers/media_control_wrapper.dart @@ -14,7 +14,6 @@ import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/media_playback_model.dart'; import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/models/settings/video_player_settings.dart'; -import 'package:fladder/providers/arguments_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/video_player_settings_provider.dart'; import 'package:fladder/providers/video_player_provider.dart'; @@ -73,13 +72,11 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro ); } - final player = ref.read(argumentsStateProvider).leanBackMode - ? NativePlayer() - : switch (ref.read(videoPlayerSettingsProvider.select((value) => value.wantedPlayer))) { - PlayerOptions.libMDK => LibMDK(), - PlayerOptions.libMPV => LibMPV(), - PlayerOptions.nativePlayer => NativePlayer(), - }; + final player = switch (ref.read(videoPlayerSettingsProvider).wantedPlayer) { + PlayerOptions.libMDK => LibMDK(), + PlayerOptions.libMPV => LibMPV(), + PlayerOptions.nativePlayer => NativePlayer(), + }; setup(player); } diff --git a/pigeons/player_settings_pigeon.dart b/pigeons/player_settings_pigeon.dart index 13a3d04..6b78f5b 100644 --- a/pigeons/player_settings_pigeon.dart +++ b/pigeons/player_settings_pigeon.dart @@ -12,11 +12,13 @@ import 'package:pigeon/pigeon.dart'; ), ) class PlayerSettings { + final bool enableTunneling; final Map skipTypes; - final int skipForward; + final int skipForward; final int skipBackward; const PlayerSettings({ + required this.enableTunneling, required this.skipTypes, required this.skipForward, required this.skipBackward,