mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-25 21:04:02 +02:00
Sleep timer using native time observer
This commit is contained in:
@@ -10,85 +10,8 @@ import RealmSwift
|
||||
|
||||
class PlayerHandler {
|
||||
private static var player: AudioPlayer?
|
||||
private static var playingTimer: Timer?
|
||||
private static var pausedTimer: Timer?
|
||||
private static var lastSyncTime: Double = 0.0
|
||||
|
||||
public static var sleepTimerChapterStopTime: Int? = nil
|
||||
private static var _remainingSleepTime: Int? = nil
|
||||
public static var remainingSleepTime: Int? {
|
||||
get {
|
||||
return _remainingSleepTime
|
||||
}
|
||||
set(time) {
|
||||
if time != nil && time! < 0 {
|
||||
_remainingSleepTime = nil
|
||||
} else {
|
||||
_remainingSleepTime = time
|
||||
}
|
||||
|
||||
if _remainingSleepTime == nil {
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: _remainingSleepTime)
|
||||
} else {
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: _remainingSleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
private static var listeningTimePassedSinceLastSync: Double = 0.0
|
||||
|
||||
public static var paused: Bool {
|
||||
get {
|
||||
guard let player = player else {
|
||||
return true
|
||||
}
|
||||
|
||||
return player.rate == 0.0
|
||||
}
|
||||
set(paused) {
|
||||
if paused {
|
||||
self.player?.pause()
|
||||
} else {
|
||||
self.player?.play()
|
||||
self.pausedTimer?.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func startTickTimer() {
|
||||
DispatchQueue.runOnMainQueue {
|
||||
NSLog("Starting the tick timer")
|
||||
playingTimer?.invalidate()
|
||||
pausedTimer?.invalidate()
|
||||
playingTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||
self.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func stopTickTimer() {
|
||||
NSLog("Stopping the tick timer")
|
||||
playingTimer?.invalidate()
|
||||
pausedTimer?.invalidate()
|
||||
playingTimer = nil
|
||||
}
|
||||
|
||||
private static func startPausedTimer() {
|
||||
guard self.paused else { return }
|
||||
self.pausedTimer?.invalidate()
|
||||
self.pausedTimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(syncServerProgressDuringPause), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
private static func cleanupOldSessions(currentSessionId: String?) {
|
||||
let realm = try! Realm()
|
||||
let oldSessions = realm.objects(PlaybackSession.self) .where({ $0.isActiveSession == true })
|
||||
try! realm.write {
|
||||
for s in oldSessions {
|
||||
if s.id != currentSessionId {
|
||||
s.isActiveSession = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func startPlayback(sessionId: String, playWhenReady: Bool, playbackRate: Float) {
|
||||
guard let session = Database.shared.getPlaybackSession(id: sessionId) else { return }
|
||||
@@ -99,26 +22,21 @@ class PlayerHandler {
|
||||
player = nil
|
||||
}
|
||||
|
||||
// Cleanup old sessions
|
||||
// Cleanup and sync old sessions
|
||||
cleanupOldSessions(currentSessionId: sessionId)
|
||||
Task { await PlayerProgress.shared.syncToServer() }
|
||||
|
||||
// Set now playing info
|
||||
NowPlayingInfo.shared.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
||||
|
||||
// Create the audio player
|
||||
player = AudioPlayer(sessionId: sessionId, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||
|
||||
startTickTimer()
|
||||
startPausedTimer()
|
||||
}
|
||||
|
||||
public static func stopPlayback() {
|
||||
// Pause playback first, so we can sync our current progress
|
||||
player?.pause()
|
||||
|
||||
// Stop updating progress before we destory the player, so we don't receive bad data
|
||||
stopTickTimer()
|
||||
|
||||
player?.destroy()
|
||||
player = nil
|
||||
|
||||
@@ -127,6 +45,41 @@ class PlayerHandler {
|
||||
NowPlayingInfo.shared.reset()
|
||||
}
|
||||
|
||||
public static var paused: Bool {
|
||||
get {
|
||||
guard let player = player else { return true }
|
||||
return player.rate == 0.0
|
||||
}
|
||||
set(paused) {
|
||||
if paused {
|
||||
self.player?.pause()
|
||||
} else {
|
||||
self.player?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var remainingSleepTime: Int? {
|
||||
get {
|
||||
guard let player = player else { return nil }
|
||||
|
||||
// Consider paused as playing at 1x
|
||||
let rate = Double(player.rate > 0 ? player.rate : 1)
|
||||
|
||||
if let sleepTimerChapterStopTime = sleepTimerChapterStopTime {
|
||||
let timeUntilChapterEnd = Double(sleepTimerChapterStopTime) - player.getCurrentTime()
|
||||
let timeUntilChapterEndScaled = timeUntilChapterEnd / rate
|
||||
return Int(timeUntilChapterEndScaled.rounded())
|
||||
} else if let stopAt = player.getSleepStopAt() {
|
||||
let timeUntilSleep = stopAt - player.getCurrentTime()
|
||||
let timeUntilSleepScaled = timeUntilSleep / rate
|
||||
return Int(timeUntilSleepScaled.rounded())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func getCurrentTime() -> Double? {
|
||||
self.player?.getCurrentTime()
|
||||
}
|
||||
@@ -135,6 +88,30 @@ class PlayerHandler {
|
||||
self.player?.setPlaybackRate(speed)
|
||||
}
|
||||
|
||||
public static func setSleepTime(secondsUntilSleep: Double) {
|
||||
guard let player = player else { return }
|
||||
let stopAt = secondsUntilSleep + player.getCurrentTime()
|
||||
player.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: true)
|
||||
}
|
||||
|
||||
public static func setChapterSleepTime(stopAt: Double) {
|
||||
guard let player = player else { return }
|
||||
self.sleepTimerChapterStopTime = Int(stopAt)
|
||||
player.setSleepTime(stopAt: stopAt, scaleBasedOnSpeed: false)
|
||||
}
|
||||
|
||||
public static func increaseSleepTime(increaseSeconds: Double) {
|
||||
self.player?.increaseSleepTime(extraTimeInSeconds: increaseSeconds)
|
||||
}
|
||||
|
||||
public static func decreaseSleepTime(decreaseSeconds: Double) {
|
||||
self.player?.decreaseSleepTime(removeTimeInSeconds: decreaseSeconds)
|
||||
}
|
||||
|
||||
public static func cancelSleepTime() {
|
||||
self.player?.removeSleepTimer()
|
||||
}
|
||||
|
||||
public static func getPlayMethod() -> Int? {
|
||||
self.player?.getPlayMethod()
|
||||
}
|
||||
@@ -142,8 +119,8 @@ class PlayerHandler {
|
||||
public static func getPlaybackSession() -> PlaybackSession? {
|
||||
guard let player = player else { return nil }
|
||||
guard player.isInitialized() else { return nil }
|
||||
guard let session = Database.shared.getPlaybackSession(id: player.getPlaybackSessionId()) else { return nil }
|
||||
return session
|
||||
|
||||
return Database.shared.getPlaybackSession(id: player.getPlaybackSessionId())
|
||||
}
|
||||
|
||||
public static func seekForward(amount: Double) {
|
||||
@@ -168,10 +145,6 @@ class PlayerHandler {
|
||||
guard let player = player else { return nil }
|
||||
guard player.isInitialized() else { return nil }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
syncPlayerProgress()
|
||||
}
|
||||
|
||||
return [
|
||||
"duration": player.getDuration(),
|
||||
"currentTime": player.getCurrentTime(),
|
||||
@@ -180,65 +153,18 @@ class PlayerHandler {
|
||||
]
|
||||
}
|
||||
|
||||
private static func tick() {
|
||||
if !paused {
|
||||
listeningTimePassedSinceLastSync += 1
|
||||
|
||||
if remainingSleepTime != nil {
|
||||
if sleepTimerChapterStopTime != nil {
|
||||
let timeUntilChapterEnd = Double(sleepTimerChapterStopTime ?? 0) - (getCurrentTime() ?? 0)
|
||||
if timeUntilChapterEnd <= 0 {
|
||||
paused = true
|
||||
remainingSleepTime = nil
|
||||
} else {
|
||||
remainingSleepTime = Int(timeUntilChapterEnd.rounded())
|
||||
}
|
||||
} else {
|
||||
if remainingSleepTime! <= 0 {
|
||||
paused = true
|
||||
}
|
||||
remainingSleepTime! -= 1
|
||||
// MARK: - Helper logic
|
||||
|
||||
private static func cleanupOldSessions(currentSessionId: String?) {
|
||||
let realm = try! Realm()
|
||||
let oldSessions = realm.objects(PlaybackSession.self) .where({ $0.isActiveSession == true })
|
||||
try! realm.write {
|
||||
for s in oldSessions {
|
||||
if s.id != currentSessionId {
|
||||
s.isActiveSession = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if listeningTimePassedSinceLastSync >= 5 {
|
||||
syncPlayerProgress()
|
||||
}
|
||||
}
|
||||
|
||||
public static func syncPlayerProgress() {
|
||||
guard let player = player else { return }
|
||||
guard player.isInitialized() else { return }
|
||||
guard let session = getPlaybackSession() else { return }
|
||||
|
||||
NSLog("Syncing player progress")
|
||||
|
||||
// Get current time
|
||||
let playerCurrentTime = player.getCurrentTime()
|
||||
|
||||
// Prevent multiple sync requests
|
||||
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
|
||||
if (lastSyncTime > 0 && timeSinceLastSync < 1) {
|
||||
NSLog("syncProgress last sync time was < 1 second so not syncing")
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent a sync if we got junk data from the player (occurs when exiting out of memory
|
||||
guard !playerCurrentTime.isNaN else { return }
|
||||
|
||||
lastSyncTime = Date().timeIntervalSince1970 // seconds
|
||||
|
||||
session.update {
|
||||
session.currentTime = playerCurrentTime
|
||||
session.timeListening += listeningTimePassedSinceLastSync
|
||||
session.updatedAt = Date().timeIntervalSince1970 * 1000
|
||||
}
|
||||
listeningTimePassedSinceLastSync = 0
|
||||
|
||||
// Persist items in the database and sync to the server
|
||||
if session.isLocal { Task { await PlayerProgress.shared.syncFromPlayer() } }
|
||||
Task { await PlayerProgress.shared.syncToServer() }
|
||||
}
|
||||
|
||||
@objc public static func syncServerProgressDuringPause() {
|
||||
|
||||
Reference in New Issue
Block a user