Diagnose-Bildschirm und Build-Datum
Zweimal in Folge stand „es läuft nicht" gegen „bei mir ist es behoben", ohne dass sich klären ließ, welcher Stand überhaupt auf dem Gerät liegt. Das soll nicht noch einmal passieren. Einstellungen → Diagnose zeigt jetzt: - welcher Server und über welche Verbindung, mit Warnung bei der gedrosselten Plex-Vermittlung - den letzten Abspielversuch: Titel, gewählter Weg, Quelladresse - jeden Schritt mit Zeitabstand zum Start. Der Abstand ist die eigentliche Auskunft: liegt zwischen „Quelle gewählt" und „spielt" eine Minute, hängt es an der Leitung; kommt „spielt" gar nicht, am Motor. - den Zeitpunkt, zu dem der laufende Build entstanden ist Das Build-Datum kommt aus dem Änderungsdatum der eigenen Binärdatei — kein Build-Skript, keine erzeugte Datei, und immer richtig. Der Bericht lässt sich kopieren und enthält keinen Token: die Quelladresse wird ohne Abfrageteil abgelegt, und dort steckt er. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import FlixyPlayback
|
||||
import PlexKit
|
||||
|
||||
/// Hält fest, was beim letzten Abspielversuch passiert ist.
|
||||
///
|
||||
/// Der Player verschwindet, wenn man ihn schließt — und mit ihm jeder Hinweis
|
||||
/// darauf, woran es lag. Dieser Mitschnitt überlebt das und steht in den
|
||||
/// Einstellungen. Aus „es läuft nicht" wird damit „VLC, lokale Verbindung,
|
||||
/// hängt bei Öffnen" — und das ist ein Unterschied, der Stunden spart.
|
||||
@MainActor
|
||||
@Observable
|
||||
final class PlaybackDiagnostics {
|
||||
struct Attempt: Identifiable {
|
||||
let id = UUID()
|
||||
let startedAt: Date
|
||||
var title: String
|
||||
var method: String
|
||||
var connection: String
|
||||
/// Die Adresse ohne Abfrageteil.
|
||||
///
|
||||
/// Der Token steckt in der Abfrage. Er hat in einem Protokoll nichts
|
||||
/// zu suchen — es landet in Screenshots, und wer ihn hat, kommt an die
|
||||
/// ganze Mediathek.
|
||||
var sourceDescription: String
|
||||
var steps: [String] = []
|
||||
var outcome: String?
|
||||
}
|
||||
|
||||
private(set) var lastAttempt: Attempt?
|
||||
|
||||
func begin(title: String, connection: PlexConnection?) {
|
||||
lastAttempt = Attempt(
|
||||
startedAt: Date(),
|
||||
title: title,
|
||||
method: "—",
|
||||
connection: connection.map(Self.describe) ?? "—",
|
||||
sourceDescription: "—"
|
||||
)
|
||||
note("Player geöffnet")
|
||||
}
|
||||
|
||||
func record(decision: PlaybackDecision, source: URL) {
|
||||
lastAttempt?.method = Self.describe(decision)
|
||||
lastAttempt?.sourceDescription = Self.describe(source)
|
||||
note("Quelle gewählt")
|
||||
}
|
||||
|
||||
/// Ein Zwischenschritt, mit Zeitabstand zum Beginn.
|
||||
///
|
||||
/// Der Abstand ist die eigentliche Auskunft: steht zwischen „Quelle
|
||||
/// gewählt" und „spielt" eine Minute, liegt es an der Leitung; kommt
|
||||
/// „spielt" gar nicht, am Motor.
|
||||
func note(_ step: String) {
|
||||
guard var attempt = lastAttempt else { return }
|
||||
let elapsed = Date().timeIntervalSince(attempt.startedAt)
|
||||
attempt.steps.append(String(format: "%.1fs %@", elapsed, step))
|
||||
lastAttempt = attempt
|
||||
}
|
||||
|
||||
func finish(outcome: String) {
|
||||
lastAttempt?.outcome = outcome
|
||||
note(outcome)
|
||||
}
|
||||
|
||||
private static func describe(_ decision: PlaybackDecision) -> String {
|
||||
switch decision.method {
|
||||
case .directPlay(let engine):
|
||||
switch engine {
|
||||
case .avPlayer: "Direkt · AVPlayer"
|
||||
case .vlc: "Direkt · VLC"
|
||||
}
|
||||
case .transcode(let limit):
|
||||
limit.map { "Transcode · max. \($0 / 1000) Mbit" } ?? "Transcode"
|
||||
}
|
||||
}
|
||||
|
||||
private static func describe(_ connection: PlexConnection) -> String {
|
||||
if connection.local { return "Lokal · \(connection.uri)" }
|
||||
if connection.relay { return "Plex-Vermittlung (gedrosselt)" }
|
||||
return "Direkt von außen · \(connection.uri)"
|
||||
}
|
||||
|
||||
private static func describe(_ url: URL) -> String {
|
||||
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
||||
return "—"
|
||||
}
|
||||
components.query = nil
|
||||
return components.string ?? "—"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user