2022-08-29 11:55:23 +00:00
|
|
|
import AVFoundation
|
|
|
|
import Foundation
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
final class SeekModel: ObservableObject {
|
2022-09-01 17:00:56 +00:00
|
|
|
static let shared = SeekModel()
|
2022-09-01 18:01:01 +00:00
|
|
|
|
2022-08-29 11:55:23 +00:00
|
|
|
@Published var currentTime = CMTime.zero
|
|
|
|
@Published var duration = CMTime.zero
|
|
|
|
|
|
|
|
@Published var lastSeekTime: CMTime? { didSet { onSeek() } }
|
|
|
|
@Published var lastSeekType: SeekType?
|
|
|
|
@Published var restoreSeekTime: CMTime?
|
|
|
|
|
|
|
|
@Published var gestureSeek: Double?
|
|
|
|
@Published var gestureStart: Double?
|
|
|
|
|
|
|
|
@Published var presentingOSD = false
|
|
|
|
|
2022-09-01 23:05:31 +00:00
|
|
|
var player: PlayerModel! { .shared }
|
2022-08-29 11:55:23 +00:00
|
|
|
|
|
|
|
var dismissTimer: Timer?
|
|
|
|
|
|
|
|
var isSeeking: Bool {
|
|
|
|
gestureSeek != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var progress: Double {
|
|
|
|
let seconds = duration.seconds
|
|
|
|
guard seconds.isFinite, seconds > 0 else { return 0 }
|
|
|
|
|
|
|
|
if isSeeking {
|
|
|
|
return gestureSeekDestinationTime / seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let seekTime = lastSeekTime else {
|
|
|
|
return currentTime.seconds / seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
return seekTime.seconds / seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
var lastSeekPlaybackTime: String {
|
|
|
|
guard let time = lastSeekTime else { return 0.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? PlayerTimeModel.timePlaceholder }
|
|
|
|
return time.seconds.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? PlayerTimeModel.timePlaceholder
|
|
|
|
}
|
|
|
|
|
|
|
|
var restoreSeekPlaybackTime: String {
|
|
|
|
guard let time = restoreSeekTime else { return PlayerTimeModel.timePlaceholder }
|
|
|
|
return time.seconds.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? PlayerTimeModel.timePlaceholder
|
|
|
|
}
|
|
|
|
|
|
|
|
var gestureSeekDestinationTime: Double {
|
2022-09-28 14:27:01 +00:00
|
|
|
guard let gestureSeek, let gestureStart else { return -1 }
|
2022-08-29 11:55:23 +00:00
|
|
|
return min(duration.seconds, max(0, gestureStart + gestureSeek))
|
|
|
|
}
|
|
|
|
|
|
|
|
var gestureSeekDestinationPlaybackTime: String {
|
|
|
|
guard gestureSeek != 0 else { return PlayerTimeModel.timePlaceholder }
|
|
|
|
return gestureSeekDestinationTime.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? PlayerTimeModel.timePlaceholder
|
|
|
|
}
|
|
|
|
|
|
|
|
var durationPlaybackTime: String {
|
|
|
|
if player?.currentItem.isNil ?? true {
|
|
|
|
return PlayerTimeModel.timePlaceholder
|
|
|
|
}
|
|
|
|
|
|
|
|
return duration.seconds.formattedAsPlaybackTime() ?? PlayerTimeModel.timePlaceholder
|
|
|
|
}
|
|
|
|
|
|
|
|
func showOSD() {
|
|
|
|
guard !presentingOSD else { return }
|
|
|
|
|
2024-04-23 20:08:08 +00:00
|
|
|
presentingOSD = true
|
2022-08-29 11:55:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func hideOSD() {
|
|
|
|
guard presentingOSD else { return }
|
|
|
|
|
2024-04-23 20:08:08 +00:00
|
|
|
presentingOSD = false
|
2022-08-29 11:55:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func hideOSDWithDelay() {
|
|
|
|
dismissTimer?.invalidate()
|
|
|
|
dismissTimer = Delay.by(3) { self.hideOSD() }
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateCurrentTime(completionHandler: (() -> Void?)? = nil) {
|
|
|
|
player.backend.getTimeUpdates()
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.currentTime = self.player.backend.currentTime ?? .zero
|
|
|
|
self.duration = self.player.backend.playerItemDuration ?? .zero
|
|
|
|
completionHandler?()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func onSeekGestureStart() {
|
|
|
|
updateCurrentTime {
|
|
|
|
self.gestureStart = self.currentTime.seconds
|
|
|
|
self.dismissTimer?.invalidate()
|
|
|
|
self.showOSD()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func onSeekGestureEnd() {
|
|
|
|
dismissTimer?.invalidate()
|
|
|
|
dismissTimer = Delay.by(3) { self.hideOSD() }
|
|
|
|
player.backend.seek(to: gestureSeekDestinationTime, seekType: .userInteracted)
|
|
|
|
}
|
|
|
|
|
|
|
|
func onSeek() {
|
|
|
|
guard !lastSeekTime.isNil else { return }
|
|
|
|
gestureSeek = nil
|
|
|
|
gestureStart = nil
|
|
|
|
showOSD()
|
|
|
|
hideOSDWithDelay()
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerSeek(at time: CMTime, type: SeekType, restore restoreTime: CMTime? = nil) {
|
|
|
|
updateCurrentTime {
|
|
|
|
withAnimation {
|
|
|
|
self.lastSeekTime = time
|
|
|
|
self.lastSeekType = type
|
|
|
|
self.restoreSeekTime = restoreTime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func restoreTime() {
|
|
|
|
guard let time = restoreSeekTime else { return }
|
|
|
|
switch lastSeekType {
|
|
|
|
case .segmentSkip:
|
|
|
|
player.restoreLastSkippedSegment()
|
|
|
|
default:
|
|
|
|
player.backend.seek(to: time, seekType: .userInteracted)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resetSeek() {
|
|
|
|
withAnimation {
|
|
|
|
lastSeekTime = nil
|
|
|
|
lastSeekType = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func reset() {
|
|
|
|
currentTime = .zero
|
|
|
|
duration = .zero
|
|
|
|
resetSeek()
|
|
|
|
gestureSeek = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var forceHours: Bool {
|
|
|
|
duration.seconds >= 60 * 60
|
|
|
|
}
|
|
|
|
}
|