Vorrat anlegen, und VLC meldet endlich das Stocken
Zwei Änderungen, die an der Wiedergabe selbst etwas tun — beide ohne Abhängigkeit davon, ob die Server je eine Leiter liefern. **Vorrat.** Ohne Angabe legt AVPlayer so viel an, wie es für einen zügigen Start braucht, und keinen Meter mehr. Das ist richtig, solange die Leitung trägt, und falsch, sobald sie aussetzt: ein Loch von fünf Sekunden trifft dann auf einen Puffer von fünf Sekunden, und das Bild steht. Fünfundzwanzig Sekunden sind ein Anfangswert, kein Messergebnis. Nur für Filme und Folgen über das Netz. **Nicht für Sender** — dort hieße ein halbminütiger Vorrat eine halbe Minute Verzug und ein zähes Umschalten. Und nicht für Dateien von der Platte, die brauchen keinen. Wer das entscheidet, muss mehr wissen als der Motor, deshalb reist die Zahl mit der Quelle statt dass die Engine rät. **VLC hat das Stocken nie gemeldet.** `VLCMediaPlayerState` kennt keinen Wert dafür — die Aufzählung führt nur `opening`, `playing`, `paused`, `stopped`, `error`. Der Puffer-Fortschritt kommt über `mediaPlayerBufferingChanged:`, und die Methode fehlte. Folge: `.buffering` ging dort **nie** an, der Wachhund im Player löste nie aus, und ein hängender Strom lief stumm ins Nichts — ohne Kreisel, ohne Meldung, ohne Abbruch. Ausgerechnet auf dem Weg, der die großen Remuxe spielt, bei denen eine schwache Leitung am ehesten scheitert. Gewertet wird nur aus dem Spielen heraus: laut Dokumentation kommt vor jeder Wiedergabe einmal 0,0 und einmal 1,0, und dieses anfängliche Füllen ist der Start, kein Rückler. Dazu meldet VLC jetzt überhaupt Statistik. `variantCount` bleibt dabei `nil` und nicht `1` — eine Datei hat keine Leiter, und „gibt es nicht" ist etwas anderes als „gibt es einmal". Bei der Bitrate steht die Annahme im Kommentar statt im Nebel: libvlc führt `inputBitrate` vermutlich in Byte je Sekunde, daher der Faktor acht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
afab93f53a
commit
baa542690c
@@ -94,6 +94,24 @@ public final class AVPlayerEngine: VideoPlayerEngine {
|
||||
AVURLAssetPreferPreciseDurationAndTimingKey: false,
|
||||
])
|
||||
let item = AVPlayerItem(asset: asset)
|
||||
|
||||
// **Vorrat statt Reaktion.**
|
||||
//
|
||||
// Ohne diese Angabe legt AVPlayer so viel an, wie es für einen
|
||||
// zügigen Start braucht — und keinen Meter mehr. Das ist richtig, wenn
|
||||
// die Leitung trägt, und falsch, wenn sie aussetzt: ein Loch von
|
||||
// fünf Sekunden trifft dann auf einen Puffer von fünf Sekunden, und
|
||||
// das Bild steht.
|
||||
//
|
||||
// Der Preis ist ehrlich: der Start dauert länger, und wer springt,
|
||||
// wirft den angelegten Vorrat weg. Beides ist weniger schlimm als ein
|
||||
// Rückler mitten im Satz.
|
||||
if let vorrat = source.forwardBuffer {
|
||||
item.preferredForwardBufferDuration = TimeInterval(
|
||||
Double(vorrat.components.seconds)
|
||||
)
|
||||
}
|
||||
|
||||
player.replaceCurrentItem(with: item)
|
||||
|
||||
observe(item: item)
|
||||
|
||||
@@ -14,11 +14,29 @@ public struct PlaybackSource: Sendable, Equatable {
|
||||
/// Ab wo gestartet werden soll. Kommt aus dem zuletzt gemeldeten
|
||||
/// Fortschritt; `.zero` heißt von vorn.
|
||||
public let startTime: Duration
|
||||
/// Wie viel Vorrat der Player anlegen soll.
|
||||
///
|
||||
/// `nil` überlässt es dem System. Das ist für eine Datei auf der Platte
|
||||
/// richtig und für einen Sender richtig — bei einem Strom über eine
|
||||
/// wackelige Leitung ist es zu wenig: der Systemwert ist auf schnellen
|
||||
/// Start ausgelegt, nicht darauf, einen Aussetzer zu überstehen.
|
||||
///
|
||||
/// **Wer das entscheidet, muss mehr wissen als der Motor.** Ob es ein
|
||||
/// Sender ist, ob die Datei lokal liegt — das weiß der Player, nicht die
|
||||
/// Engine. Deshalb reist die Zahl mit der Quelle, statt dass die Engine
|
||||
/// rät.
|
||||
public let forwardBuffer: Duration?
|
||||
|
||||
public init(url: URL, origin: Origin, startTime: Duration = .zero) {
|
||||
public init(
|
||||
url: URL,
|
||||
origin: Origin,
|
||||
startTime: Duration = .zero,
|
||||
forwardBuffer: Duration? = nil
|
||||
) {
|
||||
self.url = url
|
||||
self.origin = origin
|
||||
self.startTime = startTime
|
||||
self.forwardBuffer = forwardBuffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,40 @@ public final class VLCPlayerEngine: NSObject, VideoPlayerEngine {
|
||||
|
||||
private let player = VLCMediaPlayer()
|
||||
|
||||
/// Was VLC über den laufenden Strom weiß.
|
||||
///
|
||||
/// Bisher meldete dieser Weg gar nichts: die Standardimplementierung gibt
|
||||
/// `nil` zurück, und die Bitratenanzeige blieb bei VLC dauerhaft leer.
|
||||
///
|
||||
/// **Keine Leiter, und das ist die richtige Aussage.** VLC spielt hier
|
||||
/// eine Datei, keine Playlist — es gibt keine Stufen, zwischen denen
|
||||
/// etwas wechseln könnte. `variantCount` bleibt deshalb `nil` und nicht
|
||||
/// etwa `1`: „gibt es nicht" ist etwas anderes als „gibt es einmal".
|
||||
///
|
||||
/// **Zur Bitrate, ehrlich:** libvlc führt `inputBitrate` als Byte je
|
||||
/// Sekunde, deshalb der Faktor acht. Das ist der wahrscheinliche, aber
|
||||
/// nicht belegte Fall — wer es prüfen will, hält den Wert neben die
|
||||
/// bekannte Bitrate der Datei; liegt er um Faktor acht daneben, steht
|
||||
/// hier das Falsche.
|
||||
public var statistics: PlaybackStatistics? {
|
||||
guard let media = player.media else { return nil }
|
||||
let werte = media.statistics
|
||||
|
||||
let kilobit = Int((werte.inputBitrate * 8 / 1_000).rounded())
|
||||
let größe = player.videoSize
|
||||
|
||||
return PlaybackStatistics(
|
||||
observedBitrate: kilobit > 0 ? kilobit : nil,
|
||||
indicatedBitrate: nil,
|
||||
width: größe.width > 0 ? Int(größe.width) : nil,
|
||||
height: größe.height > 0 ? Int(größe.height) : nil,
|
||||
// Nur die wirklich verworfenen. `latePictures` sind zu spät
|
||||
// gekommen, aber angezeigt worden — sie unter „verworfen" zu
|
||||
// führen machte die Zahl größer und die Aussage falsch.
|
||||
droppedFrames: Int(werte.lostPictures)
|
||||
)
|
||||
}
|
||||
|
||||
public private(set) var state: PlaybackState = .idle {
|
||||
didSet {
|
||||
guard state != oldValue else { return }
|
||||
@@ -186,6 +220,30 @@ extension VLCPlayerEngine: VLCMediaPlayerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Der Puffer-Fortschritt — **der einzige Weg, auf dem VLC das Stocken
|
||||
/// überhaupt meldet.**
|
||||
///
|
||||
/// `VLCMediaPlayerState` kennt keinen Wert für „wartet auf Daten": die
|
||||
/// Aufzählung führt nur `opening`, `playing`, `paused`, `stopped` und
|
||||
/// `error`. Weil diese Methode bisher fehlte, ging der Zustand
|
||||
/// `.buffering` auf dem VLC-Weg **nie** an — und damit löste der
|
||||
/// Wachhund im Player dort nie aus. Ein hängender Strom lief stumm ins
|
||||
/// Nichts, ohne Kreisel, ohne Meldung, ohne Abbruch.
|
||||
///
|
||||
/// Nur aus dem Spielen heraus gewertet. Die Dokumentation sagt, dass vor
|
||||
/// jeder erfolgreichen Wiedergabe einmal 0,0 und einmal 1,0 kommt —
|
||||
/// dieses anfängliche Füllen ist der Start und kein Rückler, und es
|
||||
/// mitzuzählen gäbe jeder Wiedergabe eine Vorstrafe.
|
||||
public nonisolated func mediaPlayerBufferingChanged(_ progress: Float) {
|
||||
Task { @MainActor in
|
||||
if progress >= 1 {
|
||||
if state == .buffering { state = .playing }
|
||||
} else if state == .playing {
|
||||
state = .buffering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated func mediaPlayerTimeChanged(_ aNotification: Notification) {
|
||||
Task { @MainActor in
|
||||
continuation?.yield(.timeChanged(currentTime))
|
||||
|
||||
Reference in New Issue
Block a user