iPad meldete sich nie an Die Ursache ist unangenehm speziell: Wer im Browser schon bei Plex angemeldet ist, wird von app.plex.tv/auth sofort auf watch.plex.tv weitergeschickt — OHNE dass der Code verknüpft wird. Man sieht sich angemeldet, die App wartet vergeblich, und es gibt keinerlei Hinweis. Jetzt wird plex.tv/link mit eingetragenem Code geöffnet. Diese Seite tut nur eine Sache: den Code bestätigen. Die Gerätekennung steckt ohnehin schon im PIN. Dazu ein Ausweg: bleibt es nach fünfzehn Sekunden ohne Token, erscheint der Code im Anmeldebildschirm zum Abtippen. Schließt man das Fenster ohne Erfolg, bleibt er stehen statt abzumelden — er gilt weiter. Download fragte nicht nach der Qualität Jetzt Original, Full HD, HD oder Sparsam, jeweils mit geschätzter Größe. Die Größe ist das eigentliche Entscheidungskriterium — „Full HD" allein sagt nicht, ob es aufs Telefon passt. Die komprimierten Stufen laufen über den Universal-Transcoder und sind weiterhin NICHT am Server geprüft; steht so im Quelltext. Schwarzes Bild ohne Anzeige Der Player kannte nur „spielt" und „spielt nicht". Wartete er auf Daten, stand man vor einem Standbild ohne Hinweis. Es gibt jetzt einen eigenen Pufferzustand mit Anzeige. Aufmacher war beschnitten Er stand auf 3:4, Plex liefert als `art` aber 16:9 — mit scaledToFill ging links und rechts je ein Viertel verloren, ohne dass man es dem Bild ansah. Die Seitenverhältnisse liegen jetzt an einer Stelle in FlixyAspectRatio. Schwarze Leiste über dem Aufmacher .ultraThinMaterial statt undurchsichtigem Grund: das Bild läuft unscharf hinter der Leiste weiter, statt an einer harten Kante zu enden. Weichgezeichnet, nicht abgedunkelt. 139 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
5.0 KiB
Swift
116 lines
5.0 KiB
Swift
import Foundation
|
|
|
|
/// Der Anmeldecode aus dem PIN-Verfahren.
|
|
///
|
|
/// Plex legt auf Anfrage einen vierstelligen Code an. Der Nutzer bestätigt ihn
|
|
/// im Browser bei plex.tv, danach trägt derselbe Datensatz den Token. Die App
|
|
/// selbst bekommt nie Benutzername oder Passwort zu sehen — deshalb ist dieser
|
|
/// Weg dem manuellen Eintippen eines Tokens vorzuziehen.
|
|
public struct PlexPin: Sendable, Equatable, Decodable {
|
|
public let id: Int
|
|
public let code: String
|
|
public let authToken: String?
|
|
public let expiresAt: Date?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id, code, authToken, expiresAt
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.id = try values.decode(Int.self, forKey: .id)
|
|
self.code = try values.decode(String.self, forKey: .code)
|
|
self.authToken = try values.decodeIfPresent(String.self, forKey: .authToken)
|
|
|
|
// Plex liefert ISO-8601 mit Zeitzone. Ein eigener Formatierer statt
|
|
// einer Decoder-Strategie, weil sonst alle anderen Datumsfelder in
|
|
// dieser Antwort mitbetroffen wären.
|
|
if let text = try values.decodeIfPresent(String.self, forKey: .expiresAt) {
|
|
self.expiresAt = ISO8601DateFormatter().date(from: text)
|
|
} else {
|
|
self.expiresAt = nil
|
|
}
|
|
}
|
|
|
|
public init(id: Int, code: String, authToken: String? = nil, expiresAt: Date? = nil) {
|
|
self.id = id
|
|
self.code = code
|
|
self.authToken = authToken
|
|
self.expiresAt = expiresAt
|
|
}
|
|
|
|
/// Die Verknüpfungsseite von Plex, mit bereits eingetragenem Code.
|
|
///
|
|
/// Gegenüber `app.plex.tv/auth` der verlässlichere Weg — und zwar genau in
|
|
/// dem Fall, der sonst stillschweigend scheitert: Wer im Browser schon bei
|
|
/// Plex angemeldet ist, wird von der Auth-Seite sofort auf watch.plex.tv
|
|
/// weitergeschickt, **ohne dass der Code verknüpft wird**. Man sieht sich
|
|
/// angemeldet, die App wartet vergeblich.
|
|
///
|
|
/// Diese Seite tut nur eine Sache: den Code bestätigen. Die Kennung des
|
|
/// Geräts steckt bereits im PIN — sie kam beim Anlegen über die
|
|
/// `X-Plex-*`-Kopfzeilen mit.
|
|
public var linkURL: URL? {
|
|
let allowed = CharacterSet.alphanumerics
|
|
guard let code = code.addingPercentEncoding(withAllowedCharacters: allowed) else {
|
|
return nil
|
|
}
|
|
return URL(string: "https://plex.tv/link/?pin=\(code)")
|
|
}
|
|
|
|
/// Die Adresse, unter der der Nutzer den Code bestätigt.
|
|
///
|
|
/// Von Hand zusammengesetzt statt über `URLComponents`. Der Grund ist die
|
|
/// Form, die Plex verlangt: die Parameter stehen im Fragment (hinter dem
|
|
/// `#`), nicht in der Abfrage, und einer davon heißt buchstäblich
|
|
/// `context[device][product]`. Die eckigen Klammern müssen genau einmal
|
|
/// kodiert sein.
|
|
///
|
|
/// `URLComponents.fragment` kodiert beim Setzen ein zweites Mal: aus `%5B`
|
|
/// wird `%255B`. Plex sieht dann einen Parameter, den es nicht kennt, und
|
|
/// antwortet mit „Diese Anfrage konnte nicht abgeschlossen werden".
|
|
/// Festgehalten in `AuthorizationURLTests`.
|
|
///
|
|
/// `forwardUrl` schickt den Browser nach der Anmeldung auf eine Adresse
|
|
/// mit eigenem Schema zurück. Genau darauf wartet
|
|
/// `ASWebAuthenticationSession`: das Anmeldefenster schließt sich von
|
|
/// selbst, und die App weiß, dass sie den Token abholen kann. Ohne diese
|
|
/// Rückadresse bliebe die Seite offen und der Nutzer müsste raten, wann er
|
|
/// fertig ist.
|
|
public func authorizationURL(
|
|
clientIdentifier: String,
|
|
product: String,
|
|
forwardURL: URL? = nil
|
|
) -> URL? {
|
|
// `&`, `=` und `+` müssen raus: sie trennen Parameter bzw. stehen für
|
|
// ein Leerzeichen. In einem Wert würden sie die Adresse zerreißen.
|
|
let allowed = CharacterSet.urlQueryAllowed.subtracting(CharacterSet(charactersIn: "&=+"))
|
|
func encode(_ value: String) -> String? {
|
|
value.addingPercentEncoding(withAllowedCharacters: allowed)
|
|
}
|
|
|
|
guard let client = encode(clientIdentifier),
|
|
let code = encode(code),
|
|
let product = encode(product)
|
|
else { return nil }
|
|
|
|
var text = """
|
|
https://app.plex.tv/auth#?clientID=\(client)&code=\(code)\
|
|
&context%5Bdevice%5D%5Bproduct%5D=\(product)
|
|
"""
|
|
|
|
// Strenger kodiert als die übrigen Werte: die Rückadresse enthält
|
|
// `:` und `/`, und die lässt `urlQueryAllowed` durch. Als Wert
|
|
// innerhalb einer Adresse, die Plex selbst noch einmal zerlegt, müssen
|
|
// sie kodiert sein — sonst endet der Wert für Plex beim ersten
|
|
// Schrägstrich.
|
|
let unreserved = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~"))
|
|
if let forwardURL,
|
|
let forward = forwardURL.absoluteString.addingPercentEncoding(withAllowedCharacters: unreserved) {
|
|
text += "&forwardUrl=\(forward)"
|
|
}
|
|
|
|
return URL(string: text)
|
|
}
|
|
}
|