mirror of
https://github.com/yattee/yattee.git
synced 2024-11-09 15:58:20 +00:00
157 lines
4.5 KiB
Swift
157 lines
4.5 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
final class SeekModel: ObservableObject {
|
|
static let shared = SeekModel()
|
|
|
|
@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
|
|
|
|
var player: PlayerModel! { .shared }
|
|
|
|
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 {
|
|
guard let gestureSeek, let gestureStart else { return -1 }
|
|
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 }
|
|
|
|
withAnimation(.easeIn(duration: 0.1)) { self.presentingOSD = true }
|
|
}
|
|
|
|
func hideOSD() {
|
|
guard presentingOSD else { return }
|
|
|
|
withAnimation(.easeIn(duration: 0.1)) { self.presentingOSD = false }
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|