Vier Punkte aus dem Praxistest
Download-Knopf tat nichts DownloadService.plan gab still nil zurück, wenn es keinen Auftrag bauen konnte, und download() reichte das als nil weiter. Der Knopf sah aus, als wäre er kaputt, dabei war der Grund jedes Mal bekannt. Es gibt jetzt einen eigenen Ablehnungsgrund mit vier unterschiedlichen Begründungen — keine Verbindung, keine Datei, kein abrufbarer Teil, keine Adresse. Eine Funktion, die kommentarlos nichts tut, ist selbst der Fehler. Live-TV galt als „nicht eingerichtet" Plex legt die Empfängerliste unter dem Schlüssel `Dvr` ab, die Hülle kannte aber nur Metadata, Directory und Hub. Sie las eine leere Liste und zog daraus die falsche Schlussfolgerung — auf einem Server, auf dem ein Empfänger eingerichtet war. Dasselbe galt für die Anbieterliste unter `MediaProvider`, ohne die es keinen Programmführer gibt. Beim Umbau wäre beinahe die Fehlerdiagnose verloren gegangen: mit `try?` über alle Schlüssel verschwände auch ein echter Dekodierfehler und ergäbe stillschweigend eine leere Liste. Aus „ein Feld heißt jetzt anders" würde „die Mediathek ist leer". Jetzt wird erst geprüft, ob der Schlüssel da ist, und dann ohne try? dekodiert. Fünf Tests halten beides fest. Qualität im Player wählbar Automatisch, Original, 1080p, 720p, 480p. Der Eingriff wird genau dann gebraucht, wenn es ruckelt — dafür will niemand den Film verlassen. Ein Wechsel baut den Strom neu auf (die Stufe steckt in der Adresse), merkt sich die Stelle und fährt sie wieder an. Der alte Transcoder wird vorher angehalten, sonst rechnet der Server zwei Ströme für einen Zuschauer. AVPlayer-Anzeige entfernt Sie war eine Prüfhilfe aus Phase 1. Dieselbe Angabe steht weiterhin unter Einstellungen → Diagnose, wo sie hingehört. 137 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -179,35 +179,55 @@ public struct PlexMediaContainer<Content: Decodable & Sendable>: Sendable, Decod
|
||||
public let title2: String?
|
||||
public let content: [Content]
|
||||
|
||||
private enum OuterKeys: String, CodingKey { case mediaContainer = "MediaContainer" }
|
||||
private enum InnerKeys: String, CodingKey {
|
||||
case size, totalSize, offset, title1, title2
|
||||
case metadata = "Metadata"
|
||||
case directory = "Directory"
|
||||
case hub = "Hub"
|
||||
/// Unter welchen Namen Plex seine Sammlungen ablegt.
|
||||
///
|
||||
/// Es gibt keinen einheitlichen: Titel stehen unter `Metadata`,
|
||||
/// Mediatheken unter `Directory`, die Reihen der Startseite unter `Hub`,
|
||||
/// Fernsehempfänger unter `Dvr`, die Anbieterliste unter `MediaProvider`.
|
||||
///
|
||||
/// Genau daran war Live-TV gescheitert: die Hülle kannte `Dvr` nicht, las
|
||||
/// eine leere Liste und meldete „kein Empfänger eingerichtet" — bei einem
|
||||
/// Server, auf dem einer eingerichtet war.
|
||||
///
|
||||
/// Die Namen schließen sich gegenseitig aus, deshalb genügt es, sie der
|
||||
/// Reihe nach zu probieren.
|
||||
private static var contentKeys: [String] {
|
||||
["Metadata", "Directory", "Hub", "Dvr", "MediaProvider", "Channel", "ChannelMapping", "Device"]
|
||||
}
|
||||
|
||||
private struct DynamicKey: CodingKey {
|
||||
let stringValue: String
|
||||
var intValue: Int? { nil }
|
||||
init(_ value: String) { stringValue = value }
|
||||
init?(stringValue: String) { self.stringValue = stringValue }
|
||||
init?(intValue: Int) { nil }
|
||||
}
|
||||
|
||||
private enum OuterKeys: String, CodingKey { case mediaContainer = "MediaContainer" }
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let outer = try decoder.container(keyedBy: OuterKeys.self)
|
||||
let inner = try outer.nestedContainer(keyedBy: InnerKeys.self, forKey: .mediaContainer)
|
||||
let inner = try outer.nestedContainer(keyedBy: DynamicKey.self, forKey: .mediaContainer)
|
||||
|
||||
self.size = try inner.decodeIfPresent(Int.self, forKey: .size)
|
||||
self.totalSize = try inner.decodeIfPresent(Int.self, forKey: .totalSize)
|
||||
self.offset = try inner.decodeIfPresent(Int.self, forKey: .offset)
|
||||
self.title1 = try inner.decodeIfPresent(String.self, forKey: .title1)
|
||||
self.title2 = try inner.decodeIfPresent(String.self, forKey: .title2)
|
||||
self.size = try inner.decodeIfPresent(Int.self, forKey: DynamicKey("size"))
|
||||
self.totalSize = try inner.decodeIfPresent(Int.self, forKey: DynamicKey("totalSize"))
|
||||
self.offset = try inner.decodeIfPresent(Int.self, forKey: DynamicKey("offset"))
|
||||
self.title1 = try inner.decodeIfPresent(String.self, forKey: DynamicKey("title1"))
|
||||
self.title2 = try inner.decodeIfPresent(String.self, forKey: DynamicKey("title2"))
|
||||
|
||||
// `Metadata` bei Titeln, `Directory` bei Mediatheken, `Hub` bei den
|
||||
// Reihen der Startseite. Eine leere Mediathek liefert gar kein Feld —
|
||||
// das ist kein Fehler, sondern bedeutet schlicht null Einträge.
|
||||
if let metadata = try inner.decodeIfPresent([Content].self, forKey: .metadata) {
|
||||
self.content = metadata
|
||||
} else if let directory = try inner.decodeIfPresent([Content].self, forKey: .directory) {
|
||||
self.content = directory
|
||||
} else if let hub = try inner.decodeIfPresent([Content].self, forKey: .hub) {
|
||||
self.content = hub
|
||||
} else {
|
||||
self.content = []
|
||||
// Erst nachsehen, ob der Schlüssel überhaupt da ist, und dann ohne
|
||||
// `try?` dekodieren.
|
||||
//
|
||||
// Mit `try?` verschwände auch ein echter Dekodierfehler und ergäbe
|
||||
// stillschweigend eine leere Liste — aus „ein Feld heißt jetzt anders"
|
||||
// würde „die Mediathek ist leer". Genau diese Verwechslung hat bei
|
||||
// Live-TV schon einmal auf die falsche Fährte geführt.
|
||||
for name in Self.contentKeys where inner.contains(DynamicKey(name)) {
|
||||
self.content = try inner.decode([Content].self, forKey: DynamicKey(name))
|
||||
return
|
||||
}
|
||||
// Kein Schlüssel vorhanden: eine leere Mediathek liefert gar kein Feld.
|
||||
// Das ist kein Fehler, sondern bedeutet null Einträge.
|
||||
self.content = []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user