From 5e1a8782378d6dca8959839342c1ae840f5aaf4f Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sat, 12 Sep 2026 16:10:27 +0200 Subject: [PATCH] Live-TV: es waren zwei verschiedene UUIDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die vollständige Sitzung zeigt es nebeneinander: Tune-Antwort: …/sessions/df16c509…/B5080949-E767-…/index.m3u8 Sitzung selbst: …/sessions/df16c509…/2F3939E2-6196-…/index.m3u8 Dieselbe Adresse, aber mit verschiedenen Kennungen darin: die Tune-Antwort setzt die Sitzungskennung ein, die Sitzung selbst die Gerätekennung. Nur die zweite gibt es wirklich. Ich habe die erste genommen — daher jedes Mal 404. Gesehen habe ich das erst, als die Antwort nicht mehr bei 700 Zeichen abgeschnitten war. Genau dort fing `Part` an. Dieselbe Antwort trägt auch `TranscodeSession` — der Strom läuft bereits. Es gab nie etwas umzurechnen, und der Transcoder hatte recht, jede dieser Anfragen abzulehnen. Die Abspieladresse kommt jetzt ausschließlich aus der Sitzung. Co-Authored-By: Claude Opus 5 --- .../PlexKit/Endpoints/PlexServer+LiveTV.swift | 16 ++++++++++ .../Tests/PlexKitTests/LiveTVGuideTests.swift | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+LiveTV.swift b/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+LiveTV.swift index 19a7511..f3ced83 100644 --- a/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+LiveTV.swift +++ b/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+LiveTV.swift @@ -702,6 +702,22 @@ extension PlexServer { ), ] + // Die Sitzung selbst fragen — und **nur** ihr die Abspieladresse + // glauben. Die Antwort auf das Belegen nennt eine Adresse mit der + // Sitzungskennung darin; die Sitzung nennt dieselbe Adresse mit der + // **Gerätekennung**. Nur die zweite gibt es wirklich. Ich habe die + // erste genommen und mich über 404 gewundert. + if let sessionKey = Self.firstSessionKey(in: data), + let sessionData = try? await client.send( + PlexRequest(path: sessionKey, timeout: .seconds(20)) + ), + let livePart = Self.firstPartKey(in: sessionData), + let direct = directStreamURL(forPath: livePart) { + let outcome = await probeStream(url: direct) + probes.append(ChannelProbe(path: "\(livePart) (aus der Sitzung)", result: outcome)) + if outcome.hasPrefix("HTTP 200") { return (direct, subscription, probes) } + } + // Abwarten, bis der Empfänger abgestimmt hat. Er meldet // `status: inprogress` und `currentSize: 0` — es gibt in dem Moment // noch keinen Strom, und jede Frage danach musste scheitern. Ein diff --git a/Packages/FlixyKit/Tests/PlexKitTests/LiveTVGuideTests.swift b/Packages/FlixyKit/Tests/PlexKitTests/LiveTVGuideTests.swift index 8e27e07..199a187 100644 --- a/Packages/FlixyKit/Tests/PlexKitTests/LiveTVGuideTests.swift +++ b/Packages/FlixyKit/Tests/PlexKitTests/LiveTVGuideTests.swift @@ -359,6 +359,38 @@ struct LiveTVGuideTests { #expect(items.contains { $0.name == "hasMDE" && $0.value == "1" }) } + @Test("Die Abspieladresse kommt aus der Sitzung, nicht aus der Tune-Antwort") + func prefersPartFromSession() async throws { + // Beide Antworten nennen eine Adresse derselben Form, aber mit + // verschiedenen Kennungen darin: die Tune-Antwort setzt die + // Sitzungskennung ein, die Sitzung selbst die Gerätekennung. Nur die + // zweite gibt es wirklich — die erste ergab jedes Mal 404. + let tuned = """ + {"MediaContainer":{"size":1,"MediaSubscription":[{"key":"22", + "MetadataItem":{"key":"/livetv/sessions/abc","live":"1", + "Media":[{"id":1,"Part":[{"key":"/livetv/sessions/abc/SITZUNGSKENNUNG/index.m3u8"}]}]}}]}} + """ + let session = """ + {"MediaContainer":{"size":1,"Metadata":[{"key":"/livetv/sessions/abc","live":"1", + "Media":[{"id":1,"Part":[{"key":"/livetv/sessions/abc/GERAETEKENNUNG/index.m3u8?offset=-1.000000"}]}]}]}} + """ + let server = server(routes: [ + "/channels/kanal-7/tune": tuned, + "/livetv/sessions/abc": session, + // Nur unter der Gerätekennung liefert der Server eine + // Abspielliste; die andere Form gibt es nicht. + "/GERAETEKENNUNG/index.m3u8": "#EXTM3U", + ]) + let channel = PlexChannel(id: "kanal-7", title: "SRF 1 HD", key: "2") + + let result = await server.tune(channel: channel, dvr: PlexDVR(key: "2"), maximumBitrate: nil) + let url = try #require(result.url) + + #expect(url.path() == "/livetv/sessions/abc/GERAETEKENNUNG/index.m3u8") + #expect(!url.absoluteString.contains("SITZUNGSKENNUNG")) + #expect(!url.absoluteString.contains("/transcode/")) + } + @Test("Die Teil-Adresse wird gefunden, egal wie tief sie liegt") func findsPartAtAnyDepth() throws { // Der Vorgang liegt je nach Serverfassung unterschiedlich tief