Wiedergabe: jeden Parameter streng kodieren, so wie Plex es selbst tut
Die gebaute Adresse anzusehen hat es in einem Blick beantwortet:
path=/livetv/sessions/0043d829-… ← meine
path=%2Flivetv%2Fsessions%2F0043d829-… ← Plex
Plex' Client schickt alle Parameter durch `qs.stringify`, und das kodiert
alles. `URLComponents` lässt `/`, `:`, `&`, `=`, `+` und Klammern in
Werten stehen. Beim Server kam damit ein Pfad mit echten Schrägstrichen
an — und ein Geräteprofil, das an seinen `&` in zwei Dutzend eigene
Angaben zerfiel. Beides beantwortet er mit einer nackten 400.
Eine Regel für alle Werte, keine Ausnahmen. Damit löst sich nebenbei die
Kodierfrage, die mich mehrere Anläufe gekostet hat: eine Kennung, die
schon `%3A` enthält, wird dabei zu `%253A` — und ist genau einmal
kodiert. Die Sonderbehandlung für `path` und das Profil entfällt, und mit
ihr die Schalter, mit denen ich zwischen falschen Formen hin- und
hergeschaltet habe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ef9fd676c8
commit
44dd794e86
@@ -23,7 +23,6 @@ extension PlexServer {
|
||||
public nonisolated func transcodeURL(
|
||||
for request: TranscodeRequest,
|
||||
includeProfile: Bool = true,
|
||||
keepPathEncoding: Bool = true,
|
||||
minimal: Bool = false
|
||||
) -> URL? {
|
||||
guard let baseURL = connection.url,
|
||||
@@ -51,48 +50,34 @@ extension PlexServer {
|
||||
: request.queryItems + [
|
||||
URLQueryItem(name: "X-Plex-Token", value: token),
|
||||
] + (includeProfile ? clientProfileItems : []) + identity.queryItems
|
||||
components.queryItems = items
|
||||
|
||||
components.percentEncodedQueryItems = components.percentEncodedQueryItems?.map { item in
|
||||
// `path` von Hand einsetzen heißt: genau einmal kodiert. Das ist
|
||||
// für Dateien richtig. Für eine Sendung beim Programmführer muss
|
||||
// die Kennung ein zweites Mal kodiert werden — dann übernimmt das
|
||||
// `URLComponents` von selbst, und hier ist nichts zu tun.
|
||||
if item.name == "path", keepPathEncoding {
|
||||
return URLQueryItem(name: "path", value: request.percentEncodedPath)
|
||||
// Jeder Wert streng kodiert, ohne Ausnahme — genauso macht es Plex'
|
||||
// eigener Client, der alle Parameter durch `qs.stringify` schickt.
|
||||
//
|
||||
// `URLComponents` allein tut das nicht: es lässt `/`, `:`, `&`, `=`,
|
||||
// `+` und Klammern in Werten stehen. Damit kam beim Server
|
||||
// `path=/livetv/sessions/…` an statt `path=%2Flivetv%2Fsessions%2F…`,
|
||||
// und das Geräteprofil zerfiel an seinen `&` in zwei Dutzend eigene
|
||||
// Angaben. Beides beantwortet er mit einer nackten 400.
|
||||
//
|
||||
// Nebenbei löst diese eine Regel die Kodierfrage, die mich mehrere
|
||||
// Anläufe gekostet hat: eine Kennung, die bereits `%3A` enthält, wird
|
||||
// hier zu `%253A` — und ist damit genau einmal richtig kodiert.
|
||||
components.percentEncodedQuery = items
|
||||
.map { item in
|
||||
let value = item.value?
|
||||
.addingPercentEncoding(withAllowedCharacters: .plexParameterAllowed) ?? ""
|
||||
return "\(item.name)=\(value)"
|
||||
}
|
||||
.joined(separator: "&")
|
||||
|
||||
// Das Geräteprofil ist ein Wert voller `&`, `=` und Klammern —
|
||||
// und `URLComponents` lässt genau die unangetastet. Der Server
|
||||
// liest sie dann als eigene Parameter: aus einem Profil werden
|
||||
// zwei Dutzend Angaben, darunter ein zweites `protocol=`. Das
|
||||
// steckte in **jeder** Transcoder-Adresse, seit es sie gibt.
|
||||
if item.name.hasSuffix("Profile-Extra"),
|
||||
// Der **ursprüngliche** Wert, nicht der bereits von
|
||||
// `URLComponents` behandelte: den ein zweites Mal zu kodieren
|
||||
// macht aus `%3D` ein `%253D`.
|
||||
let value = items.first(where: { $0.name == item.name })?.value {
|
||||
return URLQueryItem(
|
||||
name: item.name,
|
||||
value: value.addingPercentEncoding(withAllowedCharacters: .plexParameterAllowed)
|
||||
?? value
|
||||
)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
return components.url
|
||||
}
|
||||
|
||||
/// Baut die Adresse eines Stroms, den der Server schon bereitstellt.
|
||||
///
|
||||
/// Für Live-TV. Das Belegen eines Tuners liefert eine fertige
|
||||
/// HLS-Abspielliste — die gehört abgerufen, nicht umgerechnet. Sie durch
|
||||
/// den Transcoder zu schicken heißt, ihn um die Umrechnung von etwas zu
|
||||
/// bitten, das bereits fertig ist; er antwortet mit einer nackten 400.
|
||||
///
|
||||
/// Token und Gerätekennung müssen in die Adresse, weil sie der
|
||||
/// Systemplayer abruft und der keine Kopfzeilen mitschickt.
|
||||
/// Für Live-TV. Token und Gerätekennung müssen in die Adresse, weil sie
|
||||
/// der Systemplayer abruft und der keine Kopfzeilen mitschickt.
|
||||
public nonisolated func directStreamURL(forPath path: String) -> URL? {
|
||||
guard let baseURL = connection.url,
|
||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
|
||||
|
||||
Reference in New Issue
Block a user