fix: Disable media tunneling by default with the option to enable it (#515)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-10-04 13:29:55 +02:00 committed by GitHub
parent 3ce0ed6dbc
commit 1942738fe4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 256 additions and 199 deletions

View file

@ -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<SegmentType, SegmentSkip>,
val skipForward: Long,
val skipBackward: Long
@ -100,14 +101,16 @@ data class PlayerSettings (
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): PlayerSettings {
val skipTypes = pigeonVar_list[0] as Map<SegmentType, SegmentSkip>
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<SegmentType, SegmentSkip>
val skipForward = pigeonVar_list[2] as Long
val skipBackward = pigeonVar_list[3] as Long
return PlayerSettings(enableTunneling, skipTypes, skipForward, skipBackward)
}
}
fun toList(): List<Any?> {
return listOf(
enableTunneling,
skipTypes,
skipForward,
skipBackward,

View file

@ -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 {

View file

@ -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(

View file

@ -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"
}

View file

@ -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<String> arguments, bool leanBackEnabled) {
arguments = arguments.map((e) => e.trim()).toList();
leanBackMode = leanBackEnabled;
return ArgumentsModel(
htpcMode: arguments.contains('--htpc') || leanBackEnabled,
leanBackMode: leanBackEnabled,

View file

@ -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<String, dynamic> json) => _$VideoPlayerSettingsModelFromJson(json);
PlayerOptions get wantedPlayer => playerOptions ?? PlayerOptions.platformDefaults;
PlayerOptions get wantedPlayer =>
leanBackMode ? PlayerOptions.nativePlayer : playerOptions ?? PlayerOptions.platformDefaults;
Map<VideoHotKeys, KeyCombination> 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<PlayerOptions> get available => kIsWeb
? {PlayerOptions.libMPV}
: switch (defaultTargetPlatform) {
TargetPlatform.android => PlayerOptions.values,
_ => {PlayerOptions.libMDK, PlayerOptions.libMPV},
};
static Iterable<PlayerOptions> 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<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.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 =>

View file

@ -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

View file

@ -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<String, dynamic> _$VideoPlayerSettingsModelToJson(
'fillScreen': instance.fillScreen,
'hardwareAccel': instance.hardwareAccel,
'useLibass': instance.useLibass,
'enableTunneling': instance.enableTunneling,
'bufferSize': instance.bufferSize,
'playerOptions': _$PlayerOptionsEnumMap[instance.playerOptions],
'internalVolume': instance.internalVolume,

View file

@ -36,6 +36,7 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
final userData = ref.read(userProvider);
pigeon.PlayerSettingsPigeon().sendPlayerSettings(
pigeon.PlayerSettings(
enableTunneling: value.enableTunneling,
skipTypes: value.segmentSkipSettings.map(
(key, value) => MapEntry(
switch (key) {
@ -82,6 +83,7 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
void setHardwareAccel(bool? value) => 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);

View file

@ -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

View file

@ -32,7 +32,7 @@ class SimpleVideoPlayer extends ConsumerStatefulWidget {
}
class _SimpleVideoPlayerState extends ConsumerState<SimpleVideoPlayer> 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(),

View file

@ -209,30 +209,31 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
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<PlayerSettingsPage> {
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: [

View file

@ -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)

View file

@ -45,11 +45,14 @@ enum SegmentSkip {
class PlayerSettings {
PlayerSettings({
required this.enableTunneling,
required this.skipTypes,
required this.skipForward,
required this.skipBackward,
});
bool enableTunneling;
Map<SegmentType, SegmentSkip> skipTypes;
int skipForward;
@ -58,6 +61,7 @@ class PlayerSettings {
List<Object?> _toList() {
return <Object?>[
enableTunneling,
skipTypes,
skipForward,
skipBackward,
@ -70,9 +74,10 @@ class PlayerSettings {
static PlayerSettings decode(Object result) {
result as List<Object?>;
return PlayerSettings(
skipTypes: (result[0] as Map<Object?, Object?>?)!.cast<SegmentType, SegmentSkip>(),
skipForward: result[1]! as int,
skipBackward: result[2]! as int,
enableTunneling: result[0]! as bool,
skipTypes: (result[1] as Map<Object?, Object?>?)!.cast<SegmentType, SegmentSkip>(),
skipForward: result[2]! as int,
skipBackward: result[3]! as int,
);
}

View file

@ -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);
}

View file

@ -12,11 +12,13 @@ import 'package:pigeon/pigeon.dart';
),
)
class PlayerSettings {
final bool enableTunneling;
final Map<SegmentType, SegmentSkip> skipTypes;
final int skipForward;
final int skipForward;
final int skipBackward;
const PlayerSettings({
required this.enableTunneling,
required this.skipTypes,
required this.skipForward,
required this.skipBackward,