Bisher gab es über eine laufende Wiedergabe zwei Zahlen — gemessene Bitrate und verworfene Bilder —, und die standen nur zur Anzeige da. Die Frage „trägt die Leitung das gerade" war nicht zu beantworten, weder für den Nutzer noch für die App. **Die wichtigste neue Zahl ist `variantCount`.** Sie kommt aus `AVAsset.variants`, also vom Parser des Players, und beantwortet die Frage, ob es überhaupt etwas zum Umschalten gibt. Steht dort 1, greift jedes Nachregeln ins Leere — und genau das ist heute der Fall, weil die App bei beiden Servern eine Playlist mit einer Stufe bestellt. Dazu der Pufferstand aus `loadedTimeRanges`: der einzige echte Frühwarner. Schrumpft er schneller, als die Zeit vergeht, kommt weniger herein als hinausgeht — zehn bis zwanzig Sekunden, bevor das Bild steht. Alles andere meldet sich erst danach. **Verworfene Bilder werden jetzt über alle Protokolleinträge summiert.** Das Zugriffsprotokoll legt bei jedem Stufenwechsel einen neuen an, dessen Zähler bei null beginnt; wer nur `last` liest, hält eine Wiedergabe für makellos, die schon zwanzigmal Bilder verworfen hat. **Der Wachhund war blind für den häufigsten Fall.** Vierzig Sekunden am Stück ohne Daten brechen ab — aber er setzte sich bei jedem kurzen Erholen zurück, und zehn Rückler à dreißig Sekunden lösten ihn nie aus. `StallLedger` zählt daneben mit, wie viel Zeit im gleitenden Fenster fürs Warten draufging. Es bricht nichts ab, es stellt nur fest — und ist ein reiner Typ ohne Uhr und ohne AVFoundation, damit sich eine Folge von Rücklern als Zahlenreihe hinschreiben und das Urteil prüfen lässt. Sieben Tests, darunter genau der Fall, an dem der alte Wachhund vorbeisah. Gezählt wird nur der Weg vom Spielen ins Warten. Das anfängliche Puffern ist der Start, kein Rückler; würde es mitgezählt, hätte jede Wiedergabe eine Vorstrafe. Und zwei Kommentare sind berichtigt. Sie behaupteten, AVPlayer wechsle die Bitrate von selbst und das sei „die zugesagte automatische Qualitätsanpassung". Umschalten kann er nur zwischen Varianten, die in der Liste stehen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
2.3 KiB
Swift
63 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
/// Welcher Engine die Wiedergabe übernimmt.
|
|
public enum PlaybackEngineKind: String, Sendable, Equatable, Codable {
|
|
/// Der Systemplayer. Bringt Bild-in-Bild, AirPlay und die Steuerung im
|
|
/// Sperrbildschirm mit — deshalb hat er immer Vorrang, wenn er kann.
|
|
case avPlayer
|
|
/// VLCKit. Spielt praktisch jedes Format, kann dafür kein Bild-in-Bild.
|
|
case vlc
|
|
}
|
|
|
|
/// Wie ein Titel abgespielt werden soll.
|
|
public struct PlaybackDecision: Sendable, Equatable {
|
|
public enum Method: Sendable, Equatable {
|
|
/// Die Datei wird unverändert übertragen und lokal dekodiert.
|
|
case directPlay(engine: PlaybackEngineKind)
|
|
/// Der Server rechnet um. Ab Phase 2; `maximumBitrate` in Kilobit.
|
|
case transcode(maximumBitrate: Int?)
|
|
}
|
|
|
|
/// Warum es so entschieden wurde.
|
|
///
|
|
/// Wandert in die technischen Infos auf der Detailseite und ins Protokoll.
|
|
/// Ohne diese Begründung ist „warum ruckelt das" nicht zu beantworten.
|
|
public enum Rationale: Sendable, Equatable {
|
|
case nativelySupported
|
|
case containerUnsupported(String)
|
|
case videoCodecUnsupported(String)
|
|
case audioCodecUnsupported(String)
|
|
/// Der Server hat die Datei noch nicht analysiert.
|
|
case streamsUnknown
|
|
case bitrateExceedsLimit(mediaBitrate: Int, limit: Int)
|
|
/// Transcoding wäre passender, ist aber (noch) nicht verfügbar.
|
|
case transcodingUnavailable
|
|
/// Im Player ausdrücklich eine Stufe gewählt.
|
|
case qualityChosen
|
|
}
|
|
|
|
public let method: Method
|
|
public let rationale: [Rationale]
|
|
|
|
public init(method: Method, rationale: [Rationale]) {
|
|
self.method = method
|
|
self.rationale = rationale
|
|
}
|
|
|
|
public var engine: PlaybackEngineKind? {
|
|
switch method {
|
|
case .directPlay(let engine): engine
|
|
// Der Transcoder liefert HLS, und damit ist AVPlayer der richtige
|
|
// Motor. **Nicht** aber, wie hier früher stand, „inklusive
|
|
// automatischem Bitratenwechsel": den gibt es nur, wenn die Playlist
|
|
// mehrere Stufen führt, und das muss ausdrücklich bestellt werden.
|
|
case .transcode: .avPlayer
|
|
}
|
|
}
|
|
|
|
public var isDirectPlay: Bool {
|
|
if case .directPlay = method { return true }
|
|
return false
|
|
}
|
|
}
|