Live-TV: der Empfänger spricht eine andere Sprache als der Programmführer
Aus der Antwort von /media/grabbers/devices/1:
{"channelKey":"6228f7f2364e02fa5ec8e51c-5fc706063f45c6002eab58a3",
"deviceIdentifier":"300",
"lineupIdentifier":"002"}
Der Programmführer kennt den Sender als `6228f7f2…-5fc706…`, der Tuner
als `300`. Die Zuordnung im Empfänger übersetzt zwischen beidem — und
genau diese Übersetzung habe ich nie benutzt. Jede Anfrage an den
Empfänger ging in der Sprache des Programmführers hinaus, und darauf gibt
es nur eine Antwort: 404.
Die Nummer am Empfänger steht jetzt an erster Stelle im Suchraum und im
Bericht. Der Empfänger ist ein HDHomeRun FLEX QUATRO mit vier Tunern; er
kennt Sender ausschließlich unter dieser Nummer.
Nebenbei aus derselben Ausgabe: /livetv/sessions antwortet mit size 0.
Die Route gibt es also — sie ist eine Liste. /livetv/sessions/<uuid> gibt
es nur, wenn die Sitzung vorher angelegt wurde, und deshalb war jeder
Abruf dort ein 404 ohne Aussagekraft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7987dbf9a4
commit
08e6e14534
@@ -385,6 +385,11 @@ extension PlexServer {
|
|||||||
// Womit überhaupt gearbeitet wird. Ich habe den Raster-Schlüssel
|
// Womit überhaupt gearbeitet wird. Ich habe den Raster-Schlüssel
|
||||||
// benutzt, ohne ihn je gesehen zu haben — und im Suchraum nur seinen
|
// benutzt, ohne ihn je gesehen zu haben — und im Suchraum nur seinen
|
||||||
// letzten Abschnitt verwendet.
|
// letzten Abschnitt verwendet.
|
||||||
|
// Die Sendernummer am Empfänger. Der Programmführer kennt den Sender
|
||||||
|
// als `6228f7f2…-5fc706…`, der Tuner als `300` — ohne diese
|
||||||
|
// Übersetzung antwortet der Empfänger auf alles mit 404.
|
||||||
|
let deviceChannel = dvr?.deviceChannel(forChannelKey: channel.id)
|
||||||
|
|
||||||
var trace: [ChannelProbe] = [ChannelProbe(
|
var trace: [ChannelProbe] = [ChannelProbe(
|
||||||
path: "Sender-Schlüssel",
|
path: "Sender-Schlüssel",
|
||||||
result: [
|
result: [
|
||||||
@@ -392,9 +397,34 @@ extension PlexServer {
|
|||||||
"key=\(channel.key ?? "–")",
|
"key=\(channel.key ?? "–")",
|
||||||
"id=\(channel.id)",
|
"id=\(channel.id)",
|
||||||
"nummer=\(channel.channelIdentifier ?? "–")",
|
"nummer=\(channel.channelIdentifier ?? "–")",
|
||||||
|
"amEmpfänger=\(deviceChannel ?? "–")",
|
||||||
].joined(separator: ", ")
|
].joined(separator: ", ")
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
// Zuerst der Weg über den Empfänger, mit **seiner** Sendernummer.
|
||||||
|
// Alles bisher Probierte sprach den Empfänger in der Sprache des
|
||||||
|
// Programmführers an; das musste 404 geben.
|
||||||
|
if let deviceChannel, let dvr {
|
||||||
|
for path in [
|
||||||
|
"/livetv/dvrs/\(dvr.key)/channels/\(deviceChannel)",
|
||||||
|
"/livetv/sessions/\(session)/\(deviceChannel)",
|
||||||
|
] {
|
||||||
|
attempts += 1
|
||||||
|
let outcome = await attemptStream(
|
||||||
|
path: path,
|
||||||
|
ratingKey: channel.id,
|
||||||
|
mediaIndex: -1,
|
||||||
|
includeProfile: true,
|
||||||
|
maximumBitrate: maximumBitrate,
|
||||||
|
session: session
|
||||||
|
)
|
||||||
|
trace.append(outcome.probe)
|
||||||
|
if let url = outcome.url {
|
||||||
|
return LiveTuneResult(url: url, sessionIdentifier: session, trace: trace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Der Raster-Schlüssel als Adresse, unverkürzt. Bisher habe ich nur
|
// Der Raster-Schlüssel als Adresse, unverkürzt. Bisher habe ich nur
|
||||||
// seinen letzten Abschnitt als Sitzungsschlüssel probiert — als
|
// seinen letzten Abschnitt als Sitzungsschlüssel probiert — als
|
||||||
// vollständige Adresse war er nie an der Reihe.
|
// vollständige Adresse war er nie an der Reihe.
|
||||||
@@ -706,7 +736,9 @@ extension PlexServer {
|
|||||||
var paths: [String] = []
|
var paths: [String] = []
|
||||||
|
|
||||||
if let dvr {
|
if let dvr {
|
||||||
let keys = [channel.gridKey, channel.key, channel.channelIdentifier, channel.id]
|
// Die Nummer am Empfänger zuerst: nur sie spricht seine Sprache.
|
||||||
|
let keys = ([dvr.deviceChannel(forChannelKey: channel.id)]
|
||||||
|
+ [channel.gridKey, channel.key, channel.channelIdentifier, channel.id])
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.map { $0.split(separator: "/").last.map(String.init) ?? $0 }
|
.map { $0.split(separator: "/").last.map(String.init) ?? $0 }
|
||||||
|
|
||||||
@@ -721,7 +753,7 @@ extension PlexServer {
|
|||||||
for device in dvr.devices {
|
for device in dvr.devices {
|
||||||
guard let deviceKey = device.key else { continue }
|
guard let deviceKey = device.key else { continue }
|
||||||
let base = deviceKey.hasPrefix("/") ? deviceKey : "/media/grabbers/devices/\(deviceKey)"
|
let base = deviceKey.hasPrefix("/") ? deviceKey : "/media/grabbers/devices/\(deviceKey)"
|
||||||
for key in keys.prefix(2) {
|
for key in keys.prefix(3) {
|
||||||
paths.append("\(base)/channels/\(key)/tune")
|
paths.append("\(base)/channels/\(key)/tune")
|
||||||
paths.append("\(base)/channels/\(key)")
|
paths.append("\(base)/channels/\(key)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,20 @@ public struct PlexDVR: Sendable, Equatable, Decodable, Identifiable {
|
|||||||
Set(devices.flatMap(\.channelMappings).filter(\.isEnabled).map(\.channelKey))
|
Set(devices.flatMap(\.channelMappings).filter(\.isEnabled).map(\.channelKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Die Sendernummer am Empfänger für eine Kennung des Programmführers.
|
||||||
|
///
|
||||||
|
/// Nachsichtig verglichen, weil die Kennung je nach Quelle mit oder ohne
|
||||||
|
/// Vorsatz der Senderliste kommt.
|
||||||
|
public func deviceChannel(forChannelKey key: String) -> String? {
|
||||||
|
let tail = key.split(separator: "-").last.map(String.init)
|
||||||
|
for mapping in devices.flatMap(\.channelMappings) {
|
||||||
|
guard let identifier = mapping.deviceIdentifier else { continue }
|
||||||
|
if mapping.channelKey == key { return identifier }
|
||||||
|
if let tail, mapping.channelKey.hasSuffix(tail) { return identifier }
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case key, uuid, language, lineupTitle, lineup, epgIdentifier
|
case key, uuid, language, lineupTitle, lineup, epgIdentifier
|
||||||
case devices = "Device"
|
case devices = "Device"
|
||||||
@@ -115,7 +129,15 @@ public struct PlexLineup: Sendable, Equatable, Decodable, Identifiable {
|
|||||||
/// Sagt, welcher Sender der Senderliste am Empfänger eingeschaltet ist —
|
/// Sagt, welcher Sender der Senderliste am Empfänger eingeschaltet ist —
|
||||||
/// mehr nicht. Namen, Nummern und Logos stehen beim Programmanbieter.
|
/// mehr nicht. Namen, Nummern und Logos stehen beim Programmanbieter.
|
||||||
public struct PlexChannelMapping: Sendable, Equatable, Decodable {
|
public struct PlexChannelMapping: Sendable, Equatable, Decodable {
|
||||||
|
/// Die Kennung des Senders beim Programmführer.
|
||||||
public let channelKey: String
|
public let channelKey: String
|
||||||
|
/// Die Sendernummer **am Empfänger** — etwa `"300"`.
|
||||||
|
///
|
||||||
|
/// Der Stein von Rosetta dieser Schnittstelle: der Programmführer kennt
|
||||||
|
/// den Sender als `6228f7f2…-5fc706…`, der Tuner als `300`. Ohne diese
|
||||||
|
/// Übersetzung redet man mit dem Empfänger in einer Sprache, die er nicht
|
||||||
|
/// spricht — und bekommt auf jede Anfrage 404.
|
||||||
|
public let deviceIdentifier: String?
|
||||||
public let lineupIdentifier: String?
|
public let lineupIdentifier: String?
|
||||||
private let enabled: String?
|
private let enabled: String?
|
||||||
|
|
||||||
@@ -123,8 +145,14 @@ public struct PlexChannelMapping: Sendable, Equatable, Decodable {
|
|||||||
/// Wahrheitswert.
|
/// Wahrheitswert.
|
||||||
public var isEnabled: Bool { enabled != "0" }
|
public var isEnabled: Bool { enabled != "0" }
|
||||||
|
|
||||||
public init(channelKey: String, lineupIdentifier: String? = nil, enabled: String? = "1") {
|
public init(
|
||||||
|
channelKey: String,
|
||||||
|
deviceIdentifier: String? = nil,
|
||||||
|
lineupIdentifier: String? = nil,
|
||||||
|
enabled: String? = "1"
|
||||||
|
) {
|
||||||
self.channelKey = channelKey
|
self.channelKey = channelKey
|
||||||
|
self.deviceIdentifier = deviceIdentifier
|
||||||
self.lineupIdentifier = lineupIdentifier
|
self.lineupIdentifier = lineupIdentifier
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,36 @@ struct LiveTVGuideTests {
|
|||||||
#expect(path == "/livetv/sessions/xyz/stream")
|
#expect(path == "/livetv/sessions/xyz/stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Die Zuordnung übersetzt zwischen Programmführer und Empfänger")
|
||||||
|
func translatesChannelKeyToDeviceChannel() {
|
||||||
|
// Der Programmführer kennt den Sender als `6228f7f2…-5fc706…`, der
|
||||||
|
// Tuner als `300`. Ohne diese Übersetzung spricht man den Empfänger
|
||||||
|
// in einer Sprache an, die er nicht versteht — und bekommt 404 auf
|
||||||
|
// alles. Genau daran ist das Einschalten reihenweise gescheitert.
|
||||||
|
let dvr = PlexDVR(
|
||||||
|
key: "2",
|
||||||
|
devices: [PlexTunerDevice(key: "1", channelMappings: [
|
||||||
|
PlexChannelMapping(
|
||||||
|
channelKey: "6228f7f2364e02fa5ec8e51c-5fc706063f45c6002eab58a3",
|
||||||
|
deviceIdentifier: "300",
|
||||||
|
lineupIdentifier: "002"
|
||||||
|
),
|
||||||
|
PlexChannelMapping(
|
||||||
|
channelKey: "6228f7f2364e02fa5ec8e51c-5fc705fa6b022a002d77f212",
|
||||||
|
deviceIdentifier: "299",
|
||||||
|
lineupIdentifier: "001"
|
||||||
|
),
|
||||||
|
])]
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(dvr.deviceChannel(forChannelKey: "6228f7f2364e02fa5ec8e51c-5fc706063f45c6002eab58a3") == "300")
|
||||||
|
#expect(dvr.deviceChannel(forChannelKey: "6228f7f2364e02fa5ec8e51c-5fc705fa6b022a002d77f212") == "299")
|
||||||
|
// Auch ohne den Vorsatz der Senderliste, denn die Kennung kommt je
|
||||||
|
// nach Quelle mit oder ohne ihn.
|
||||||
|
#expect(dvr.deviceChannel(forChannelKey: "5fc706063f45c6002eab58a3") == "300")
|
||||||
|
#expect(dvr.deviceChannel(forChannelKey: "unbekannt") == nil)
|
||||||
|
}
|
||||||
|
|
||||||
@Test("Die Sender des Tuners stehen unter DeviceChannel")
|
@Test("Die Sender des Tuners stehen unter DeviceChannel")
|
||||||
func readsDeviceChannelKey() async throws {
|
func readsDeviceChannelKey() async throws {
|
||||||
// Der Server lieferte 278 Sender, die Hülle verwarf sie: sie kannte
|
// Der Server lieferte 278 Sender, die Hülle verwarf sie: sie kannte
|
||||||
|
|||||||
Reference in New Issue
Block a user