fix(player): stop writing show-wide track defaults
This commit is contained in:
@@ -168,37 +168,24 @@ _PlaybackOpenTiming _playbackOpenTiming({
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a [TrackPreferencePersister] that fans the language-preference +
|
||||
/// stream-selection writes out to a [PlexClient] resolved lazily on each
|
||||
/// call. Returns a no-op-on-null persister so the [TrackManager] doesn't
|
||||
/// have to import [PlexClient] itself; the resolver returning null (e.g.
|
||||
/// when the active server is Jellyfin) makes the call short-circuit.
|
||||
/// Builds a [TrackPreferencePersister] that writes the per-episode stream
|
||||
/// selection out to a [PlexClient] resolved lazily on each call. Returns a
|
||||
/// no-op-on-null persister so the [TrackManager] doesn't have to import
|
||||
/// [PlexClient] itself; the resolver returning null (e.g. when the active
|
||||
/// server is Jellyfin) makes the call short-circuit.
|
||||
///
|
||||
/// Only the current episode's part is touched — we deliberately do NOT write
|
||||
/// the show-wide audio/subtitle language default (#1393): an in-player track
|
||||
/// change should not silently rewrite the whole series' Plex prefs. The
|
||||
/// explicit path for that lives in the metadata-edit UI.
|
||||
TrackPreferencePersister _plexTrackPersister(PlexClient? Function() resolve) {
|
||||
return ({
|
||||
required String id,
|
||||
required int partId,
|
||||
required String trackType,
|
||||
String? languageCode,
|
||||
int? streamID,
|
||||
}) async {
|
||||
return ({required int partId, required String trackType, int? streamID}) async {
|
||||
if (streamID == null) return;
|
||||
final client = resolve();
|
||||
if (client == null) return;
|
||||
final futures = <Future>[];
|
||||
if (languageCode != null && (trackType == 'subtitle' || languageCode.isNotEmpty)) {
|
||||
futures.add(
|
||||
trackType == 'audio'
|
||||
? client.setMetadataPreferences(id, audioLanguage: languageCode)
|
||||
: client.setMetadataPreferences(id, subtitleLanguage: languageCode),
|
||||
);
|
||||
}
|
||||
if (streamID != null) {
|
||||
futures.add(
|
||||
trackType == 'audio'
|
||||
? client.selectStreams(partId, audioStreamID: streamID, allParts: true)
|
||||
: client.selectStreams(partId, subtitleStreamID: streamID, allParts: true),
|
||||
);
|
||||
}
|
||||
await Future.wait(futures);
|
||||
await (trackType == 'audio'
|
||||
? client.selectStreams(partId, audioStreamID: streamID, allParts: true)
|
||||
: client.selectStreams(partId, subtitleStreamID: streamID, allParts: true));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1198,29 +1198,6 @@ class PlexClient
|
||||
);
|
||||
}
|
||||
|
||||
/// Set per-media language preferences (audio and subtitle)
|
||||
/// For TV shows, use grandparentRatingKey to set preference for the entire series
|
||||
/// For movies, use the movie's ratingKey
|
||||
Future<bool> setMetadataPreferences(String ratingKey, {String? audioLanguage, String? subtitleLanguage}) async {
|
||||
final queryParams = <String, dynamic>{};
|
||||
if (audioLanguage != null) {
|
||||
queryParams['audioLanguage'] = audioLanguage;
|
||||
}
|
||||
if (subtitleLanguage != null) {
|
||||
queryParams['subtitleLanguage'] = subtitleLanguage;
|
||||
}
|
||||
|
||||
// If no preferences to set, return early
|
||||
if (queryParams.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _wrapBoolApiCall(
|
||||
() => _http.put('/library/metadata/$ratingKey/prefs', queryParameters: queryParams),
|
||||
'Failed to set metadata preferences',
|
||||
);
|
||||
}
|
||||
|
||||
/// Select specific audio and subtitle streams for playback
|
||||
/// This updates which streams are "selected" in the media metadata
|
||||
/// Uses the part ID from media info for accurate stream selection
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:async';
|
||||
import '../mpv/mpv.dart';
|
||||
|
||||
import '../media/media_item.dart';
|
||||
import '../media/media_item_types.dart';
|
||||
import '../media/media_server_user_profile.dart';
|
||||
import '../media/media_source_info.dart';
|
||||
import '../services/settings_service.dart';
|
||||
@@ -12,16 +11,14 @@ import '../utils/app_logger.dart';
|
||||
import '../utils/language_codes.dart';
|
||||
import '../utils/track_label_builder.dart';
|
||||
|
||||
/// Persists a track choice through Plex's immediate preference endpoints.
|
||||
/// Persists a track choice for the current part to the server.
|
||||
/// Backends that persist through another path (Jellyfin uses playback progress
|
||||
/// stream indexes) or lack server-side track preferences leave this null.
|
||||
/// stream indexes) or lack server-side stream selection leave this null.
|
||||
/// [trackType] is `'audio'` or `'subtitle'`.
|
||||
typedef TrackPreferencePersister =
|
||||
Future<void> Function({
|
||||
required String id,
|
||||
required int partId,
|
||||
required String trackType,
|
||||
String? languageCode,
|
||||
int? streamID,
|
||||
});
|
||||
|
||||
@@ -342,7 +339,7 @@ class TrackManager {
|
||||
}
|
||||
}
|
||||
|
||||
await _saveTrackPreferences(partId: partId, trackType: 'audio', languageCode: track.language, streamID: streamID);
|
||||
await _saveTrackPreferences(partId: partId, trackType: 'audio', streamID: streamID);
|
||||
}
|
||||
|
||||
/// Handle subtitle track changes — save stream selection and language preference.
|
||||
@@ -351,16 +348,12 @@ class TrackManager {
|
||||
final partId = await _guardTrackChange(info);
|
||||
if (partId == null) return;
|
||||
|
||||
String? languageCode;
|
||||
int? streamID;
|
||||
|
||||
if (track.id == 'no') {
|
||||
languageCode = 'none';
|
||||
streamID = 0;
|
||||
appLogger.i('User turned subtitles off, saving preference');
|
||||
} else if (info != null) {
|
||||
languageCode = track.language;
|
||||
|
||||
streamID = _matchTrackByAttributes(
|
||||
mpvLanguage: track.language,
|
||||
mpvTitle: track.title,
|
||||
@@ -388,7 +381,7 @@ class TrackManager {
|
||||
}
|
||||
}
|
||||
|
||||
await _saveTrackPreferences(partId: partId, trackType: 'subtitle', languageCode: languageCode, streamID: streamID);
|
||||
await _saveTrackPreferences(partId: partId, trackType: 'subtitle', streamID: streamID);
|
||||
}
|
||||
|
||||
/// Handle secondary subtitle track changes — no server save needed.
|
||||
@@ -399,11 +392,6 @@ class TrackManager {
|
||||
|
||||
// ── Private helpers ────────────────────────────────────────────────
|
||||
|
||||
/// Series/movie-level identifier used for language preferences.
|
||||
String get _preferenceId {
|
||||
return metadata.isEpisode ? (metadata.grandparentId ?? metadata.id) : metadata.id;
|
||||
}
|
||||
|
||||
/// Common guard checks for track change handlers.
|
||||
Future<int?> _guardTrackChange(MediaSourceInfo? info) async {
|
||||
final settings = await SettingsService.getInstance();
|
||||
@@ -423,29 +411,18 @@ class TrackManager {
|
||||
return partId;
|
||||
}
|
||||
|
||||
/// Save language preference and stream selection to the server.
|
||||
Future<void> _saveTrackPreferences({
|
||||
required int partId,
|
||||
required String trackType,
|
||||
String? languageCode,
|
||||
int? streamID,
|
||||
}) async {
|
||||
/// Save the stream selection for the current part to the server.
|
||||
Future<void> _saveTrackPreferences({required int partId, required String trackType, int? streamID}) async {
|
||||
try {
|
||||
if (!isActive()) return;
|
||||
final persist = persistTrackPreference;
|
||||
if (persist == null) {
|
||||
return;
|
||||
}
|
||||
await persist(
|
||||
id: _preferenceId,
|
||||
partId: partId,
|
||||
trackType: trackType,
|
||||
languageCode: languageCode,
|
||||
streamID: streamID,
|
||||
);
|
||||
appLogger.d('Successfully saved $trackType preferences (language + stream)');
|
||||
await persist(partId: partId, trackType: trackType, streamID: streamID);
|
||||
appLogger.d('Successfully saved $trackType stream selection');
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to save $trackType preferences', error: e);
|
||||
appLogger.e('Failed to save $trackType stream selection', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,13 +144,7 @@ Future<void> _drainAsync() async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _noopPersister({
|
||||
required String id,
|
||||
required int partId,
|
||||
required String trackType,
|
||||
String? languageCode,
|
||||
int? streamID,
|
||||
}) async {}
|
||||
Future<void> _noopPersister({required int partId, required String trackType, int? streamID}) async {}
|
||||
|
||||
void main() {
|
||||
// The constructor doesn't touch prefs, but [dispose] / [applyTrackSelection]
|
||||
|
||||
Reference in New Issue
Block a user