feat: Videoplayer remember subtitle and audio selection(#339)

This commit is contained in:
Julien9969 2025-05-22 13:24:42 -04:00 committed by GitHub
parent 93a38a0b6b
commit b1491b0ada
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 247 additions and 39 deletions

View file

@ -34,6 +34,7 @@ class AccountModel with _$AccountModel {
@Default([]) List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false) UserConfiguration? userConfiguration,
}) = _AccountModel;
factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);

View file

@ -37,6 +37,9 @@ mixin _$AccountModel {
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration =>
throw _privateConstructorUsedError;
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? get userConfiguration =>
throw _privateConstructorUsedError;
/// Serializes this AccountModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -68,7 +71,9 @@ abstract class $AccountModelCopyWith<$Res> {
List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration});
ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? userConfiguration});
}
/// @nodoc
@ -99,6 +104,7 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
Object? savedFilters = null,
Object? policy = freezed,
Object? serverConfiguration = freezed,
Object? userConfiguration = freezed,
}) {
return _then(_value.copyWith(
name: null == name
@ -153,6 +159,10 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?,
userConfiguration: freezed == userConfiguration
? _value.userConfiguration
: userConfiguration // ignore: cast_nullable_to_non_nullable
as UserConfiguration?,
) as $Val);
}
}
@ -179,7 +189,9 @@ abstract class _$$AccountModelImplCopyWith<$Res>
List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration});
ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? userConfiguration});
}
/// @nodoc
@ -208,6 +220,7 @@ class __$$AccountModelImplCopyWithImpl<$Res>
Object? savedFilters = null,
Object? policy = freezed,
Object? serverConfiguration = freezed,
Object? userConfiguration = freezed,
}) {
return _then(_$AccountModelImpl(
name: null == name
@ -262,6 +275,10 @@ class __$$AccountModelImplCopyWithImpl<$Res>
? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?,
userConfiguration: freezed == userConfiguration
? _value.userConfiguration
: userConfiguration // ignore: cast_nullable_to_non_nullable
as UserConfiguration?,
));
}
}
@ -283,7 +300,9 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
final List<LibraryFiltersModel> savedFilters = const [],
@JsonKey(includeFromJson: false, includeToJson: false) this.policy,
@JsonKey(includeFromJson: false, includeToJson: false)
this.serverConfiguration})
this.serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
this.userConfiguration})
: _latestItemsExcludes = latestItemsExcludes,
_searchQueryHistory = searchQueryHistory,
_savedFilters = savedFilters,
@ -346,10 +365,13 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
@override
@JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
final UserConfiguration? userConfiguration;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration)';
return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration)';
}
@override
@ -369,7 +391,8 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
..add(DiagnosticsProperty('quickConnectState', quickConnectState))
..add(DiagnosticsProperty('savedFilters', savedFilters))
..add(DiagnosticsProperty('policy', policy))
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration));
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration))
..add(DiagnosticsProperty('userConfiguration', userConfiguration));
}
@override
@ -398,7 +421,9 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
.equals(other._savedFilters, _savedFilters) &&
(identical(other.policy, policy) || other.policy == policy) &&
(identical(other.serverConfiguration, serverConfiguration) ||
other.serverConfiguration == serverConfiguration));
other.serverConfiguration == serverConfiguration) &&
(identical(other.userConfiguration, userConfiguration) ||
other.userConfiguration == userConfiguration));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -417,7 +442,8 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
quickConnectState,
const DeepCollectionEquality().hash(_savedFilters),
policy,
serverConfiguration);
serverConfiguration,
userConfiguration);
/// Create a copy of AccountModel
/// with the given fields replaced by the non-null parameter values.
@ -451,7 +477,9 @@ abstract class _AccountModel extends AccountModel {
@JsonKey(includeFromJson: false, includeToJson: false)
final UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration}) = _$AccountModelImpl;
final ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false)
final UserConfiguration? userConfiguration}) = _$AccountModelImpl;
const _AccountModel._() : super._();
factory _AccountModel.fromJson(Map<String, dynamic> json) =
@ -485,6 +513,9 @@ abstract class _AccountModel extends AccountModel {
@override
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
UserConfiguration? get userConfiguration;
/// Create a copy of AccountModel
/// with the given fields replaced by the non-null parameter values.

View file

@ -175,6 +175,20 @@ class StreamModel {
});
}
class AudioAndSubStreamModel extends StreamModel {
final String language;
final String displayTitle;
AudioAndSubStreamModel({
required this.displayTitle,
required super.name,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
required this.language,
});
}
class VersionStreamModel {
final String name;
final int index;
@ -250,19 +264,17 @@ extension SortByExternalExtension<T extends StreamModel> on Iterable<T> {
}
}
class AudioStreamModel extends StreamModel {
final String displayTitle;
final String language;
class AudioStreamModel extends AudioAndSubStreamModel {
final String channelLayout;
AudioStreamModel({
required this.displayTitle,
required super.displayTitle,
required super.name,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
required this.language,
required super.language,
required this.channelLayout,
});
@ -292,8 +304,8 @@ class AudioStreamModel extends StreamModel {
AudioStreamModel.no({
super.name = 'Off',
this.displayTitle = 'Off',
this.language = '',
super.displayTitle = 'Off',
super.language = '',
super.codec = '',
this.channelLayout = '',
super.isDefault = false,
@ -302,19 +314,17 @@ class AudioStreamModel extends StreamModel {
});
}
class SubStreamModel extends StreamModel {
class SubStreamModel extends AudioAndSubStreamModel {
String id;
String title;
String displayTitle;
String language;
String? url;
bool supportsExternalStream;
SubStreamModel({
required super.name,
required this.id,
required this.title,
required this.displayTitle,
required this.language,
required super.displayTitle,
required super.language,
this.url,
required super.codec,
required super.isDefault,
@ -327,8 +337,8 @@ class SubStreamModel extends StreamModel {
super.name = 'Off',
this.id = 'Off',
this.title = 'Off',
this.displayTitle = 'Off',
this.language = '',
super.displayTitle = 'Off',
super.language = '',
this.url = '',
super.codec = '',
super.isDefault = false,

View file

@ -31,6 +31,7 @@ import 'package:fladder/util/bitrate_helper.dart';
import 'package:fladder/util/duration_extensions.dart';
import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/util/streams_selection.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart';
class Media {
@ -196,13 +197,25 @@ class PlaybackModelHelper {
);
final streamModel = firstItemToPlay.streamModel;
final audioStreamIndex = selectAudioStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
oldModel?.mediaStreams?.currentAudioStream,
streamModel?.audioStreams,
streamModel?.defaultAudioStreamIndex
);
final subStreamIndex = selectSubStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
oldModel?.mediaStreams?.currentSubStream,
streamModel?.subStreams,
streamModel?.defaultSubStreamIndex
);
final Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: firstItemToPlay.id,
body: PlaybackInfoDto(
startTimeTicks: startPosition?.toRuntimeTicks,
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
audioStreamIndex: audioStreamIndex,
subtitleStreamIndex: subStreamIndex,
enableTranscoding: true,
autoOpenLiveStream: true,
deviceProfile: ref.read(videoProfileProvider),
@ -223,8 +236,8 @@ class PlaybackModelHelper {
if (mediaSource == null) return null;
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(playbackInfo.mediaSources, ref).copyWith(
defaultAudioStreamIndex: streamModel?.defaultAudioStreamIndex,
defaultSubStreamIndex: streamModel?.defaultSubStreamIndex,
defaultAudioStreamIndex: audioStreamIndex,
defaultSubStreamIndex: subStreamIndex,
);
final mediaSegments = await api.mediaSegmentsGet(id: item.id);
@ -328,8 +341,18 @@ class PlaybackModelHelper {
final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position));
final audioIndex = playbackModel.mediaStreams?.defaultAudioStreamIndex;
final subIndex = playbackModel.mediaStreams?.defaultSubStreamIndex;
final audioIndex = selectAudioStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
playbackModel.mediaStreams?.currentAudioStream,
playbackModel.audioStreams,
playbackModel.mediaStreams?.defaultAudioStreamIndex
);
final subIndex = selectSubStream(
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
playbackModel.mediaStreams?.currentSubStream,
playbackModel.subStreams,
playbackModel.mediaStreams?.defaultSubStreamIndex
);
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: item.id,