**Vorbereitung dafür, dass VLC verschwindet.** Online ist der Fall längst
gelöst: steht nur die Schachtel im Weg, packt der Server um — dieselben
Ströme, eine MP4-Hülle. Offline gibt es diesen Ausweg nicht. Was einmal als
Matroska auf dem Gerät liegt, kann hinterher niemand mehr umpacken, und ohne
zweite Engine bliebe die Datei schwarz.
Also entscheidet sich das beim Herunterladen. Fragt jemand das Original an und
AVFoundation könnte die Datei nicht öffnen, wird `remuxDownloadURL` benutzt:
`/Videos/{id}/stream.mp4` mit `allowVideoStreamCopy` und
`allowAudioStreamCopy`. Der Server reicht durch, was passt, und rechnet nur
um, was nicht passt — das Bild bleibt das Original, nur DTS oder TrueHD
werden zu AC3 oder AAC.
Drei Dinge hängen daran und sind mitgezogen:
- Die **Größe** ist dann keine Zusage mehr: dieselben Ströme in einer anderen
Hülle wiegen anders. `expectedIsExact` fällt auf falsch, und es gilt wieder
die weiche Prüfung.
- Der **Container** im Offline-Abzug ist „mp4", nicht die alte Schachtel.
Sonst entschiede der Player anhand einer Datei, die es so nicht mehr gibt.
- Der **Tonkodierer** bleibt leer. Ob aus DTS dann AAC oder AC3 wird, sagt der
Server vorher nicht; eine Behauptung wäre schlechter als die Lücke, und die
Spuren liest der Player ohnehin aus der Datei.
**Die Frage „kann das der Systemplayer?" steht jetzt an einer Stelle.**
`DeviceCapabilities.opens(container:videoCodec:audioCodec:)` — dieselbe Liste,
nach der auch beim Abspielen entschieden wird. Was der Server nicht nennt,
gilt als passend: vorsichtshalber umzupacken hiesse, aus einer tadellosen MP4
eine neu gerechnete Datei zu machen. Fünf Tests.
NOCH NICHT AN EINEM ECHTEN SERVER GEPRÜFT: ob Jellyfin das Kopieren hier
tatsächlich wählt, verrät erst die Dateigröße des ersten Downloads.
140 Tests in FlixyPlayback, iOS und tvOS bauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
3.0 KiB
Swift
64 lines
3.0 KiB
Swift
import Foundation
|
|
|
|
/// Was AVPlayer auf diesem Gerät nativ abspielen kann.
|
|
///
|
|
/// Diese Liste ist der Grund, warum Flixy überhaupt zwei Engines hat. iOS
|
|
/// spielt weder Matroska-Dateien noch DTS- oder TrueHD-Ton — und genau das
|
|
/// liegt in typischen Plex-Beständen. Alles, was hier nicht steht, geht an
|
|
/// VLCKit (oder ab Phase 2 an den Transcoder).
|
|
public struct DeviceCapabilities: Sendable, Equatable {
|
|
public var containers: Set<String>
|
|
public var videoCodecs: Set<String>
|
|
public var audioCodecs: Set<String>
|
|
|
|
public init(containers: Set<String>, videoCodecs: Set<String>, audioCodecs: Set<String>) {
|
|
self.containers = containers
|
|
self.videoCodecs = videoCodecs
|
|
self.audioCodecs = audioCodecs
|
|
}
|
|
|
|
/// Der Stand für iOS 26 auf allen Geräten, die diese Version tragen
|
|
/// (A12 aufwärts).
|
|
public static let avFoundation = DeviceCapabilities(
|
|
// Kein `mkv`, kein `avi`, kein `ts`. Das ist keine Auslassung, sondern
|
|
// der Kern des Problems: Matroska ist der übliche Container für
|
|
// Remuxe, und AVFoundation öffnet ihn nicht.
|
|
containers: ["mp4", "m4v", "mov", "qt"],
|
|
// AV1 fehlt bewusst: Hardware-Dekodierung gibt es erst ab A17 Pro, und
|
|
// Software-Dekodierung von 4K-AV1 bringt auch neue Geräte an die
|
|
// Grenze. VLC macht das kontrollierter.
|
|
videoCodecs: ["h264", "avc1", "hevc", "h265", "hvc1", "dvh1", "dvhe"],
|
|
// DTS, TrueHD und Vorbis fehlen — die drei Gründe, warum ein sonst
|
|
// tadelloser MP4-Film trotzdem stumm bliebe.
|
|
audioCodecs: ["aac", "mp3", "ac3", "eac3", "alac", "pcm", "lpcm"]
|
|
)
|
|
|
|
/// Schachteln, die sich verlustfrei in eine MP4 umpacken lassen.
|
|
///
|
|
/// Nicht jede: ein Container kann Dinge enthalten, für die MP4 keinen
|
|
/// Platz hat. Diese drei sind die, die in echten Beständen vorkommen und
|
|
/// deren Ströme regelmässig MP4-tauglich sind.
|
|
public static let remuxableContainers: Set<String> = ["mkv", "matroska", "ts", "m2ts", "avi"]
|
|
|
|
/// Ob diese Datei so, wie sie ist, geöffnet werden kann.
|
|
///
|
|
/// **Hier und nicht beim Aufrufer**, weil es die Frage inzwischen zweimal
|
|
/// gibt: beim Abspielen entscheidet sie `PlaybackDecider`, beim
|
|
/// Herunterladen muss sie vorher beantwortet sein — was offline liegt,
|
|
/// kann hinterher niemand mehr umpacken.
|
|
///
|
|
/// **Was fehlt, zählt als passend.** Nennt der Server einen Codec nicht,
|
|
/// ist das keine Aussage über seine Untauglichkeit. Vorsichtshalber
|
|
/// umzupacken hiesse, aus einer tadellosen MP4 eine neu gerechnete Datei
|
|
/// zu machen.
|
|
public func opens(container: String?, videoCodec: String?, audioCodec: String?) -> Bool {
|
|
func passt(_ wert: String?, in liste: Set<String>) -> Bool {
|
|
guard let wert, !wert.isEmpty else { return true }
|
|
return liste.contains(wert.lowercased())
|
|
}
|
|
return passt(container, in: containers)
|
|
&& passt(videoCodec, in: videoCodecs)
|
|
&& passt(audioCodec, in: audioCodecs)
|
|
}
|
|
}
|