Live-TV: POST, nicht GET — nachgelesen statt geraten
Aus dem Quelltext von Plex' eigenem Web-Client:
const o = `/livetv/dvrs/${t}/channels/${a}/tune`,
l = {method:"POST", headers:{"X-Plex-Session-Identifier": …}}
Genau diesen Pfad hatte ich seit mehreren Runden richtig. Jedes Mal per
GET abgefragt, jedes Mal 404 — und ein 404 sieht aus wie „gibt es nicht"
statt wie „nicht so". Also habe ich den Pfad verworfen und weitergesucht,
wieder und wieder, an einer Stelle, die von Anfang an stimmte.
Die Antwort auf das POST trägt den Vorgang mitsamt Metadaten-Eintrag, und
dort steht die abspielbare Teil-Adresse — nicht im Raster und nicht bei
der Sendung. Sie wird suchend gelesen statt an fester Stelle: der Vorgang
liegt je nach Serverfassung unterschiedlich tief, und ein falscher Weg
hinein sähe wieder aus wie „nichts da".
Die Kennung wird wie in `encodeURIComponent` kodiert — insbesondere
bleibt der Bindestrich stehen. `.alphanumerics` hätte aus
`6228f7f2-5fc705fa` ein `6228f7f2%2D5fc705fa` gemacht, und der Server
fände den Sender nicht.
Dass die Antwort in einer öffentlichen Quelle stand, während ich zwei
Tage lang Adressen erfunden habe, ist die eigentliche Lehre dieser Runde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
08e6e14534
commit
562fd758d5
@@ -295,6 +295,48 @@ struct LiveTVGuideTests {
|
||||
#expect(path == "/livetv/sessions/xyz/stream")
|
||||
}
|
||||
|
||||
@Test("Der Tuner wird per POST belegt, und die Antwort trägt den Strom")
|
||||
func tunesThroughDVRWithPost() async throws {
|
||||
// Nachgelesen im Quelltext von Plex' eigenem Client:
|
||||
// POST /livetv/dvrs/{dvr}/channels/{kennung}/tune
|
||||
// Genau diesen Pfad hatte ich seit Runden richtig — und jedes Mal
|
||||
// mit GET abgefragt. Die Antwort darauf ist 404, und das sah aus wie
|
||||
// „gibt es nicht" statt wie „nicht so".
|
||||
let tuned = """
|
||||
{"MediaContainer":{"size":1,"MediaSubscription":[{"key":"77",
|
||||
"MetadataItem":{"ratingKey":"live-77","key":"/livetv/sessions/abc","type":"clip",
|
||||
"title":"blue ZOOM HD",
|
||||
"Media":[{"id":9,"Part":[{"id":9,"key":"/livetv/sessions/abc/parts/9"}]}]}}]}}
|
||||
"""
|
||||
let server = server(routes: [
|
||||
"/channels/6228f7f2-5fc705fa/tune": tuned,
|
||||
"/start.m3u8": "#EXTM3U",
|
||||
])
|
||||
let channel = PlexChannel(id: "6228f7f2-5fc705fa", title: "blue ZOOM HD", key: "1")
|
||||
let dvr = PlexDVR(key: "2")
|
||||
|
||||
let result = await server.tune(channel: channel, dvr: dvr, maximumBitrate: nil)
|
||||
let url = try #require(result.url)
|
||||
let path = try #require(URLComponents(url: url, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?.first { $0.name == "path" }?.value)
|
||||
|
||||
#expect(path == "/livetv/sessions/abc/parts/9")
|
||||
#expect(result.trace.contains { $0.path.hasPrefix("POST ") })
|
||||
}
|
||||
|
||||
@Test("Die Teil-Adresse wird gefunden, egal wie tief sie liegt")
|
||||
func findsPartAtAnyDepth() throws {
|
||||
// Der Vorgang liegt je nach Serverfassung unterschiedlich tief
|
||||
// verschachtelt. Ein fest verdrahteter Weg hinein sähe wieder aus
|
||||
// wie „nichts da".
|
||||
let json = Data("""
|
||||
{"MediaContainer":{"Tief":{"Tiefer":[{"Media":[{"Part":[{"key":"/gefunden"}]}]}]}}}
|
||||
""".utf8)
|
||||
|
||||
#expect(PlexServer.firstPartKey(in: json) == "/gefunden")
|
||||
#expect(PlexServer.firstPartKey(in: Data("{}".utf8)) == nil)
|
||||
}
|
||||
|
||||
@Test("Die Zuordnung übersetzt zwischen Programmführer und Empfänger")
|
||||
func translatesChannelKeyToDeviceChannel() {
|
||||
// Der Programmführer kennt den Sender als `6228f7f2…-5fc706…`, der
|
||||
|
||||
Reference in New Issue
Block a user