Live-TV: den Medien-Block ansehen, statt ihn zu zählen
„1 Fassung, keine Teil-Adresse" beendet nichts. Wenn dort kein abspielbarer Pfad steht, zählt, was stattdessen dort steht — irgendwo muss der Server sagen, wie diese Sendung abzuspielen ist. Der Bericht zeigt den Medien-Block jetzt Feld für Feld. Dazu zwei Dinge, die ich benutzt habe, ohne sie je gesehen zu haben: der Raster-Schlüssel des Senders und seine übrigen Kennungen stehen jetzt im Bericht. Und der Raster-Schlüssel wird als vollständige Adresse probiert — bisher war immer nur sein letzter Abschnitt als Sitzungsschlüssel an der Reihe. Der Empfänger kommt ebenfalls dazu: /livetv/dvrs/2/channels antwortet auf diesem Server gar nicht, aber /media/grabbers/devices/1/channels führt 278 Sender. Wenn der DVR keine kennt, kennt sie das Gerät. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
62a913f2cd
commit
162247e8dc
@@ -380,9 +380,40 @@ extension PlexServer {
|
||||
maximumBitrate: Int?
|
||||
) async -> LiveTuneResult {
|
||||
let session = UUID().uuidString
|
||||
var trace: [ChannelProbe] = []
|
||||
var attempts = 0
|
||||
|
||||
// Womit überhaupt gearbeitet wird. Ich habe den Raster-Schlüssel
|
||||
// benutzt, ohne ihn je gesehen zu haben — und im Suchraum nur seinen
|
||||
// letzten Abschnitt verwendet.
|
||||
var trace: [ChannelProbe] = [ChannelProbe(
|
||||
path: "Sender-Schlüssel",
|
||||
result: [
|
||||
"gridKey=\(channel.gridKey ?? "–")",
|
||||
"key=\(channel.key ?? "–")",
|
||||
"id=\(channel.id)",
|
||||
"nummer=\(channel.channelIdentifier ?? "–")",
|
||||
].joined(separator: ", ")
|
||||
)]
|
||||
|
||||
// Der Raster-Schlüssel als Adresse, unverkürzt. Bisher habe ich nur
|
||||
// seinen letzten Abschnitt als Sitzungsschlüssel probiert — als
|
||||
// vollständige Adresse war er nie an der Reihe.
|
||||
if let gridKey = channel.gridKey, gridKey.hasPrefix("/") {
|
||||
attempts += 1
|
||||
let outcome = await attemptStream(
|
||||
path: gridKey,
|
||||
ratingKey: channel.id,
|
||||
mediaIndex: 0,
|
||||
includeProfile: true,
|
||||
maximumBitrate: maximumBitrate,
|
||||
session: session
|
||||
)
|
||||
trace.append(outcome.probe)
|
||||
if let url = outcome.url {
|
||||
return LiveTuneResult(url: url, sessionIdentifier: session, trace: trace)
|
||||
}
|
||||
}
|
||||
|
||||
// Die Sendung erst nachschlagen. Das Raster nennt keine abspielbare
|
||||
// Adresse — die Einzelabfrage beim Programmanbieter womöglich schon.
|
||||
// Ihre Adresse blind an den Transcoder zu geben, hat dreimal eine
|
||||
@@ -526,11 +557,42 @@ extension PlexServer {
|
||||
}
|
||||
|
||||
let parts = item.media.flatMap(\.parts).compactMap(\.key)
|
||||
let summary = parts.isEmpty
|
||||
? "\(item.media.count) Fassungen, keine Teil-Adresse — \(Self.containerSummary(data))"
|
||||
: "\(parts.count) Teil-Adressen"
|
||||
guard parts.isEmpty else {
|
||||
return (parts, item.ratingKey, "\(parts.count) Teil-Adressen")
|
||||
}
|
||||
|
||||
return (parts, item.ratingKey, summary)
|
||||
// Keine Teil-Adresse — dann zählt, was stattdessen dort steht.
|
||||
// „1 Fassung, keine Teil-Adresse" beendet nichts; der Aufbau der
|
||||
// Antwort schon, denn irgendwo muss der Server sagen, wie diese
|
||||
// Sendung abzuspielen ist.
|
||||
return ([], item.ratingKey, "ohne Teil-Adresse\n" + Self.mediaOutline(data))
|
||||
}
|
||||
|
||||
/// Der Aufbau des Medien-Blocks einer Antwort, kurz gehalten.
|
||||
static func mediaOutline(_ data: Data) -> String {
|
||||
guard let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let container = object["MediaContainer"] as? [String: Any],
|
||||
let first = (container["Metadata"] as? [[String: Any]])?.first
|
||||
else { return Self.containerSummary(data) }
|
||||
|
||||
var lines: [String] = []
|
||||
if let media = (first["Media"] as? [[String: Any]])?.first {
|
||||
lines += media.keys.sorted().map { key in
|
||||
let value = media[key]!
|
||||
if let text = value as? String { return " Media.\(key) = \(text)" }
|
||||
if let number = value as? NSNumber { return " Media.\(key) = \(number)" }
|
||||
if let array = value as? [Any] { return " Media.\(key): \(array.count) Einträge" }
|
||||
return " Media.\(key): \(type(of: value))"
|
||||
}
|
||||
} else {
|
||||
lines.append(" kein Media-Block")
|
||||
}
|
||||
|
||||
// Und die Kurzform der Sendung selbst — dort stehen `key` und `guid`.
|
||||
for key in ["key", "guid", "ratingKey", "type", "live", "onAir"] where first[key] != nil {
|
||||
lines.append(" \(key) = \(first[key]!)")
|
||||
}
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
/// Baut eine Stromadresse und ruft sie einmal ab.
|
||||
@@ -628,12 +690,26 @@ extension PlexServer {
|
||||
var paths: [String] = []
|
||||
|
||||
if let dvr {
|
||||
for key in [channel.gridKey, channel.key, channel.channelIdentifier, channel.id]
|
||||
.compactMap({ $0 })
|
||||
.map({ $0.split(separator: "/").last.map(String.init) ?? $0 }) {
|
||||
let keys = [channel.gridKey, channel.key, channel.channelIdentifier, channel.id]
|
||||
.compactMap { $0 }
|
||||
.map { $0.split(separator: "/").last.map(String.init) ?? $0 }
|
||||
|
||||
for key in keys {
|
||||
paths.append("/livetv/dvrs/\(dvr.key)/channels/\(key)/tune")
|
||||
paths.append("/livetv/dvrs/\(dvr.key)/channels/\(key)")
|
||||
}
|
||||
|
||||
// Und über das Gerät. Es ist die einzige Stelle, die auf diesem
|
||||
// Server überhaupt Sender führt — 278 Stück unter `DeviceChannel`.
|
||||
// Wenn der DVR keine kennt, kennt sie das Gerät.
|
||||
for device in dvr.devices {
|
||||
guard let deviceKey = device.key else { continue }
|
||||
let base = deviceKey.hasPrefix("/") ? deviceKey : "/media/grabbers/devices/\(deviceKey)"
|
||||
for key in keys.prefix(2) {
|
||||
paths.append("\(base)/channels/\(key)/tune")
|
||||
paths.append("\(base)/channels/\(key)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths += sessionKeys(channel: channel, program: program).map { "\(sessionPath)/\($0)" }
|
||||
|
||||
Reference in New Issue
Block a user