mirror of
https://github.com/yattee/yattee.git
synced 2025-08-09 20:24:06 +00:00
Improve seek gesture
This commit is contained in:
160
Model/SeekModel.swift
Normal file
160
Model/SeekModel.swift
Normal file
@@ -0,0 +1,160 @@
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class SeekModel: ObservableObject {
|
||||
@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!
|
||||
|
||||
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 = gestureSeek, let gestureStart = 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()
|
||||
}
|
||||
|
||||
//
|
||||
// player.backend.updateControls {
|
||||
// self.gestureStart = self.currentTime.seconds
|
||||
// completionHandler?()
|
||||
// }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user