feat: remember playback progress across player restarts
Persist authoritative per-video positions in UserDefaults. Save periodic checkpoints, pause, close, replacement, and quit; restore a validated position before autoplay. Canonical YouTube webpage URLs survive fresh stream extraction, and local symlinks share file progress. Clear completed or out-of-duration bookmarks. Leave failed opens and live/non-seekable inputs out of persistence. Pending opens hide errors belonging to the previous source. Verified separate-process restart, source replacement, failed-open recovery, completion, checkpoints, real YouTube short-link resume, live exclusion, and make all check.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import Foundation
|
||||
|
||||
struct PlaybackProgress {
|
||||
private let defaults: UserDefaults
|
||||
private var positions: [String: Double]
|
||||
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
positions = defaults.dictionary(forKey: "playbackPositions") as? [String: Double] ?? [:]
|
||||
}
|
||||
|
||||
func position(for url: URL) -> Double? {
|
||||
guard let position = positions[key(for: url)], position.isFinite, position > 0 else { return nil }
|
||||
return position
|
||||
}
|
||||
|
||||
mutating func save(_ position: Double?, for url: URL) {
|
||||
let key = key(for: url)
|
||||
let position = position.flatMap { $0.isFinite && $0 > 0 ? $0 : nil }
|
||||
guard positions[key] != position else { return }
|
||||
positions[key] = position
|
||||
defaults.set(positions, forKey: "playbackPositions")
|
||||
}
|
||||
|
||||
private func key(for url: URL) -> String {
|
||||
// YouTube callers supply the canonical webpage URL, never an expiring media URL.
|
||||
return url.isFileURL ? url.standardizedFileURL.resolvingSymlinksInPath().absoluteString : url.absoluteString
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user