fix: respect series-level subtitle mode for episodes
This commit is contained in:
@@ -89,6 +89,7 @@ class PlexMetadata with MultiServerFields {
|
||||
final List<PlexRole>? role; // Cast members
|
||||
final String? audioLanguage; // Per-media preferred audio language
|
||||
final String? subtitleLanguage; // Per-media preferred subtitle language
|
||||
final int? subtitleMode; // Per-media subtitle mode (0=manual, 1=foreign audio, 2=always, -1=account default)
|
||||
final int? playlistItemID; // Playlist item ID (for dumb playlists only)
|
||||
final int? playQueueItemID; // Play queue item ID (unique even for duplicates)
|
||||
final int? librarySectionID; // Library section ID this item belongs to
|
||||
@@ -175,6 +176,7 @@ class PlexMetadata with MultiServerFields {
|
||||
this.role,
|
||||
this.audioLanguage,
|
||||
this.subtitleLanguage,
|
||||
this.subtitleMode,
|
||||
this.playlistItemID,
|
||||
this.playQueueItemID,
|
||||
this.librarySectionID,
|
||||
@@ -232,6 +234,7 @@ class PlexMetadata with MultiServerFields {
|
||||
List<PlexRole>? role,
|
||||
String? audioLanguage,
|
||||
String? subtitleLanguage,
|
||||
int? subtitleMode,
|
||||
int? playlistItemID,
|
||||
int? playQueueItemID,
|
||||
int? librarySectionID,
|
||||
@@ -287,6 +290,7 @@ class PlexMetadata with MultiServerFields {
|
||||
role: role ?? this.role,
|
||||
audioLanguage: audioLanguage ?? this.audioLanguage,
|
||||
subtitleLanguage: subtitleLanguage ?? this.subtitleLanguage,
|
||||
subtitleMode: subtitleMode ?? this.subtitleMode,
|
||||
playlistItemID: playlistItemID ?? this.playlistItemID,
|
||||
playQueueItemID: playQueueItemID ?? this.playQueueItemID,
|
||||
librarySectionID: librarySectionID ?? this.librarySectionID,
|
||||
|
||||
@@ -45,6 +45,7 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
|
||||
role: (json['Role'] as List<dynamic>?)?.map((e) => PlexRole.fromJson(e as Map<String, dynamic>)).toList(),
|
||||
audioLanguage: json['audioLanguage'] as String?,
|
||||
subtitleLanguage: json['subtitleLanguage'] as String?,
|
||||
subtitleMode: (json['subtitleMode'] as num?)?.toInt(),
|
||||
playlistItemID: (json['playlistItemID'] as num?)?.toInt(),
|
||||
playQueueItemID: (json['playQueueItemID'] as num?)?.toInt(),
|
||||
librarySectionID: (json['librarySectionID'] as num?)?.toInt(),
|
||||
@@ -99,6 +100,7 @@ Map<String, dynamic> _$PlexMetadataToJson(PlexMetadata instance) => <String, dyn
|
||||
'Role': instance.role,
|
||||
'audioLanguage': instance.audioLanguage,
|
||||
'subtitleLanguage': instance.subtitleLanguage,
|
||||
'subtitleMode': instance.subtitleMode,
|
||||
'playlistItemID': instance.playlistItemID,
|
||||
'playQueueItemID': instance.playQueueItemID,
|
||||
'librarySectionID': instance.librarySectionID,
|
||||
|
||||
@@ -290,6 +290,8 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
|
||||
return meta.audioLanguage ?? '';
|
||||
case 'subtitleLanguage':
|
||||
return meta.subtitleLanguage ?? '';
|
||||
case 'subtitleMode':
|
||||
return meta.subtitleMode?.toString() ?? '-1';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -2229,10 +2229,42 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (!mounted || player == null) return;
|
||||
|
||||
// For episodes without per-media prefs, fetch show/season metadata
|
||||
// to get series-level language/subtitle mode preferences
|
||||
PlexMetadata effectiveMetadata = widget.metadata;
|
||||
if (!widget.isOffline &&
|
||||
widget.metadata.isEpisode &&
|
||||
widget.metadata.subtitleMode == null &&
|
||||
widget.metadata.audioLanguage == null) {
|
||||
try {
|
||||
final client = _getClientForMetadata(context);
|
||||
final showMeta = widget.metadata.grandparentRatingKey != null
|
||||
? await client.getMetadataWithImages(widget.metadata.grandparentRatingKey!)
|
||||
: null;
|
||||
final seasonMeta = widget.metadata.parentRatingKey != null
|
||||
? await client.getMetadataWithImages(widget.metadata.parentRatingKey!)
|
||||
: null;
|
||||
// Season overrides show (Plex preference hierarchy)
|
||||
final inheritedAudio = seasonMeta?.audioLanguage ?? showMeta?.audioLanguage;
|
||||
final inheritedSubLang = seasonMeta?.subtitleLanguage ?? showMeta?.subtitleLanguage;
|
||||
final inheritedSubMode = seasonMeta?.subtitleMode ?? showMeta?.subtitleMode;
|
||||
if (inheritedAudio != null || inheritedSubLang != null || inheritedSubMode != null) {
|
||||
effectiveMetadata = widget.metadata.copyWith(
|
||||
audioLanguage: inheritedAudio,
|
||||
subtitleLanguage: inheritedSubLang,
|
||||
subtitleMode: inheritedSubMode,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch show/season prefs for track selection', error: e);
|
||||
}
|
||||
if (!mounted || player == null) return;
|
||||
}
|
||||
|
||||
final trackService = TrackSelectionService(
|
||||
player: player!,
|
||||
profileSettings: profileSettings,
|
||||
metadata: widget.metadata,
|
||||
metadata: effectiveMetadata,
|
||||
plexMediaInfo: _currentMediaInfo,
|
||||
);
|
||||
|
||||
|
||||
@@ -710,11 +710,37 @@ class TrackSelectionService {
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 3: Try per-media language preference
|
||||
if (metadata.subtitleLanguage != null) {
|
||||
if (metadata.subtitleLanguage == 'none' || metadata.subtitleLanguage!.isEmpty) {
|
||||
// Priority 3: Try per-media language/subtitle mode preference
|
||||
final hasSubtitleMode = metadata.subtitleMode != null && metadata.subtitleMode != -1;
|
||||
if (metadata.subtitleLanguage != null || hasSubtitleMode) {
|
||||
if (metadata.subtitleLanguage == 'none' || (metadata.subtitleLanguage?.isEmpty ?? false)) {
|
||||
return TrackSelectionResult(SubtitleTrack.off, TrackSelectionPriority.perMedia);
|
||||
} else if (availableTracks.isNotEmpty) {
|
||||
}
|
||||
|
||||
// Determine effective subtitle mode: per-media overrides account setting
|
||||
final effectiveMode = hasSubtitleMode
|
||||
? metadata.subtitleMode!
|
||||
: profileSettings?.autoSelectSubtitle;
|
||||
|
||||
// Mode 0: Manually selected / off
|
||||
if (effectiveMode == 0) {
|
||||
return TrackSelectionResult(SubtitleTrack.off, TrackSelectionPriority.perMedia);
|
||||
}
|
||||
|
||||
// Mode 1: Shown with foreign audio only
|
||||
if (effectiveMode == 1 && selectedAudioTrack != null && metadata.subtitleLanguage != null) {
|
||||
final audioLang = selectedAudioTrack.language?.toLowerCase();
|
||||
final prefLang = metadata.subtitleLanguage!.toLowerCase();
|
||||
final languageVariations = LanguageCodes.getVariations(prefLang);
|
||||
|
||||
// If audio matches preferred subtitle language, no subtitles needed
|
||||
if (audioLang != null && languageVariations.contains(audioLang)) {
|
||||
return TrackSelectionResult(SubtitleTrack.off, TrackSelectionPriority.perMedia);
|
||||
}
|
||||
}
|
||||
|
||||
// Mode 2 / null / continuing from mode 1 with foreign audio: find matching subtitle
|
||||
if (metadata.subtitleLanguage != null && availableTracks.isNotEmpty) {
|
||||
final matchedTrack = availableTracks.firstWhere(
|
||||
(track) => languageMatches(track.language, metadata.subtitleLanguage),
|
||||
orElse: () => availableTracks.first,
|
||||
|
||||
Reference in New Issue
Block a user