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