Beim Umpacken fällt die Spurwahl an — also wird sie dort getroffen

Dein Befund an „Notruf Hafenkante": keine Spuren, nichts auswählbar. Die
technischen Angaben derselben Folge sagen, warum:

    Container    MKV
    Tonspur      AAC - Stereo - Standard
    Tonspur      Deutsch - German - AAC - Stereo - Standard
    Untertitel   German - Standard - SUBRIP

Die Quelle ist Matroska. AVFoundation öffnet das nicht, also lief der
Download bisher über die zweite Engine — und die meldete die Spurliste leer.
Offline wählen ging damit nie.

**Umpacken allein hätte es nicht gelöst.** Jellyfin bildet in die neue Hülle
genau **eine** Tonspur ab und übernimmt den Untertitel nur, wenn man ihn
nennt. Ohne Angabe nimmt der Server seine eigene Vorgabe — und das ist bei
deutschen Aufnahmen regelmäßig die Spur mit Audiodeskription.

Also fällt die Wahl jetzt beim Herunterladen, mit derselben Abwägung wie beim
Abspielen (`AudioTrackChooser`, der für genau diesen Fall gebaut wurde: die
als deutsch ausgezeichnete Spur ist die mit Audiodeskription). Der Untertitel
geht als `subtitleMethod=Embed` mit — als **Spur**, nicht ins Bild gebrannt,
sonst liesse er sich nicht abschalten.

Die Mediathek bleibt bei der Abwägung aussen vor: sie nachzuschlagen kostet
eine Anfrage je Folge, und Serie und allgemeine Einstellung tragen den Wunsch
ohnehin.

Der Offline-Abzug behauptet nicht mehr, alle Sprachen seien dabei — beim
Umpacken bleibt je eine übrig, und alle zu notieren wäre ein Versprechen, das
die Datei nicht hält.

**Nebenbei richtiggestellt:** die technischen Angaben rechneten mit
`.directPlayOnly` und zeigten deshalb „Direkt (Systemplayer)" direkt über
„iOS öffnet den Container MKV nicht" — zwei Angaben, die sich widersprechen.
Das war früher richtig (ohne Serverhilfe übernahm die zweite Engine und hiess
auch so); jetzt wird mit Serverhilfe gerechnet.

NOCH NICHT AN EINEM ECHTEN SERVER GEPRÜFT.

iOS und tvOS bauen, 494 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-03 08:32:41 +02:00
co-authored by Claude Opus 5
parent 6b927c21b0
commit a432592271
6 changed files with 111 additions and 20 deletions
+33 -4
View File
@@ -82,6 +82,16 @@ enum DownloadQuality: String, CaseIterable, Identifiable {
struct DownloadService {
let backend: (any MediaBackend)?
let coordinator: DownloadCoordinator
/// Welche Sprache gewünscht ist — nur fürs Umpacken.
///
/// **Weil beim Umpacken eine Tonspur übrig bleibt.** Online wählt man im
/// Player; offline gibt es nach dem Herunterladen nichts mehr zu wählen,
/// also fällt die Entscheidung hier. Dieselbe Abwägung wie beim Abspielen
/// (`AudioTrackChooser`) — eine zweite Regel liefe irgendwann auseinander.
///
/// Optional, damit Aufrufer ohne Spracheinstellungen weiter bauen: dann
/// gilt, was der Server als Standard meldet.
var languages: LanguagePreferences?
/// Baut den Auftrag für einen einzelnen Titel.
///
@@ -116,8 +126,24 @@ struct DownloadService {
let url: URL?
switch quality {
case .original where umpacken:
url = backend.remuxDownloadURL(itemID: item.id, versionIndex: mediaIndex)
?? backend.downloadURL(for: part)
let ton = languages.flatMap {
// Die Mediathek bleibt aussen vor: sie nachzuschlagen kostet
// eine Anfrage je Folge, und die Ebene darüber — Serie und
// allgemein — trägt den Wunsch ohnehin.
AudioTrackChooser.choose(
from: part.streams,
wish: $0.wish(seriesID: item.seriesID, libraryID: nil)
)
} ?? part.selectedAudioStream
let untertitel = part.subtitleStreams.first { $0.isSelected }
?? part.subtitleStreams.first
url = backend.remuxDownloadURL(
itemID: item.id,
versionIndex: mediaIndex,
audioStreamIndex: ton?.index,
subtitleStreamIndex: untertitel?.index
) ?? backend.downloadURL(for: part)
case .original:
url = backend.downloadURL(for: part)
case .fullHD, .hd, .low:
@@ -281,8 +307,11 @@ struct DownloadService {
audioCodec: remuxed ? nil : (part.selectedAudioStream?.codec ?? media.audioCodec),
videoResolution: media.videoResolution,
bitrate: media.bitrate,
audioLanguages: part.audioStreams.compactMap(\.language),
subtitleLanguages: part.subtitleStreams.compactMap(\.language),
// **Beim Umpacken bleibt je eine übrig.** Alle zu notieren wäre
// ein Versprechen, das die Datei nicht hält — und offline ist es
// nicht nachprüfbar, bevor der Player sie öffnet.
audioLanguages: remuxed ? [] : part.audioStreams.compactMap(\.language),
subtitleLanguages: remuxed ? [] : part.subtitleStreams.compactMap(\.language),
markers: item.markers.map {
OfflineMarker(
type: $0.kind.rawValue,