Player: beim Umstellen sah man den Filmanfang statt seiner Stelle

Ein frisch angeforderter Stream beginnt vorn, puffert und springt erst
danach an die Stelle, an der man war. Zehn Sekunden lang lief sichtbar
der Filmanfang — und der Eindruck, die Stelle sei weg und man müsse sie
wiederfinden.

Der Umstieg wird jetzt abgedeckt, solange der Player noch nicht dort ist,
und die Abdeckung nennt die Stelle ausdrücklich. Genau darum geht es:
nicht, dass es schneller wird, sondern dass niemand denkt, er habe etwas
verloren.

Aufgehoben wird sie, sobald der Player innerhalb von zwei Sekunden um die
Stelle liegt. Dazu eine Notbremse nach dreißig Sekunden — springt er nie
dorthin, bliebe sonst für immer eine schwarze Fläche über dem Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-12 22:21:51 +02:00
co-authored by Claude Opus 5
parent 171ba99908
commit 33dfac352a
3 changed files with 2838 additions and 2774 deletions
+21
View File
@@ -57,6 +57,13 @@ final class PlayerModel {
private(set) var quality: PlayerQuality = .automatic
/// Ob gerade auf eine andere Stufe umgestellt wird.
private(set) var isChangingQuality = false
/// Wohin nach dem Umstellen zurückgesprungen wird.
///
/// Solange das gesetzt ist, ist das Bild nicht die Stelle, an der man war:
/// ein frisch angeforderter Stream beginnt vorn, puffert und springt erst
/// danach. Sichtbar war in dieser Zeit der **Filmanfang** — und der
/// Eindruck, die Stelle verloren zu haben.
private(set) var resumeTarget: Duration?
/// Was gerade über die Leitung kommt. Beim Öffnen der Wahl abgefragt.
var statistics: PlaybackStatistics? { engine?.statistics }
@@ -210,6 +217,7 @@ final class PlayerModel {
defer { isChangingQuality = false }
let resumeAt = currentTime
resumeTarget = resumeAt
quality = newQuality
// Den alten Transcoder anhalten, bevor der neue anläuft. Sonst rechnet
@@ -246,6 +254,13 @@ final class PlayerModel {
}
await load(source, decision: newDecision)
// Notbremse: springt der Player nie an die Stelle, bliebe sonst für
// immer eine Abdeckung über dem Bild.
Task { [weak self] in
try? await Task.sleep(for: .seconds(30))
self?.resumeTarget = nil
}
}
/// Wiedergabe aus dem Download-Ordner.
@@ -483,6 +498,7 @@ final class PlayerModel {
/// weggenommen. Was man hörte: der alte Ton lief weiter — auch nachdem
/// die App im Hintergrund war —, und das Neue blieb stumm.
func silence() {
resumeTarget = nil
eventTask?.cancel()
reportTask?.cancel()
@@ -666,6 +682,11 @@ final class PlayerModel {
}
case .timeChanged(let time):
currentTime = time
// Angekommen: ab hier zeigt das Bild wieder die Stelle,
// an der man war.
if let target = resumeTarget, time + .seconds(2) >= target {
resumeTarget = nil
}
updateActiveMarker()
case .durationResolved(let value):
duration = value