Live-TV: Zuordnung und Senderliste sind zweierlei
Der Strukturabzug vom Server hat es entschieden. ChannelMapping enthält genau vier Felder — channelKey, deviceIdentifier, enabled, lineupIdentifier — und keinen einzigen Namen. Es ist keine Senderliste, sondern ein Verweis darauf: welche 28 von 521 Sendern der Swisscom-Liste eingeschaltet sind. Zweimal habe ich die Zuordnung für die Senderliste gehalten. Beim ersten Mal blieb sie leer, beim zweiten hätten alle 28 Sender „—" geheißen und dieselbe Kennung getragen — SwiftUI hätte bei doppelten Kennungen ohnehin nur einen gezeigt. Jetzt werden beide Quellen zusammengeführt: die Namen, Nummern und Logos kommen von der Senderliste beim Programmanbieter, die Auswahl aus der Zuordnung. Die Kennung des Anbieters steht als `epgIdentifier` direkt am Empfänger — verlässlicher, als sie aus der Anbieterliste zu suchen. Der Abgleich ist nachsichtig: je nach Anbieter entspricht der channelKey der Kennung, dem Pfad oder der Sendernummer. Ein strenger Vergleich fände in zwei von drei Fällen nichts, und „keiner passte zusammen" sieht aus wie „keine Sender". Genau das unterscheidet die Meldung jetzt auch. Der Strukturabzug in der Diagnose deckt jetzt beide Endpunkte ab — nach einem Fehlschlag bleibt keine weitere Runde Raten übrig. 166 Tests, neun davon zu Zuordnung und Abgleich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,67 +48,64 @@ struct ClearLogoTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("Empfänger und Sender")
|
||||
@Suite("Empfänger, Zuordnung und Senderliste")
|
||||
struct TunerDecodingTests {
|
||||
|
||||
@Test("Sender werden aus dem Empfänger gelesen")
|
||||
func readsChannelsFromDevice() throws {
|
||||
// Der Fall, an dem „Keine Sender" hing: Plex liefert sie verschachtelt
|
||||
// in der Antwort auf /livetv/dvrs mit, nicht über einen eigenen
|
||||
// Endpunkt.
|
||||
let json = """
|
||||
{"MediaContainer":{"Dvr":[{
|
||||
"key": "1", "lineupTitle": "Kabel",
|
||||
"Device": [{
|
||||
"key": "2", "uuid": "tuner-1", "title": "HDHomeRun",
|
||||
"Channel": [
|
||||
{"id": 10, "channelIdentifier": "1.1", "title": "SRF 1 HD", "key": "/livetv/channel/1"},
|
||||
{"id": 11, "channelIdentifier": "1.2", "title": "SRF zwei"}
|
||||
]
|
||||
}]
|
||||
}]}}
|
||||
"""
|
||||
/// Die Antwort, wie sie ein echter Server liefert.
|
||||
///
|
||||
/// Nachgebildet aus dem Strukturabzug eines Servers mit Swisscom-Liste:
|
||||
/// 28 zugeordnete Sender von 521 in der Liste. Entscheidend daran ist,
|
||||
/// was NICHT drinsteht — die Zuordnung führt keine Namen.
|
||||
private let dvrJSON = """
|
||||
{"MediaContainer":{"size":1,"Dvr":[{
|
||||
"key": "1",
|
||||
"uuid": "dvr-uuid",
|
||||
"epgIdentifier": "tv.plex.providers.epg.cloud:2",
|
||||
"lineupTitle": "Swisscom Blue TV (Deutschschweiz)",
|
||||
"lineup": "lineup://tv.plex.providers.epg.cloud/swisscom",
|
||||
"Lineup": [{"id": "swisscom-de", "title": "Swisscom Blue TV (Deutschschweiz)"}],
|
||||
"Device": [{
|
||||
"key": "2", "uuid": "tuner-1", "title": "Swisscom Blue TV",
|
||||
"ChannelMapping": [
|
||||
{"channelKey": "srf1", "lineupIdentifier": "swisscom-de", "enabled": "1"},
|
||||
{"channelKey": "srf2", "lineupIdentifier": "swisscom-de", "enabled": "1"},
|
||||
{"channelKey": "abgeschaltet", "lineupIdentifier": "swisscom-de", "enabled": "0"}
|
||||
]
|
||||
}]
|
||||
}]}}
|
||||
"""
|
||||
|
||||
private func dvr() throws -> PlexDVR {
|
||||
let container = try PlexResponse.decode(
|
||||
PlexMediaContainer<PlexDVR>.self,
|
||||
from: Data(json.utf8)
|
||||
from: Data(dvrJSON.utf8)
|
||||
)
|
||||
let dvr = try #require(container.content.first)
|
||||
|
||||
#expect(dvr.devices.count == 1)
|
||||
#expect(dvr.channels.count == 2)
|
||||
#expect(dvr.channels.first?.title == "SRF 1 HD")
|
||||
#expect(dvr.channels.first?.channelIdentifier == "1.1")
|
||||
return try #require(container.content.first)
|
||||
}
|
||||
|
||||
@Test("Auch die ältere Schreibweise ChannelMapping wird gelesen")
|
||||
func readsChannelMapping() throws {
|
||||
let json = """
|
||||
{"MediaContainer":{"Dvr":[{
|
||||
"key": "1",
|
||||
"Device": [{"uuid": "t", "ChannelMapping": [{"id": "a", "title": "3sat"}]}]
|
||||
}]}}
|
||||
"""
|
||||
let container = try PlexResponse.decode(
|
||||
PlexMediaContainer<PlexDVR>.self,
|
||||
from: Data(json.utf8)
|
||||
)
|
||||
#expect(container.content.first?.channels.first?.title == "3sat")
|
||||
@Test("Die Kennung des Programmanbieters steht am Empfänger")
|
||||
func readsEPGIdentifier() throws {
|
||||
// Verlässlicher, als sie aus der Anbieterliste zu suchen.
|
||||
#expect(try dvr().epgIdentifier == "tv.plex.providers.epg.cloud:2")
|
||||
}
|
||||
|
||||
@Test("Ein Empfänger ohne Sender ergibt eine leere Liste, keinen Fehler")
|
||||
func deviceWithoutChannels() throws {
|
||||
let json = #"{"MediaContainer":{"Dvr":[{"key":"1","Device":[{"uuid":"t"}]}]}}"#
|
||||
let container = try PlexResponse.decode(
|
||||
PlexMediaContainer<PlexDVR>.self,
|
||||
from: Data(json.utf8)
|
||||
)
|
||||
#expect(container.content.first?.channels.isEmpty == true)
|
||||
@Test("Die Senderliste wird gelesen")
|
||||
func readsLineup() throws {
|
||||
#expect(try dvr().lineups.first?.id == "swisscom-de")
|
||||
}
|
||||
|
||||
@Test("Nur eingeschaltete Sender zählen zur Zuordnung")
|
||||
func onlyEnabledMappings() throws {
|
||||
// `enabled` kommt als Zeichenkette „1" oder „0", nicht als
|
||||
// Wahrheitswert.
|
||||
let keys = try dvr().enabledChannelKeys
|
||||
#expect(keys == ["srf1", "srf2"])
|
||||
#expect(!keys.contains("abgeschaltet"))
|
||||
}
|
||||
|
||||
@Test("Ein Empfänger-Schlüssel als Zahl wird gelesen")
|
||||
func numericDVRKey() throws {
|
||||
// Je nach Serverfassung kommt er als Zahl oder als Zeichenkette. Ein
|
||||
// fester Typ hätte die ganze Empfängerliste unlesbar gemacht.
|
||||
// Je nach Serverfassung kommt er als Zahl oder als Zeichenkette.
|
||||
let json = #"{"MediaContainer":{"Dvr":[{"key":42}]}}"#
|
||||
let container = try PlexResponse.decode(
|
||||
PlexMediaContainer<PlexDVR>.self,
|
||||
@@ -117,3 +114,42 @@ struct TunerDecodingTests {
|
||||
#expect(container.content.first?.key == "42")
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("Abgleich von Zuordnung und Senderliste")
|
||||
struct ChannelMatchingTests {
|
||||
|
||||
@Test("Übereinstimmung über die Kennung")
|
||||
func matchesByIdentifier() {
|
||||
let channel = PlexChannel(id: "srf1", title: "SRF 1")
|
||||
#expect(channel.matches(anyOf: ["srf1", "srf2"]))
|
||||
}
|
||||
|
||||
@Test("Übereinstimmung über die Sendernummer")
|
||||
func matchesByChannelNumber() {
|
||||
let channel = PlexChannel(id: "x", title: "SRF 1", channelIdentifier: "1.1")
|
||||
#expect(channel.matches(anyOf: ["1.1"]))
|
||||
}
|
||||
|
||||
@Test("Übereinstimmung über den Pfad")
|
||||
func matchesByPath() {
|
||||
// Je nach Anbieter führt die Zuordnung den vollen Pfad, der Sender
|
||||
// aber nur die Kennung — oder umgekehrt.
|
||||
let channel = PlexChannel(id: "x", title: "SRF 1", key: "/livetv/channels/srf1")
|
||||
#expect(channel.matches(anyOf: ["srf1"]))
|
||||
#expect(channel.matches(anyOf: ["/livetv/channels/srf1"]))
|
||||
}
|
||||
|
||||
@Test("Umgekehrt: Zuordnung führt den Pfad, Sender die Kennung")
|
||||
func matchesReversed() {
|
||||
let channel = PlexChannel(id: "srf1", title: "SRF 1")
|
||||
#expect(channel.matches(anyOf: ["/lineups/swisscom/channels/srf1"]))
|
||||
}
|
||||
|
||||
@Test("Nicht zugeordnete Sender fallen heraus")
|
||||
func nonMatchingIsExcluded() {
|
||||
// Der Punkt der ganzen Übung: von 521 Sendern der Liste sollen die 28
|
||||
// übrig bleiben, die eingeschaltet sind.
|
||||
let channel = PlexChannel(id: "orf1", title: "ORF 1")
|
||||
#expect(!channel.matches(anyOf: ["srf1", "srf2"]))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user