fix(jellyfin): avoid side-loading negotiated subtitles
This commit is contained in:
@@ -22,6 +22,8 @@ typedef JellyfinStreamFields = ({
|
||||
String? displayTitle,
|
||||
bool isDefault,
|
||||
bool isForced,
|
||||
bool isExternalFile,
|
||||
bool usesExternalDelivery,
|
||||
bool isExternal,
|
||||
String? deliveryUrl,
|
||||
int? channels,
|
||||
@@ -30,7 +32,8 @@ typedef JellyfinStreamFields = ({
|
||||
|
||||
JellyfinStreamFields parseJellyfinStreamFields(Map<String, dynamic> s, {int fallbackIndex = 0}) {
|
||||
final deliveryMethod = (s['DeliveryMethod'] as String?)?.toLowerCase();
|
||||
final isExternal = deliveryMethod != null ? deliveryMethod == 'external' : s['IsExternal'] == true;
|
||||
final isExternalFile = s['IsExternal'] == true;
|
||||
final usesExternalDelivery = deliveryMethod == 'external';
|
||||
return (
|
||||
type: (s['Type'] as String?)?.toLowerCase(),
|
||||
index: flexibleInt(s['Index']) ?? fallbackIndex,
|
||||
@@ -41,7 +44,9 @@ JellyfinStreamFields parseJellyfinStreamFields(Map<String, dynamic> s, {int fall
|
||||
displayTitle: s['DisplayTitle'] as String?,
|
||||
isDefault: s['IsDefault'] as bool? ?? false,
|
||||
isForced: s['IsForced'] as bool? ?? false,
|
||||
isExternal: isExternal,
|
||||
isExternalFile: isExternalFile,
|
||||
usesExternalDelivery: usesExternalDelivery,
|
||||
isExternal: isExternalFile || usesExternalDelivery,
|
||||
deliveryUrl: s['DeliveryUrl'] as String?,
|
||||
channels: flexibleInt(s['Channels']),
|
||||
frameRate: flexibleDouble(s['RealFrameRate']) ?? flexibleDouble(s['AverageFrameRate']),
|
||||
@@ -231,7 +236,8 @@ class JellyfinFileInfoStreamReader implements FileInfoStreamReader {
|
||||
selected: f.isDefault,
|
||||
forced: f.isForced,
|
||||
key: f.isExternal ? f.deliveryUrl : null,
|
||||
external: f.isExternal,
|
||||
external: f.isExternalFile,
|
||||
usesExternalDelivery: f.usesExternalDelivery,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ mixin _JellyfinImageDownloadMethods on MediaServerCacheMixin {
|
||||
if (raw is! Map<String, dynamic>) continue;
|
||||
if (raw['Type'] != 'Subtitle') continue;
|
||||
final fields = parseJellyfinStreamFields(raw);
|
||||
if (!fields.isExternal) continue;
|
||||
if (!fields.isExternalFile) continue;
|
||||
final index = raw['Index'];
|
||||
if (index is! int) continue;
|
||||
final codec = fields.codec?.toLowerCase();
|
||||
|
||||
@@ -150,7 +150,7 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
);
|
||||
var effectiveSourceId = bundle.selectedSourceId;
|
||||
var effectiveContainer = bundle.container;
|
||||
var externalSubtitles = _buildExternalSubtitles(metadata.id, effectiveSourceId, mediaInfo);
|
||||
var includeExternalSubtitleDelivery = false;
|
||||
|
||||
String? videoUrl;
|
||||
String? playSessionId;
|
||||
@@ -187,7 +187,6 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
chapters: bundle.chapters,
|
||||
trickplay: bundle.trickplay,
|
||||
);
|
||||
externalSubtitles = _buildExternalSubtitles(metadata.id, effectiveSourceId, mediaInfo);
|
||||
}
|
||||
|
||||
final negotiatedPlaySessionId = negotiation['PlaySessionId'];
|
||||
@@ -208,6 +207,7 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
videoUrl = _withApiKey(transcodingUrl);
|
||||
playMethod = 'Transcode';
|
||||
isTranscoding = true;
|
||||
includeExternalSubtitleDelivery = true;
|
||||
} else if (directStreamUrl is String && directStreamUrl.isNotEmpty) {
|
||||
capturePlaySessionId(directStreamUrl);
|
||||
videoUrl = _withApiKey(directStreamUrl);
|
||||
@@ -224,6 +224,12 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
|
||||
final effectiveAudioStreamId = _resolveJellyfinAudioStreamId(requestedAudioStreamId, mediaInfo);
|
||||
mediaInfo = _withSelectedJellyfinAudioStream(mediaInfo, effectiveAudioStreamId);
|
||||
final externalSubtitles = _buildExternalSubtitles(
|
||||
metadata.id,
|
||||
effectiveSourceId,
|
||||
mediaInfo,
|
||||
includeExternalDelivery: includeExternalSubtitleDelivery,
|
||||
);
|
||||
final pinnedSourceId = bundle.pinnedSourceIdForItem(metadata.id);
|
||||
videoUrl ??= buildDirectStreamUrl(metadata.id, container: effectiveContainer, mediaSourceId: pinnedSourceId);
|
||||
|
||||
@@ -316,10 +322,15 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
return path.startsWith('/') ? path : '/$path';
|
||||
}
|
||||
|
||||
List<SubtitleTrack> _buildExternalSubtitles(String itemId, String? mediaSourceId, MediaSourceInfo mediaInfo) {
|
||||
List<SubtitleTrack> _buildExternalSubtitles(
|
||||
String itemId,
|
||||
String? mediaSourceId,
|
||||
MediaSourceInfo mediaInfo, {
|
||||
bool includeExternalDelivery = false,
|
||||
}) {
|
||||
final externalSubtitles = <SubtitleTrack>[];
|
||||
for (final track in mediaInfo.subtitleTracks) {
|
||||
if (!track.isExternal) continue;
|
||||
if (!track.isExternalFile && !(includeExternalDelivery && track.usesExternalDelivery)) continue;
|
||||
final path = track.key ?? _jellyfinSubtitleFallbackPath(itemId, mediaSourceId, track);
|
||||
if (path == null) continue;
|
||||
// Jellyfin's subtitle URL is a path relative to baseUrl; build the
|
||||
|
||||
@@ -424,7 +424,7 @@ class JellyfinMappers {
|
||||
channels: f.channels,
|
||||
frameRate: f.frameRate,
|
||||
forced: f.isForced,
|
||||
sidecarPath: f.isExternal ? f.deliveryUrl : null,
|
||||
sidecarPath: f.isExternalFile ? f.deliveryUrl : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ List<MediaSubtitleTrack> _withDefaultSubtitleSelection(List<MediaSubtitleTrack>
|
||||
forced: track.forced,
|
||||
key: track.key,
|
||||
external: track.external,
|
||||
usesExternalDelivery: track.usesExternalDelivery,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user