Improve seek gesture

This commit is contained in:
Arkadiusz Fal
2022-08-29 13:55:23 +02:00
parent d5f8ad4eec
commit e444dc3c79
14 changed files with 238 additions and 158 deletions

View File

@@ -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(

View File

@@ -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()

View File

@@ -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)
}
}

View File

@@ -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()

View File

@@ -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
}
}