fix(plex): skip malformed subtitle streams

close #1589
This commit is contained in:
edde746
2026-07-20 10:28:31 +02:00
parent e32fcc2190
commit 0283c3bfff
2 changed files with 66 additions and 2 deletions
+6 -2
View File
@@ -10,6 +10,10 @@ import 'plex_mappers.dart';
const _streamReader = PlexFileInfoStreamReader();
void _logMalformedStream(Object error, StackTrace stackTrace, Map<String, dynamic> _) {
appLogger.w('Skipping malformed Plex stream metadata', error: error, stackTrace: stackTrace);
}
List<Map> _mapList(Object? raw) {
final values = flexibleList(raw);
if (values == null || values.isEmpty) return const [];
@@ -122,7 +126,7 @@ PlexVideoPlaybackData parsePlexVideoPlaybackDataFromJson(
if (partKey != null) {
videoUrl = '$baseUrl$partKey'.withPlexToken(token);
final streams = walkStreams(flexibleList(part['Stream']), _streamReader);
final streams = walkStreams(flexibleList(part['Stream']), _streamReader, onMalformed: _logMalformedStream);
final chapters = plexChaptersFromCacheJson(metadataJson);
mediaInfo = MediaSourceInfo(
@@ -161,7 +165,7 @@ MediaFileInfo? parsePlexFileInfoFromJson(Map<String, dynamic>? metadataJson) {
// One pass over the streams array, capturing both the raw video / audio
// map pointers (for fields the parsed track classes don't carry —
// colorSpace, bitDepth, …) and the parsed track lists.
final parsedTracks = walkStreams(flexibleList(part?['Stream']), _streamReader);
final parsedTracks = walkStreams(flexibleList(part?['Stream']), _streamReader, onMalformed: _logMalformedStream);
final videoStream = parsedTracks.videoStream;
final audioStream = parsedTracks.audioStream;
@@ -333,6 +333,41 @@ void main() {
expect(criteria.primaries, 'bt2020');
expect(criteria.matrix, 'bt2020nc');
});
test('skips unidentifiable subtitle streams without blocking playback', () {
final result = parsePlexVideoPlaybackDataFromJson(
{
'Media': [
{
'id': 1,
'Part': [
{
'id': 10,
'key': '/library/parts/10/file.mp4',
'accessible': 1,
'exists': 1,
'Stream': [
{'streamType': 1, 'id': 100},
{'streamType': 2, 'id': 301, 'selected': true},
{'streamType': 3, 'languageCode': 'eng'},
{'streamType': 3, 'id': 'cc1', 'languageCode': 'eng'},
{'streamType': 3, 'id': '401', 'languageCode': 'spa', 'selected': true},
],
},
],
},
],
},
baseUrl: 'http://plex:32400',
token: 'token',
);
expect(result.videoUrl, 'http://plex:32400/library/parts/10/file.mp4?X-Plex-Token=token');
expect(result.mediaInfo, isNotNull);
expect(result.mediaInfo!.audioTracks.map((track) => track.id), [301]);
expect(result.mediaInfo!.subtitleTracks.map((track) => track.id), [401]);
expect(result.mediaInfo!.subtitleTracks.single.selected, isTrue);
});
});
group('parsePlexFileInfoFromJson', () {
@@ -395,5 +430,30 @@ void main() {
expect(info?.audioTracks.single.selected, isTrue);
expect(info?.subtitleTracks.single.key, '/subtitles/401');
});
test('skips unidentifiable subtitle streams in file info', () {
final info = parsePlexFileInfoFromJson({
'Media': [
{
'container': 'mp4',
'Part': [
{
'file': '/media/movie.mp4',
'Stream': [
{'streamType': 1, 'id': 100},
{'streamType': 3, 'id': null, 'languageCode': 'eng'},
{'streamType': 3, 'id': 'cea-608', 'languageCode': 'eng'},
{'streamType': 3, 'id': 402, 'languageCode': 'spa'},
],
},
],
},
],
});
expect(info, isNotNull);
expect(info!.filePath, '/media/movie.mp4');
expect(info.subtitleTracks.map((track) => track.id), [402]);
});
});
}