Live-TV: die gebaute Adresse zeigen und die Parameter halbieren
Zwei Dinge, die ich mehrere Runden zu spät tue. Die Adresse steht jetzt in der Spur, mit entferntem Token. Ohne sie diskutiere ich über eine Anfrage, die niemand gesehen hat — und genau das habe ich getan, während ich einzelne Parameter der Reihe nach verdächtigt habe. Und eine Form, die nur das Nötigste enthält: Pfad, Protokoll, hasMDE, Indizes, Sitzung, Token, Gerätekennung. Eine nackte 400 sagt nicht, welche Angabe stört. Drei Formen nebeneinander — voll, ohne Profil, nur das Nötigste — sagen es. Dazu zwei Kleinigkeiten: die Abspielliste wird ein zweites Mal nach zwei Sekunden abgerufen, weil ein Empfänger ein Stück Hardware ist und einen Moment braucht, bis er abgestimmt hat. Und wenn das Belegen klappt, hört die Suche danach auf — die übrigen zwanzig Abfragen sind dann nur noch Lärm im Bericht und Last für den Server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3467f0f690
commit
ef9fd676c8
@@ -428,10 +428,21 @@ extension PlexServer {
|
|||||||
trace: trace
|
trace: trace
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Belegt, aber nicht abspielbar: den Tuner sofort wieder
|
// Belegt, aber nicht abspielbar: den Tuner sofort wieder
|
||||||
// freigeben, statt ihn für nichts zu halten.
|
// freigeben, statt ihn für nichts zu halten.
|
||||||
if let subscription = outcome.subscription {
|
if let subscription = outcome.subscription {
|
||||||
await endLiveSession(subscriptionKey: subscription)
|
await endLiveSession(subscriptionKey: subscription)
|
||||||
|
|
||||||
|
// Und hier aufhören. Dass das Belegen klappt, beantwortet die
|
||||||
|
// Frage nach dem Weg — die übrigen zwanzig Abfragen sind dann
|
||||||
|
// nur noch Lärm im Bericht und Last für den Server.
|
||||||
|
return LiveTuneResult(
|
||||||
|
url: nil,
|
||||||
|
sessionIdentifier: session,
|
||||||
|
subscriptionKey: nil,
|
||||||
|
trace: trace
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,12 +702,17 @@ extension PlexServer {
|
|||||||
// angelegt hat, wird umgerechnet — nicht die Abspielliste darin und
|
// angelegt hat, wird umgerechnet — nicht die Abspielliste darin und
|
||||||
// nicht meine eigene Kennung. Ihr Schlüssel steht in der Antwort.
|
// nicht meine eigene Kennung. Ihr Schlüssel steht in der Antwort.
|
||||||
if let sessionKey = Self.firstSessionKey(in: data) {
|
if let sessionKey = Self.firstSessionKey(in: data) {
|
||||||
for withProfile in [true, false] {
|
// Voll, ohne Profil, und auf das Nötigste zusammengestrichen.
|
||||||
|
// Eine nackte 400 sagt nicht, welche Angabe sie stört; die drei
|
||||||
|
// Formen nebeneinander sagen es.
|
||||||
|
let variants: [(profile: Bool, minimal: Bool)] = [(true, false), (false, false), (true, true)]
|
||||||
|
for variant in variants {
|
||||||
let outcome = await attemptStream(
|
let outcome = await attemptStream(
|
||||||
path: sessionKey,
|
path: sessionKey,
|
||||||
ratingKey: channel.id,
|
ratingKey: channel.id,
|
||||||
mediaIndex: 0,
|
mediaIndex: 0,
|
||||||
includeProfile: withProfile,
|
includeProfile: variant.profile,
|
||||||
|
minimal: variant.minimal,
|
||||||
maximumBitrate: maximumBitrate,
|
maximumBitrate: maximumBitrate,
|
||||||
session: session
|
session: session
|
||||||
)
|
)
|
||||||
@@ -706,11 +722,19 @@ extension PlexServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Und die Abspielliste unmittelbar. Sie antwortete mit 404 statt 400:
|
// Und die Abspielliste unmittelbar. Sie antwortete mit 404 statt 400:
|
||||||
// die Adresse stimmt, die Ressource steht nur nicht bereit.
|
// die Adresse stimmt, die Ressource steht nur nicht bereit. Ein
|
||||||
|
// Empfänger ist ein Stück Hardware — er braucht einen Moment, bis er
|
||||||
|
// abgestimmt hat. Deshalb ein zweiter Versuch nach kurzer Pause.
|
||||||
if let direct = directStreamURL(forPath: part) {
|
if let direct = directStreamURL(forPath: part) {
|
||||||
let outcome = await probeStream(url: direct)
|
for attempt in 0..<2 {
|
||||||
probes.append(ChannelProbe(path: "\(part) (unmittelbar)", result: outcome))
|
if attempt > 0 { try? await Task.sleep(for: .seconds(2)) }
|
||||||
if outcome.hasPrefix("HTTP 200") { return (direct, subscription, probes) }
|
let outcome = await probeStream(url: direct)
|
||||||
|
probes.append(ChannelProbe(
|
||||||
|
path: "\(part) (unmittelbar\(attempt > 0 ? ", nach 2 s" : ""))",
|
||||||
|
result: outcome
|
||||||
|
))
|
||||||
|
if outcome.hasPrefix("HTTP 200") { return (direct, subscription, probes) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let outcome = await attemptStream(
|
let outcome = await attemptStream(
|
||||||
@@ -825,6 +849,7 @@ extension PlexServer {
|
|||||||
mediaIndex: Int,
|
mediaIndex: Int,
|
||||||
includeProfile: Bool,
|
includeProfile: Bool,
|
||||||
keepPathEncoding: Bool = true,
|
keepPathEncoding: Bool = true,
|
||||||
|
minimal: Bool = false,
|
||||||
maximumBitrate: Int?,
|
maximumBitrate: Int?,
|
||||||
session: String
|
session: String
|
||||||
) async -> (url: URL?, probe: ChannelProbe) {
|
) async -> (url: URL?, probe: ChannelProbe) {
|
||||||
@@ -841,7 +866,8 @@ extension PlexServer {
|
|||||||
guard let url = transcodeURL(
|
guard let url = transcodeURL(
|
||||||
for: request,
|
for: request,
|
||||||
includeProfile: includeProfile,
|
includeProfile: includeProfile,
|
||||||
keepPathEncoding: keepPathEncoding
|
keepPathEncoding: keepPathEncoding,
|
||||||
|
minimal: minimal
|
||||||
) else {
|
) else {
|
||||||
return (nil, ChannelProbe(path: path, result: "Adresse nicht baubar"))
|
return (nil, ChannelProbe(path: path, result: "Adresse nicht baubar"))
|
||||||
}
|
}
|
||||||
@@ -849,8 +875,16 @@ extension PlexServer {
|
|||||||
let outcome = await probeStream(url: url)
|
let outcome = await probeStream(url: url)
|
||||||
let label = "\(path) (mediaIndex \(mediaIndex)"
|
let label = "\(path) (mediaIndex \(mediaIndex)"
|
||||||
+ "\(includeProfile ? "" : ", ohne Profil")"
|
+ "\(includeProfile ? "" : ", ohne Profil")"
|
||||||
+ "\(keepPathEncoding ? "" : ", doppelt kodiert"))"
|
+ "\(keepPathEncoding ? "" : ", doppelt kodiert")"
|
||||||
return (outcome.hasPrefix("HTTP 200") ? url : nil, ChannelProbe(path: label, result: outcome))
|
+ "\(minimal ? ", nur das Nötigste" : "") )"
|
||||||
|
|
||||||
|
// Die gebaute Adresse mit in die Spur. Ohne sie diskutiert man über
|
||||||
|
// eine Anfrage, die niemand gesehen hat — und genau das habe ich
|
||||||
|
// mehrere Runden lang getan.
|
||||||
|
let detail = outcome.hasPrefix("HTTP 200")
|
||||||
|
? outcome
|
||||||
|
: "\(outcome)\n \(redacted(url.absoluteString))"
|
||||||
|
return (outcome.hasPrefix("HTTP 200") ? url : nil, ChannelProbe(path: label, result: detail))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Welche Adresse mit welchem Index umzurechnen ist.
|
/// Welche Adresse mit welchem Index umzurechnen ist.
|
||||||
@@ -1320,7 +1354,7 @@ extension PlexServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private nonisolated func redacted(_ text: String) -> String {
|
nonisolated func redacted(_ text: String) -> String {
|
||||||
text.replacingOccurrences(of: token, with: "«token»")
|
text.replacingOccurrences(of: token, with: "«token»")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ extension PlexServer {
|
|||||||
public nonisolated func transcodeURL(
|
public nonisolated func transcodeURL(
|
||||||
for request: TranscodeRequest,
|
for request: TranscodeRequest,
|
||||||
includeProfile: Bool = true,
|
includeProfile: Bool = true,
|
||||||
keepPathEncoding: Bool = true
|
keepPathEncoding: Bool = true,
|
||||||
|
minimal: Bool = false
|
||||||
) -> URL? {
|
) -> URL? {
|
||||||
guard let baseURL = connection.url,
|
guard let baseURL = connection.url,
|
||||||
var components = URLComponents(
|
var components = URLComponents(
|
||||||
@@ -32,9 +33,24 @@ extension PlexServer {
|
|||||||
)
|
)
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
let items = request.queryItems + [
|
// Das Nötigste, um herauszufinden, an welcher Angabe der Server
|
||||||
URLQueryItem(name: "X-Plex-Token", value: token),
|
// Anstoß nimmt. Eine nackte 400 sagt nicht, welcher Parameter ihr
|
||||||
] + (includeProfile ? clientProfileItems : []) + identity.queryItems
|
// nicht passt — halbieren sagt es.
|
||||||
|
let items = minimal
|
||||||
|
? [
|
||||||
|
URLQueryItem(name: "path", value: request.percentEncodedPath),
|
||||||
|
URLQueryItem(name: "protocol", value: "hls"),
|
||||||
|
URLQueryItem(name: "hasMDE", value: "1"),
|
||||||
|
URLQueryItem(name: "mediaIndex", value: String(request.mediaIndex)),
|
||||||
|
URLQueryItem(name: "partIndex", value: String(request.partIndex)),
|
||||||
|
URLQueryItem(name: "session", value: request.sessionIdentifier),
|
||||||
|
URLQueryItem(name: "X-Plex-Session-Identifier", value: request.sessionIdentifier),
|
||||||
|
URLQueryItem(name: "X-Plex-Token", value: token),
|
||||||
|
URLQueryItem(name: "X-Plex-Client-Identifier", value: identity.clientIdentifier),
|
||||||
|
]
|
||||||
|
: request.queryItems + [
|
||||||
|
URLQueryItem(name: "X-Plex-Token", value: token),
|
||||||
|
] + (includeProfile ? clientProfileItems : []) + identity.queryItems
|
||||||
components.queryItems = items
|
components.queryItems = items
|
||||||
|
|
||||||
components.percentEncodedQueryItems = components.percentEncodedQueryItems?.map { item in
|
components.percentEncodedQueryItems = components.percentEncodedQueryItems?.map { item in
|
||||||
|
|||||||
Reference in New Issue
Block a user