mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
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:
parent
3ce0ed6dbc
commit
1942738fe4
16 changed files with 256 additions and 199 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue