mirror of
https://github.com/sudoxnym/audiobookshelf-atv.git
synced 2026-07-14 07:52:25 +00:00
Realm modifications should occur on concurrent queue
This commit is contained in:
parent
452b25057e
commit
46623d70a3
2 changed files with 80 additions and 72 deletions
|
|
@ -18,8 +18,6 @@ enum PlayMethod:Int {
|
|||
}
|
||||
|
||||
class AudioPlayer: NSObject {
|
||||
private let audioPlayerQueue = DispatchQueue(label: "ABSAudioPlayerQueue")
|
||||
|
||||
// enums and @objc are not compatible
|
||||
@objc dynamic var status: Int
|
||||
@objc dynamic var rate: Float
|
||||
|
|
@ -143,7 +141,7 @@ class AudioPlayer: NSObject {
|
|||
// Rate will be different depending on playback speed, aim for 2 observations/sec
|
||||
let seconds = 0.5 * (self.rate > 0 ? self.rate : 1.0)
|
||||
let time = CMTime(seconds: Double(seconds), preferredTimescale: timeScale)
|
||||
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: audioPlayerQueue) { [weak self] time in
|
||||
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: PlayerProgress.queue) { [weak self] time in
|
||||
let sleepTimeStopAt = self?.sleepTimeStopAt
|
||||
Task {
|
||||
// Let the player update the current playback positions
|
||||
|
|
@ -207,7 +205,7 @@ class AudioPlayer: NSObject {
|
|||
|
||||
private func startPausedTimer() {
|
||||
guard self.pausedTimer == nil else { return }
|
||||
audioPlayerQueue.async {
|
||||
PlayerProgress.queue.async {
|
||||
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
|
||||
NSLog("PAUSE TIMER: Syncing from server")
|
||||
Task { await PlayerProgress.shared.syncFromServer() }
|
||||
|
|
@ -400,7 +398,7 @@ class AudioPlayer: NSObject {
|
|||
var times = [NSValue]()
|
||||
times.append(NSValue(time: sleepTime))
|
||||
|
||||
sleepTimeToken = self.audioPlayer.addBoundaryTimeObserver(forTimes: times, queue: audioPlayerQueue) { [weak self] in
|
||||
sleepTimeToken = self.audioPlayer.addBoundaryTimeObserver(forTimes: times, queue: PlayerProgress.queue) { [weak self] in
|
||||
NSLog("SLEEP TIMER: Pausing audio")
|
||||
self?.pause()
|
||||
self?.removeSleepTimer()
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import UIKit
|
|||
import RealmSwift
|
||||
|
||||
class PlayerProgress {
|
||||
|
||||
public static let shared = PlayerProgress()
|
||||
public static let queue = DispatchQueue(label: "ABSPlayerProgressQueue")
|
||||
|
||||
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ class PlayerProgress {
|
|||
|
||||
public func syncFromPlayer(currentTime: Double, includesPlayProgress: Bool, isStopping: Bool) async {
|
||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromPlayer")
|
||||
let session = await updateLocalSessionFromPlayer(currentTime: currentTime, includesPlayProgress: includesPlayProgress)
|
||||
let session = updateLocalSessionFromPlayer(currentTime: currentTime, includesPlayProgress: includesPlayProgress)
|
||||
updateLocalMediaProgressFromLocalSession()
|
||||
if let session = session {
|
||||
await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
||||
|
|
@ -45,48 +45,52 @@ class PlayerProgress {
|
|||
|
||||
// MARK: - SYNC LOGIC
|
||||
|
||||
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) async -> PlaybackSession? {
|
||||
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
||||
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
||||
|
||||
session.update {
|
||||
session.realm?.refresh()
|
||||
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) -> PlaybackSession? {
|
||||
PlayerProgress.queue.sync {
|
||||
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
||||
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
||||
|
||||
let nowInSeconds = Date().timeIntervalSince1970
|
||||
let nowInMilliseconds = nowInSeconds * 1000
|
||||
let lastUpdateInMilliseconds = session.updatedAt ?? nowInMilliseconds
|
||||
let lastUpdateInSeconds = lastUpdateInMilliseconds / 1000
|
||||
let secondsSinceLastUpdate = nowInSeconds - lastUpdateInSeconds
|
||||
|
||||
session.currentTime = currentTime
|
||||
session.updatedAt = nowInMilliseconds
|
||||
|
||||
if includesPlayProgress {
|
||||
session.timeListening += secondsSinceLastUpdate
|
||||
session.update {
|
||||
session.realm?.refresh()
|
||||
|
||||
let nowInSeconds = Date().timeIntervalSince1970
|
||||
let nowInMilliseconds = nowInSeconds * 1000
|
||||
let lastUpdateInMilliseconds = session.updatedAt ?? nowInMilliseconds
|
||||
let lastUpdateInSeconds = lastUpdateInMilliseconds / 1000
|
||||
let secondsSinceLastUpdate = nowInSeconds - lastUpdateInSeconds
|
||||
|
||||
session.currentTime = currentTime
|
||||
session.updatedAt = nowInMilliseconds
|
||||
|
||||
if includesPlayProgress {
|
||||
session.timeListening += secondsSinceLastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
return session.freeze()
|
||||
}
|
||||
|
||||
return session.freeze()
|
||||
}
|
||||
|
||||
private func updateLocalMediaProgressFromLocalSession() {
|
||||
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
||||
guard session.isLocal else { return }
|
||||
|
||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
|
||||
guard let localMediaProgress = localMediaProgress else {
|
||||
// Local media progress should have been created
|
||||
// If we're here, it means a library id is invalid
|
||||
return
|
||||
}
|
||||
PlayerProgress.queue.sync {
|
||||
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
||||
guard session.isLocal else { return }
|
||||
|
||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
|
||||
guard let localMediaProgress = localMediaProgress else {
|
||||
// Local media progress should have been created
|
||||
// If we're here, it means a library id is invalid
|
||||
return
|
||||
}
|
||||
|
||||
localMediaProgress.updateFromPlaybackSession(session)
|
||||
Database.shared.saveLocalMediaProgress(localMediaProgress)
|
||||
|
||||
NSLog("Local progress saved to the database")
|
||||
|
||||
// Send the local progress back to front-end
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
||||
localMediaProgress.updateFromPlaybackSession(session)
|
||||
Database.shared.saveLocalMediaProgress(localMediaProgress)
|
||||
|
||||
NSLog("Local progress saved to the database")
|
||||
|
||||
// Send the local progress back to front-end
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAllServerSessionFromLocalSession() async {
|
||||
|
|
@ -102,31 +106,32 @@ class PlayerProgress {
|
|||
}
|
||||
|
||||
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async {
|
||||
guard var session = session.thaw() else { return }
|
||||
var safeToSync = true
|
||||
|
||||
// We need to update and check the server time in a transaction for thread-safety
|
||||
session.update {
|
||||
session.realm?.refresh()
|
||||
PlayerProgress.queue.sync {
|
||||
var safeToSync = true
|
||||
guard var session = session.thaw() else { return }
|
||||
|
||||
let nowInMilliseconds = Date().timeIntervalSince1970 * 1000
|
||||
let lastUpdateInMilliseconds = session.serverUpdatedAt
|
||||
|
||||
// If required, rate limit requests based on session last update
|
||||
if rateLimitSync {
|
||||
let timeSinceLastSync = nowInMilliseconds - lastUpdateInMilliseconds
|
||||
let timeBetweenSessionSync = PlayerProgress.TIME_BETWEEN_SESSION_SYNC_IN_SECONDS * 1000
|
||||
safeToSync = timeSinceLastSync > timeBetweenSessionSync
|
||||
if !safeToSync {
|
||||
return // This only exits the update block
|
||||
// We need to update and check the server time in a transaction for thread-safety
|
||||
session.update {
|
||||
session.realm?.refresh()
|
||||
|
||||
let nowInMilliseconds = Date().timeIntervalSince1970 * 1000
|
||||
let lastUpdateInMilliseconds = session.serverUpdatedAt
|
||||
|
||||
// If required, rate limit requests based on session last update
|
||||
if rateLimitSync {
|
||||
let timeSinceLastSync = nowInMilliseconds - lastUpdateInMilliseconds
|
||||
let timeBetweenSessionSync = PlayerProgress.TIME_BETWEEN_SESSION_SYNC_IN_SECONDS * 1000
|
||||
safeToSync = timeSinceLastSync > timeBetweenSessionSync
|
||||
if !safeToSync {
|
||||
return // This only exits the update block
|
||||
}
|
||||
}
|
||||
|
||||
session.serverUpdatedAt = nowInMilliseconds
|
||||
}
|
||||
|
||||
session.serverUpdatedAt = nowInMilliseconds
|
||||
session = session.freeze()
|
||||
guard safeToSync else { return }
|
||||
}
|
||||
session = session.freeze()
|
||||
|
||||
guard safeToSync else { return }
|
||||
|
||||
NSLog("Sending sessionId(\(session.id)) to server")
|
||||
|
||||
|
|
@ -138,10 +143,13 @@ class PlayerProgress {
|
|||
success = await ApiClient.reportPlaybackProgress(report: playbackReport, sessionId: session.id)
|
||||
}
|
||||
|
||||
|
||||
// Remove old sessions after they synced with the server
|
||||
if success && !session.isActiveSession {
|
||||
if let session = session.thaw() {
|
||||
session.delete()
|
||||
PlayerProgress.queue.sync {
|
||||
if let session = session.thaw() {
|
||||
session.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -176,14 +184,16 @@ class PlayerProgress {
|
|||
|
||||
// Update the session, if needed
|
||||
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
||||
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||
guard let session = session.thaw() else { return }
|
||||
session.update {
|
||||
session.currentTime = serverCurrentTime
|
||||
session.updatedAt = serverLastUpdate
|
||||
PlayerProgress.queue.sync {
|
||||
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||
guard let session = session.thaw() else { return }
|
||||
session.update {
|
||||
session.currentTime = serverCurrentTime
|
||||
session.updatedAt = serverLastUpdate
|
||||
}
|
||||
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
||||
PlayerHandler.seek(amount: session.currentTime)
|
||||
}
|
||||
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
||||
PlayerHandler.seek(amount: session.currentTime)
|
||||
} else {
|
||||
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue