Files
Flaro/Packages/FlixyKit/Sources/FlixyPlayback/Decision/PlaybackDecision.swift
T
ScarriffleandClaude Opus 5 9e7f3393b4 DTS, TrueHD und Vorbis gehen jetzt an den Server statt an VLC
Bisher entschied der Decider: steht nur die Schachtel im Weg, wird
umgepackt — stimmt ein Codec nicht, geht es an VLC. Das war beim Ton zu
grob gedacht.

Beim Umpacken nennt das Geräteprofil dem Server, was ankommen darf:

    "VideoCodec": "h264,hevc"
    "AudioCodec": "aac,ac3,eac3,mp3"

Trifft ein Strom eine dieser Angaben, kopiert der Server ihn; trifft er
sie nicht, rechnet er **genau diesen einen** um. Eine Matroska mit HEVC
und DTS braucht also kein VLC: das Bild geht Byte für Byte durch, und nur
die Tonspur wird nach AC3 oder AAC kodiert. Das kostet Tonqualität und
sonst nichts.

Die Bedingung heisst deshalb nicht mehr „nur die Schachtel", sondern
„das Bild bleibt": Container und Ton dürfen im Weg stehen, der
Videocodec nicht. Dazu musste die Prüfung der Schachtel aufhören, sich
immer zu stellen — eine MP4 mit DTS stand mit einem reinen Tonhindernis
da und fiel trotzdem durch die Liste umpackbarer Container.

**Der grössere Gewinn liegt auf dem Apple TV.** Dort gibt es VLC nicht,
und bisher landete eine Datei mit DTS-Ton beim vollen Neurechnen — Bild
inbegriffen, obwohl mit dem Bild nichts war. Jetzt bleibt das Bild
unberührt. Auf iPhone, iPad und Mac verhält sich derselbe Film jetzt
gleich, und zwar über AVPlayer: mit Bild-in-Bild, AirPlay und
Sperrbildschirm, die es auf dem VLC-Weg nie gab.

Geprüft im Simulator gegen die Attrappe mit `--vlc` (meldet DTS): der
Film läuft, und in der Leiste steht jetzt der AirPlay-Knopf, der vorher
fehlte — der sichtbarste Beleg dafür, dass AVPlayer spielt und nicht VLC.

„Umgepackt" und „umgepackt, Ton umgerechnet" sind in den technischen
Infos und in der Diagnose auseinandergehalten. Ein Weg, der den Ton neu
kodiert, darf nicht „ohne Neukodierung" heissen.

Was bleibt bei VLC: nicht unterstützte **Bild**codecs, allen voran AV1,
und Dateien, die der Server noch nicht untersucht hat. Dafür gäbe es nur
das volle Neurechnen — eine eigene Entscheidung, nicht diese.

iOS und tvOS bauen, 463 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-01 07:46:39 +02:00

89 lines
3.8 KiB
Swift

import Foundation
/// Welcher Engine die Wiedergabe übernimmt.
public enum PlaybackEngineKind: String, Sendable, Equatable, Codable {
/// Der Systemplayer. Bringt Bild-in-Bild, AirPlay und die Steuerung im
/// Sperrbildschirm mit — deshalb hat er immer Vorrang, wenn er kann.
case avPlayer
/// VLCKit. Spielt praktisch jedes Format, kann dafür kein Bild-in-Bild.
case vlc
}
/// Wie ein Titel abgespielt werden soll.
public struct PlaybackDecision: Sendable, Equatable {
public enum Method: Sendable, Equatable {
/// Die Datei wird unverändert übertragen und lokal dekodiert.
case directPlay(engine: PlaybackEngineKind)
/// Der Server reicht das Bild durch und legt es in eine andere
/// Schachtel — den Ton rechnet er um, falls nötig.
///
/// **Der dritte Weg, und der wichtigste für diesen Bestand.** Ein
/// Blu-ray-Rip liegt als Matroska vor, und AVFoundation öffnet
/// Matroska nicht — obwohl das Bild darin H.264 oder HEVC ist und der
/// Ton AAC oder AC3, also alles, was AVPlayer bestens kann. Im Weg
/// steht allein die Verpackung.
///
/// **Das Bild wird dabei nie angefasst.** Es geht Byte für Byte in
/// eine MP4, und das kostet den Server fast nichts — anders als beim
/// Neurechnen.
///
/// **Der Ton kann es müssen.** Steht DTS, TrueHD oder Vorbis darin,
/// kennt AVPlayer die Spur nicht; der Server kodiert dann genau diese
/// eine Spur nach AC3 oder AAC. Das kostet Tonqualität — und sonst
/// nichts, vor allem keine einzige Bildzeile. Ob es im Einzelfall
/// passiert ist, steht in `rationale`: `audioCodecUnsupported`.
///
/// Der Gewinn ist gross: solche Titel landen nicht mehr bei VLC,
/// sondern bei AVPlayer. Damit gibt es dort Bild-in-Bild, AirPlay und
/// die Systembedienung — und auf einem Apple TV, wo es VLC gar nicht
/// gibt, muss nicht mehr das ganze Bild neu gerechnet werden.
case remux
/// Der Server rechnet um. Ab Phase 2; `maximumBitrate` in Kilobit.
case transcode(maximumBitrate: Int?)
}
/// Warum es so entschieden wurde.
///
/// Wandert in die technischen Infos auf der Detailseite und ins Protokoll.
/// Ohne diese Begründung ist „warum ruckelt das" nicht zu beantworten.
public enum Rationale: Sendable, Equatable {
case nativelySupported
case containerUnsupported(String)
case videoCodecUnsupported(String)
case audioCodecUnsupported(String)
/// Der Server hat die Datei noch nicht analysiert.
case streamsUnknown
case bitrateExceedsLimit(mediaBitrate: Int, limit: Int)
/// Transcoding wäre passender, ist aber (noch) nicht verfügbar.
case transcodingUnavailable
/// Im Player ausdrücklich eine Stufe gewählt.
case qualityChosen
}
public let method: Method
public let rationale: [Rationale]
public init(method: Method, rationale: [Rationale]) {
self.method = method
self.rationale = rationale
}
public var engine: PlaybackEngineKind? {
switch method {
case .directPlay(let engine): engine
// Der Transcoder liefert HLS, und damit ist AVPlayer der richtige
// Engine. **Nicht** aber, wie hier früher stand, „inklusive
// automatischem Bitratenwechsel": den gibt es nur, wenn die Playlist
// mehrere Stufen führt, und das muss ausdrücklich bestellt werden.
case .transcode: .avPlayer
// Umgepackt kommt eine MP4 heraus — genau das, wofür es AVPlayer gibt.
case .remux: .avPlayer
}
}
public var isDirectPlay: Bool {
if case .directPlay = method { return true }
return false
}
}