From d2237c29cd1c4fe45a7bdde647b8e731a78d8f5c Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 27 Sep 2026 18:15:05 +0200 Subject: [PATCH] =?UTF-8?q?Fragen=20statt=20starten=20=E2=80=94=20die=20Pr?= =?UTF-8?q?obe=20=C3=BCberfuhr=20den=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim letzten Lauf scheiterte auch die Anfrage, die vorher funktioniert hatte. Kein neuer Befund, sondern mein Messfehler: die Probe warf sechs Transcode-Sitzungen in Folge an, und danach lehnt Plex alles mit nackten 400ern ab — auch das, was vorher ging. Jede Anfrage an `start.m3u8` startet einen Transcoder. Für eine Parameterreihe ist das das falsche Werkzeug. Der **Entscheider** sagt, was der Server täte, ohne etwas zu tun: Auflösung, Bitrate, und ob umgerechnet oder umgepackt wird. Die Reihe läuft jetzt darüber. Die zwei verbleibenden echten Sitzungen bekommen Abstand voneinander. Der Wert wird aus der Antwort gesucht statt geparst — Plex antwortet je nach Kopfzeile als XML oder JSON, und es geht um eine Anzeige, nicht um Daten, mit denen weitergerechnet wird. Co-Authored-By: Claude Opus 5 --- .../FlixyKit/Sources/FlixyProbe/main.swift | 47 +++++++++++++++---- .../Endpoints/PlexServer+Playback.swift | 21 +++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Packages/FlixyKit/Sources/FlixyProbe/main.swift b/Packages/FlixyKit/Sources/FlixyProbe/main.swift index 96e89ef..b1176c3 100644 --- a/Packages/FlixyKit/Sources/FlixyProbe/main.swift +++ b/Packages/FlixyKit/Sources/FlixyProbe/main.swift @@ -204,18 +204,26 @@ if CommandLine.arguments.contains("--leiter") { resolution: auflösung, isLocal: connection.local ) - guard let url = server.transcodeURL(for: probe) else { continue } - let antwort = await prüfePlaylist(url) - let zeile = antwort - .split(separator: "\n") - .first { $0.contains("Mbit/s") } - .map { $0.trimmingCharacters(in: .whitespaces) } ?? "keine Stufe" - print(" \(name): \(zeile)") - await server.stopTranscode(sessionIdentifier: probe.sessionIdentifier) + // **Über den Entscheider, nicht über start.m3u8.** Jede Anfrage dort + // wirft einen Transcoder an; sechs in Folge bringen den Server dazu, + // auch das abzulehnen, was vorher ging. Der Entscheider sagt, was er + // täte, ohne etwas zu tun. + let wortlaut = await server.decisionText(for: probe) + let auflösung = herausziehen("videoResolution", aus: wortlaut) + ?? herausziehen("height", aus: wortlaut) + let bitrate = herausziehen("bitrate", aus: wortlaut) + let entscheidung = herausziehen("transcodeDecisionText", aus: wortlaut) + ?? herausziehen("generalDecisionText", aus: wortlaut) + print(" \(name):") + print(" Auflösung \(auflösung ?? "–") · Bitrate \(bitrate ?? "–")") + if let entscheidung { print(" \(entscheidung)") } } print() + // Zwischen den beiden echten Sitzungen Luft lassen: Plex nimmt eine + // schnelle Folge von Transcode-Starts übel und lehnt danach alles ab. for (name, anfrage) in [("ohne autoAdjustQuality", ohne), ("mit autoAdjustQuality=1", mit)] { + try? await Task.sleep(for: .seconds(3)) print("=== \(name) ===") guard let url = server.transcodeURL(for: anfrage) else { print(" Keine Adresse zu bauen.\n") @@ -321,3 +329,26 @@ func storedClientIdentifier() -> String { UserDefaults.standard.set(created, forKey: key) return created } + + +/// Zieht einen Wert aus der Antwort des Entscheiders. +/// +/// Plex antwortet je nach Kopfzeile als XML oder JSON; beide Formen tragen +/// den Wert als `name="wert"` bzw. `"name":"wert"`. Statt zwei Parser zu +/// bauen, wird hier gesucht — es geht um eine Anzeige, nicht um Daten, mit +/// denen weitergerechnet wird. +func herausziehen(_ name: String, aus text: String) -> String? { + for muster in ["\(name)=\"", "\"\(name)\":\""] { + guard let bereich = text.range(of: muster) else { continue } + let rest = text[bereich.upperBound...] + let wert = String(rest.prefix { $0 != "\"" }) + if !wert.isEmpty { return wert } + } + // JSON-Zahlen stehen ohne Anführungszeichen. + if let bereich = text.range(of: "\"\(name)\":") { + let rest = text[bereich.upperBound...].drop { $0 == " " } + let wert = String(rest.prefix { $0.isNumber }) + if !wert.isEmpty { return wert } + } + return nil +} diff --git a/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+Playback.swift b/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+Playback.swift index 639323f..82bd601 100644 --- a/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+Playback.swift +++ b/Packages/FlixyKit/Sources/PlexKit/Endpoints/PlexServer+Playback.swift @@ -119,6 +119,27 @@ extension PlexServer { ) } + /// Die Entscheidung im Wortlaut — **ohne** etwas zu starten. + /// + /// Der Entscheider sagt, was der Server *täte*: welche Auflösung, welche + /// Bitrate, ob umgerechnet oder umgepackt wird. Das ist das richtige + /// Werkzeug, um Parameter auszuprobieren. + /// + /// Der Weg über `start.m3u8` ist es **nicht**: jede Anfrage dort wirft + /// einen Transcoder an, und ein halbes Dutzend davon in Folge bringt den + /// Server dazu, alles Weitere mit nackten 400ern abzulehnen — auch das, + /// was vorher ging. Genau so ist eine Messung schon einmal gekippt. + public func decisionText(for request: TranscodeRequest) async -> String { + let daten = try? await client.send( + PlexRequest( + path: "/video/:/transcode/universal/decision", + query: request.queryItems + clientProfileItems + ) + ) + guard let daten else { return "nicht erreichbar" } + return String(decoding: daten.prefix(4_000), as: UTF8.self) + } + /// Hält den Transcoder an. /// /// Muss beim Verlassen des Players laufen. Ohne dieses Signal rechnet der