mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-04 18:15:01 +02:00
Rewrite sleep timer logic again
This commit is contained in:
parent
dc8852eb0d
commit
c14f6ec4c2
3 changed files with 111 additions and 129 deletions
|
@ -41,8 +41,9 @@ class AudioPlayer: NSObject {
|
|||
|
||||
// Sleep timer values
|
||||
internal var sleepTimeChapterStopAt: Double?
|
||||
internal var sleepTimeStopAt: Double?
|
||||
internal var sleepTimeToken: Any?
|
||||
internal var sleepTimeChapterToken: Any?
|
||||
internal var sleepTimer: Timer?
|
||||
internal var sleepTimeRemaining: Double?
|
||||
|
||||
private var currentTrackIndex = 0
|
||||
private var allPlayerItems:[AVPlayerItem] = []
|
||||
|
@ -168,8 +169,8 @@ class AudioPlayer: NSObject {
|
|||
await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: isPlaying, isStopping: false)
|
||||
}
|
||||
|
||||
// Update the sleep time, if set
|
||||
if self.sleepTimeStopAt != nil {
|
||||
if self.isSleepTimerSet() {
|
||||
// Update the UI
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
|
||||
}
|
||||
}
|
||||
|
@ -264,12 +265,6 @@ class AudioPlayer: NSObject {
|
|||
return
|
||||
}
|
||||
|
||||
// Capture remaining sleep time before changing the track position
|
||||
let sleepSecondsRemaining = self.getSleepTimeRemaining()
|
||||
|
||||
// Stop the paused timer
|
||||
self.stopPausedTimer()
|
||||
|
||||
// Determine where we are starting playback
|
||||
let lastPlayed = (session.updatedAt ?? 0)/1000
|
||||
let currentTime = allowSeekBack ? calculateSeekBackTimeAtCurrentTime(session.currentTime, lastPlayed: lastPlayed) : session.currentTime
|
||||
|
@ -282,16 +277,7 @@ class AudioPlayer: NSObject {
|
|||
let seekTime = currentTime - currentTrackStartOffset
|
||||
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000), toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] completed in
|
||||
guard completed else { return }
|
||||
guard let self = self else { return }
|
||||
|
||||
// Start playback
|
||||
self.audioPlayer.play()
|
||||
self.rate = self.tmpRate
|
||||
self.audioPlayer.rate = self.tmpRate
|
||||
self.status = 1
|
||||
|
||||
// Update the progress
|
||||
self.updateNowPlaying()
|
||||
self?.resumePlayback()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,9 +306,24 @@ class AudioPlayer: NSObject {
|
|||
return currentTime - time
|
||||
}
|
||||
|
||||
private func resumePlayback() {
|
||||
NSLog("PLAY: Resuming playback")
|
||||
|
||||
// Stop the paused timer
|
||||
self.stopPausedTimer()
|
||||
|
||||
self.audioPlayer.play()
|
||||
self.audioPlayer.rate = self.tmpRate
|
||||
self.status = 1
|
||||
|
||||
// Update the progress
|
||||
self.updateNowPlaying()
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
guard self.isInitialized() else { return }
|
||||
|
||||
NSLog("PAUSE: Pausing playback")
|
||||
self.audioPlayer.pause()
|
||||
|
||||
Task {
|
||||
|
@ -332,7 +333,6 @@ class AudioPlayer: NSObject {
|
|||
}
|
||||
|
||||
self.status = 0
|
||||
self.rate = 0.0
|
||||
|
||||
updateNowPlaying()
|
||||
|
||||
|
@ -342,10 +342,7 @@ class AudioPlayer: NSObject {
|
|||
public func seek(_ to: Double, from: String) {
|
||||
let continuePlaying = rate > 0.0
|
||||
|
||||
// Capture remaining sleep time before changing the track position or pausing
|
||||
let sleepSecondsRemaining = self.getSleepTimeRemaining()
|
||||
|
||||
pause()
|
||||
self.pause()
|
||||
|
||||
NSLog("SEEK: Seek to \(to) from \(from)")
|
||||
|
||||
|
@ -386,22 +383,16 @@ class AudioPlayer: NSObject {
|
|||
guard completed else { return NSLog("SEEK: WARNING: seeking not completed (to \(seekTime)") }
|
||||
guard let self = self else { return }
|
||||
|
||||
// Reschedule the sleep timer
|
||||
if let currentTime = self.getCurrentTime() {
|
||||
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
|
||||
if continuePlaying {
|
||||
self.resumePlayback()
|
||||
}
|
||||
|
||||
if continuePlaying {
|
||||
self.play()
|
||||
}
|
||||
self.updateNowPlaying()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
|
||||
// Capture remaining sleep time before changing the rate
|
||||
let sleepSecondsRemaining = self.getSleepTimeRemaining()
|
||||
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
|
||||
|
||||
if self.audioPlayer.rate != rate {
|
||||
|
@ -415,11 +406,6 @@ class AudioPlayer: NSObject {
|
|||
if playbackSpeedChanged {
|
||||
self.tmpRate = rate
|
||||
|
||||
// If we have an active sleep timer, reschedule based on rate
|
||||
if let currentTime = self.getCurrentTime() {
|
||||
self.rescheduleSleepTimerAtTime(time: currentTime, secondsRemaining: sleepSecondsRemaining)
|
||||
}
|
||||
|
||||
// Setup the time observer again at the new rate
|
||||
self.setupTimeObserver()
|
||||
}
|
||||
|
|
|
@ -12,54 +12,59 @@ extension AudioPlayer {
|
|||
|
||||
// MARK: - Public API
|
||||
|
||||
public func isSleepTimerSet() -> Bool {
|
||||
return self.isCountdownSleepTimerSet() || self.isChapterSleepTimerSet()
|
||||
}
|
||||
|
||||
public func getSleepTimeRemaining() -> Double? {
|
||||
guard let currentTime = self.getCurrentTime() else { return nil }
|
||||
|
||||
// Return the player time until sleep
|
||||
var timeUntilSleep: Double? = nil
|
||||
var sleepTimeRemaining: Double? = nil
|
||||
if let chapterStopAt = self.sleepTimeChapterStopAt {
|
||||
timeUntilSleep = chapterStopAt - currentTime
|
||||
} else if let stopAt = self.sleepTimeStopAt {
|
||||
timeUntilSleep = stopAt - currentTime
|
||||
sleepTimeRemaining = chapterStopAt - currentTime
|
||||
} else if self.isCountdownSleepTimerSet() {
|
||||
sleepTimeRemaining = self.sleepTimeRemaining
|
||||
}
|
||||
|
||||
// Scale the time until sleep based on the playback rate
|
||||
if let timeUntilSleep = timeUntilSleep {
|
||||
let timeUntilSleepScaled = timeUntilSleep / self.getPlaybackRate()
|
||||
guard timeUntilSleepScaled.isNaN == false else { return nil }
|
||||
|
||||
return timeUntilSleepScaled.rounded()
|
||||
} else {
|
||||
// Guard against invalid sleep timers
|
||||
if sleepTimeRemaining?.isLess(than: 0) ?? false {
|
||||
self.removeSleepTimer()
|
||||
return nil
|
||||
}
|
||||
|
||||
return sleepTimeRemaining
|
||||
}
|
||||
|
||||
// Let iOS handle the sleep timer logic by letting us know when it's time to stop
|
||||
public func setSleepTime(stopAt: Double, scaleBasedOnSpeed: Bool = false) {
|
||||
NSLog("SLEEP TIMER: Scheduling for \(stopAt)")
|
||||
public func setSleepTimer(secondsUntilSleep: Double) {
|
||||
NSLog("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
|
||||
self.removeSleepTimer()
|
||||
self.sleepTimeRemaining = secondsUntilSleep
|
||||
|
||||
// Reset any previous sleep timer
|
||||
let isChapterSleepTimer = !scaleBasedOnSpeed
|
||||
self.removeSleepTimer(resetStopAt: !isChapterSleepTimer)
|
||||
|
||||
guard let currentTime = getCurrentTime() else {
|
||||
NSLog("SLEEP TIMER: Failed to get currenTime")
|
||||
return
|
||||
DispatchQueue.runOnMainQueue {
|
||||
self.sleepTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
|
||||
if self.isPlaying() {
|
||||
self.decrementSleepTimerIfRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the time to stop playing
|
||||
let scaledStopAt = self.calculateScaledStopAt(stopAt, currentTime: currentTime, scaleBasedOnSpeed: scaleBasedOnSpeed)
|
||||
self.sleepTimeStopAt = scaledStopAt
|
||||
let sleepTime = CMTime(seconds: scaledStopAt, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
// Update the UI
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
|
||||
}
|
||||
|
||||
public func setChapterSleepTimer(stopAt: Double) {
|
||||
NSLog("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
|
||||
self.removeSleepTimer()
|
||||
|
||||
// Schedule the observation time
|
||||
self.sleepTimeChapterStopAt = stopAt
|
||||
let sleepTime = CMTime(seconds: stopAt, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
var times = [NSValue]()
|
||||
times.append(NSValue(time: sleepTime))
|
||||
|
||||
self.sleepTimeToken = self.audioPlayer.addBoundaryTimeObserver(forTimes: times, queue: self.queue) { [weak self] in
|
||||
NSLog("SLEEP TIMER: Pausing audio")
|
||||
self?.pause()
|
||||
self?.removeSleepTimer()
|
||||
self.sleepTimeChapterToken = self.audioPlayer.addBoundaryTimeObserver(forTimes: times, queue: self.queue) { [weak self] in
|
||||
self?.handleSleepEnd()
|
||||
}
|
||||
|
||||
// Update the UI
|
||||
|
@ -67,56 +72,66 @@ extension AudioPlayer {
|
|||
}
|
||||
|
||||
public func increaseSleepTime(extraTimeInSeconds: Double) {
|
||||
if let sleepTime = self.getSleepTimeRemaining(), let currentTime = getCurrentTime() {
|
||||
let newSleepTimerPosition = currentTime + sleepTime + extraTimeInSeconds
|
||||
if newSleepTimerPosition > currentTime {
|
||||
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
|
||||
}
|
||||
}
|
||||
self.removeChapterSleepTimer()
|
||||
guard let sleepTimeRemaining = self.sleepTimeRemaining else { return }
|
||||
self.sleepTimeRemaining = sleepTimeRemaining + extraTimeInSeconds
|
||||
|
||||
// Update the UI
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
|
||||
}
|
||||
|
||||
public func decreaseSleepTime(removeTimeInSeconds: Double) {
|
||||
if let sleepTime = self.getSleepTimeRemaining(), let currentTime = getCurrentTime() {
|
||||
let newSleepTimerPosition = currentTime + sleepTime - removeTimeInSeconds
|
||||
if newSleepTimerPosition > currentTime {
|
||||
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func removeSleepTimer(resetStopAt: Bool = true) {
|
||||
if resetStopAt {
|
||||
self.sleepTimeStopAt = nil
|
||||
self.sleepTimeChapterStopAt = nil
|
||||
}
|
||||
|
||||
if let token = self.sleepTimeToken {
|
||||
self.audioPlayer.removeTimeObserver(token)
|
||||
self.sleepTimeToken = nil
|
||||
}
|
||||
self.removeChapterSleepTimer()
|
||||
guard let sleepTimeRemaining = self.sleepTimeRemaining else { return }
|
||||
self.sleepTimeRemaining = sleepTimeRemaining - removeTimeInSeconds
|
||||
|
||||
// Update the UI
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: self)
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
|
||||
}
|
||||
|
||||
public func removeSleepTimer() {
|
||||
self.sleepTimer?.invalidate()
|
||||
self.sleepTimer = nil
|
||||
self.removeChapterSleepTimer()
|
||||
self.sleepTimeRemaining = nil
|
||||
|
||||
// Update the UI after a delay, to avoid a race condition when changing chapters
|
||||
DispatchQueue.runOnMainQueue {
|
||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
|
||||
if !self.isSleepTimerSet() {
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Internal helpers
|
||||
|
||||
internal func rescheduleSleepTimerAtTime(time: Double, secondsRemaining: Double?) {
|
||||
guard self.isSleepTimerSet() else { return }
|
||||
|
||||
// Cancel a chapter sleep timer that is no longer valid
|
||||
if isChapterSleepTimerBeforeTime(time) {
|
||||
return self.removeSleepTimer()
|
||||
internal func decrementSleepTimerIfRunning() {
|
||||
if var sleepTimeRemaining = self.sleepTimeRemaining {
|
||||
sleepTimeRemaining -= 1
|
||||
self.sleepTimeRemaining = sleepTimeRemaining
|
||||
|
||||
// Handle the sleep if the timer has expired
|
||||
if sleepTimeRemaining <= 0 {
|
||||
self.handleSleepEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// Update the sleep timer
|
||||
if !isChapterSleepTimer() {
|
||||
guard let secondsRemaining = secondsRemaining else { return }
|
||||
let newSleepTimerPosition = time + secondsRemaining
|
||||
self.setSleepTime(stopAt: newSleepTimerPosition, scaleBasedOnSpeed: true)
|
||||
}
|
||||
|
||||
private func handleSleepEnd() {
|
||||
NSLog("SLEEP TIMER: Pausing audio")
|
||||
self.pause()
|
||||
self.removeSleepTimer()
|
||||
}
|
||||
|
||||
private func removeChapterSleepTimer() {
|
||||
if let token = self.sleepTimeChapterToken {
|
||||
self.audioPlayer.removeTimeObserver(token)
|
||||
}
|
||||
self.sleepTimeChapterToken = nil
|
||||
self.sleepTimeChapterStopAt = nil
|
||||
}
|
||||
|
||||
private func isChapterSleepTimerBeforeTime(_ time: Double) -> Bool {
|
||||
|
@ -127,29 +142,12 @@ extension AudioPlayer {
|
|||
return false
|
||||
}
|
||||
|
||||
private func isSleepTimerSet() -> Bool {
|
||||
return self.sleepTimeStopAt != nil
|
||||
private func isCountdownSleepTimerSet() -> Bool {
|
||||
return self.sleepTimeRemaining != nil
|
||||
}
|
||||
|
||||
private func isChapterSleepTimer() -> Bool {
|
||||
private func isChapterSleepTimerSet() -> Bool {
|
||||
return self.sleepTimeChapterStopAt != nil
|
||||
}
|
||||
|
||||
private func getPlaybackRate() -> Double {
|
||||
// Consider paused as playing at 1x
|
||||
return Double(self.rate > 0 ? self.rate : 1)
|
||||
}
|
||||
|
||||
private func calculateScaledStopAt(_ stopAt: Double, currentTime: Double, scaleBasedOnSpeed: Bool) -> Double {
|
||||
if scaleBasedOnSpeed {
|
||||
// Calculate the scaled time to stop at
|
||||
let secondsUntilStopAt1x = stopAt - currentTime
|
||||
let secondsUntilSleep = secondsUntilStopAt1x * self.getPlaybackRate()
|
||||
NSLog("SLEEP TIMER: Adjusted based on playback speed of \(self.getPlaybackRate()) to \(secondsUntilSleep)")
|
||||
return currentTime + secondsUntilSleep
|
||||
} else {
|
||||
return stopAt
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -70,13 +70,11 @@ class PlayerHandler {
|
|||
}
|
||||
|
||||
public static func setSleepTime(secondsUntilSleep: Double) {
|
||||
guard let currentTime = self.player?.getCurrentTime() else { return }
|
||||
let stopAt = secondsUntilSleep + currentTime
|
||||
self.player?.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: true)
|
||||
self.player?.setSleepTimer(secondsUntilSleep: secondsUntilSleep)
|
||||
}
|
||||
|
||||
public static func setChapterSleepTime(stopAt: Double) {
|
||||
self.player?.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: false)
|
||||
self.player?.setChapterSleepTimer(stopAt: stopAt)
|
||||
}
|
||||
|
||||
public static func increaseSleepTime(increaseSeconds: Double) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue