mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Improve seek gesture
This commit is contained in:
@@ -16,6 +16,7 @@ final class AVPlayerBackend: PlayerBackend {
|
||||
var controls: PlayerControlsModel!
|
||||
var playerTime: PlayerTimeModel!
|
||||
var networkState: NetworkStateModel!
|
||||
var seek: SeekModel!
|
||||
|
||||
var stream: Stream?
|
||||
var video: Video?
|
||||
@@ -145,7 +146,7 @@ final class AVPlayerBackend: PlayerBackend {
|
||||
avPlayer.replaceCurrentItem(with: nil)
|
||||
}
|
||||
|
||||
func seek(to time: CMTime, seekType _: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)?) {
|
||||
func seek(to time: CMTime, seekType _: SeekType, completionHandler: ((Bool) -> Void)?) {
|
||||
guard !model.live else { return }
|
||||
|
||||
avPlayer.seek(
|
||||
|
@@ -17,6 +17,7 @@ final class MPVBackend: PlayerBackend {
|
||||
var controls: PlayerControlsModel!
|
||||
var playerTime: PlayerTimeModel!
|
||||
var networkState: NetworkStateModel!
|
||||
var seek: SeekModel!
|
||||
|
||||
var stream: Stream?
|
||||
var video: Video?
|
||||
@@ -299,7 +300,7 @@ final class MPVBackend: PlayerBackend {
|
||||
client?.stop()
|
||||
}
|
||||
|
||||
func seek(to time: CMTime, seekType _: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)?) {
|
||||
func seek(to time: CMTime, seekType _: SeekType, completionHandler: ((Bool) -> Void)?) {
|
||||
client?.seek(to: time) { [weak self] _ in
|
||||
self?.getTimeUpdates()
|
||||
self?.updateControls()
|
||||
|
@@ -9,6 +9,7 @@ protocol PlayerBackend {
|
||||
var model: PlayerModel! { get set }
|
||||
var controls: PlayerControlsModel! { get set }
|
||||
var playerTime: PlayerTimeModel! { get set }
|
||||
var seek: SeekModel! { get set }
|
||||
var networkState: NetworkStateModel! { get set }
|
||||
|
||||
var stream: Stream? { get set }
|
||||
@@ -41,8 +42,8 @@ protocol PlayerBackend {
|
||||
|
||||
func stop()
|
||||
|
||||
func seek(to time: CMTime, seekType: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)?)
|
||||
func seek(to seconds: Double, seekType: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)?)
|
||||
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)?)
|
||||
func seek(to seconds: Double, seekType: SeekType, completionHandler: ((Bool) -> Void)?)
|
||||
|
||||
func setRate(_ rate: Float)
|
||||
|
||||
@@ -67,21 +68,21 @@ protocol PlayerBackend {
|
||||
}
|
||||
|
||||
extension PlayerBackend {
|
||||
func seek(to time: CMTime, seekType: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
playerTime.registerSeek(at: time, type: seekType, restore: currentTime)
|
||||
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
||||
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func seek(to seconds: Double, seekType: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
func seek(to seconds: Double, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
let seconds = CMTime.secondsInDefaultTimescale(seconds)
|
||||
playerTime.registerSeek(at: seconds, type: seekType, restore: currentTime)
|
||||
seek.registerSeek(at: seconds, type: seekType, restore: currentTime)
|
||||
seek(to: seconds, seekType: seekType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func seek(relative time: CMTime, seekType: PlayerTimeModel.SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
func seek(relative time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
if let currentTime = currentTime, let duration = playerItemDuration {
|
||||
let seekTime = min(max(0, currentTime.seconds + time.seconds), duration.seconds)
|
||||
playerTime.registerSeek(at: .secondsInDefaultTimescale(seekTime), type: seekType, restore: currentTime)
|
||||
seek.registerSeek(at: .secondsInDefaultTimescale(seekTime), type: seekType, restore: currentTime)
|
||||
seek(to: seekTime, seekType: seekType, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
@@ -91,14 +91,14 @@ final class PlayerModel: ObservableObject {
|
||||
@Published var stream: Stream?
|
||||
@Published var currentRate: Float = 1.0 { didSet { backend.setRate(currentRate) } }
|
||||
|
||||
@Published var qualityProfileSelection: QualityProfile? { didSet { handleQualityProfileChange() }}
|
||||
@Published var qualityProfileSelection: QualityProfile? { didSet { handleQualityProfileChange() } }
|
||||
|
||||
@Published var availableStreams = [Stream]() { didSet { handleAvailableStreamsChange() } }
|
||||
@Published var streamSelection: Stream? { didSet { rebuildTVMenu() } }
|
||||
|
||||
@Published var queue = [PlayerQueueItem]() { didSet { handleQueueChange() } }
|
||||
@Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } }
|
||||
@Published var videoBeingOpened: Video? { didSet { playerTime.reset() } }
|
||||
@Published var videoBeingOpened: Video? { didSet { seek.reset() } }
|
||||
@Published var historyVideos = [Video]()
|
||||
|
||||
@Published var preservedTime: CMTime?
|
||||
@@ -148,6 +148,13 @@ final class PlayerModel: ObservableObject {
|
||||
backend.networkState.player = self
|
||||
}
|
||||
}}
|
||||
var seek: SeekModel { didSet {
|
||||
backends.forEach { backend in
|
||||
var backend = backend
|
||||
backend.seek = seek
|
||||
backend.seek.player = self
|
||||
}
|
||||
}}
|
||||
var navigation: NavigationModel
|
||||
|
||||
var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext
|
||||
@@ -193,7 +200,8 @@ final class PlayerModel: ObservableObject {
|
||||
controls: PlayerControlsModel = PlayerControlsModel(),
|
||||
navigation: NavigationModel = NavigationModel(),
|
||||
playerTime: PlayerTimeModel = PlayerTimeModel(),
|
||||
networkState: NetworkStateModel = NetworkStateModel()
|
||||
networkState: NetworkStateModel = NetworkStateModel(),
|
||||
seek: SeekModel = SeekModel()
|
||||
) {
|
||||
self.accounts = accounts
|
||||
self.comments = comments
|
||||
@@ -201,6 +209,7 @@ final class PlayerModel: ObservableObject {
|
||||
self.navigation = navigation
|
||||
self.playerTime = playerTime
|
||||
self.networkState = networkState
|
||||
self.seek = seek
|
||||
|
||||
self.avPlayerBackend = AVPlayerBackend(
|
||||
model: self,
|
||||
@@ -244,7 +253,7 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
#endif
|
||||
|
||||
if !presentingPlayer { presentingPlayer = true }
|
||||
presentingPlayer = true
|
||||
|
||||
#if os(macOS)
|
||||
Windows.player.open()
|
||||
|
@@ -3,32 +3,11 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class PlayerTimeModel: ObservableObject {
|
||||
enum SeekType: Equatable {
|
||||
case segmentSkip(String)
|
||||
case segmentRestore
|
||||
case userInteracted
|
||||
case loopRestart
|
||||
case backendSync
|
||||
|
||||
var presentable: Bool {
|
||||
self != .backendSync
|
||||
}
|
||||
}
|
||||
|
||||
static let timePlaceholder = "--:--"
|
||||
|
||||
@Published var currentTime = CMTime.zero
|
||||
@Published var duration = CMTime.zero
|
||||
|
||||
@Published var lastSeekTime: CMTime?
|
||||
@Published var lastSeekType: SeekType?
|
||||
@Published var restoreSeekTime: CMTime?
|
||||
|
||||
@Published var gestureSeek = 0.0
|
||||
@Published var gestureStart = 0.0
|
||||
|
||||
@Published var seekOSDDismissed = true
|
||||
|
||||
var player: PlayerModel!
|
||||
|
||||
var forceHours: Bool {
|
||||
@@ -55,70 +34,4 @@ final class PlayerTimeModel: ObservableObject {
|
||||
guard let withoutSegmentsDuration = player?.playerItemDurationWithoutSponsorSegments?.seconds else { return Self.timePlaceholder }
|
||||
return withoutSegmentsDuration.formattedAsPlaybackTime(forceHours: forceHours) ?? Self.timePlaceholder
|
||||
}
|
||||
|
||||
var lastSeekPlaybackTime: String {
|
||||
guard let time = lastSeekTime else { return 0.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? Self.timePlaceholder }
|
||||
return time.seconds.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? Self.timePlaceholder
|
||||
}
|
||||
|
||||
var restoreSeekPlaybackTime: String {
|
||||
guard let time = restoreSeekTime else { return Self.timePlaceholder }
|
||||
return time.seconds.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? Self.timePlaceholder
|
||||
}
|
||||
|
||||
var gestureSeekDestinationTime: Double {
|
||||
min(duration.seconds, max(0, gestureStart + gestureSeek))
|
||||
}
|
||||
|
||||
var gestureSeekDestinationPlaybackTime: String {
|
||||
guard gestureSeek != 0 else { return Self.timePlaceholder }
|
||||
return gestureSeekDestinationTime.formattedAsPlaybackTime(allowZero: true, forceHours: forceHours) ?? Self.timePlaceholder
|
||||
}
|
||||
|
||||
func onSeekGestureStart(completionHandler: (() -> Void)? = nil) {
|
||||
player.backend.getTimeUpdates()
|
||||
player.backend.updateControls {
|
||||
self.gestureStart = self.currentTime.seconds
|
||||
completionHandler?()
|
||||
}
|
||||
}
|
||||
|
||||
func onSeekGestureEnd() {
|
||||
player.backend.updateControls()
|
||||
player.backend.seek(to: gestureSeekDestinationTime, seekType: .userInteracted)
|
||||
}
|
||||
|
||||
func registerSeek(at time: CMTime, type: SeekType, restore restoreTime: CMTime? = nil) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
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 = 0
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user