Sleep timer using native time observer

This commit is contained in:
ronaldheft
2022-08-22 17:04:48 -04:00
parent 8952cbfd20
commit d57fe44bcc
4 changed files with 264 additions and 172 deletions
+72 -146
View File
@@ -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() {