mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 23:27:17 +02:00
Realm modifications should occur on concurrent queue
This commit is contained in:
@@ -18,8 +18,6 @@ enum PlayMethod:Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AudioPlayer: NSObject {
|
class AudioPlayer: NSObject {
|
||||||
private let audioPlayerQueue = DispatchQueue(label: "ABSAudioPlayerQueue")
|
|
||||||
|
|
||||||
// enums and @objc are not compatible
|
// enums and @objc are not compatible
|
||||||
@objc dynamic var status: Int
|
@objc dynamic var status: Int
|
||||||
@objc dynamic var rate: Float
|
@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
|
// 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 seconds = 0.5 * (self.rate > 0 ? self.rate : 1.0)
|
||||||
let time = CMTime(seconds: Double(seconds), preferredTimescale: timeScale)
|
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
|
let sleepTimeStopAt = self?.sleepTimeStopAt
|
||||||
Task {
|
Task {
|
||||||
// Let the player update the current playback positions
|
// Let the player update the current playback positions
|
||||||
@@ -207,7 +205,7 @@ class AudioPlayer: NSObject {
|
|||||||
|
|
||||||
private func startPausedTimer() {
|
private func startPausedTimer() {
|
||||||
guard self.pausedTimer == nil else { return }
|
guard self.pausedTimer == nil else { return }
|
||||||
audioPlayerQueue.async {
|
PlayerProgress.queue.async {
|
||||||
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
|
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
|
||||||
NSLog("PAUSE TIMER: Syncing from server")
|
NSLog("PAUSE TIMER: Syncing from server")
|
||||||
Task { await PlayerProgress.shared.syncFromServer() }
|
Task { await PlayerProgress.shared.syncFromServer() }
|
||||||
@@ -400,7 +398,7 @@ class AudioPlayer: NSObject {
|
|||||||
var times = [NSValue]()
|
var times = [NSValue]()
|
||||||
times.append(NSValue(time: sleepTime))
|
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")
|
NSLog("SLEEP TIMER: Pausing audio")
|
||||||
self?.pause()
|
self?.pause()
|
||||||
self?.removeSleepTimer()
|
self?.removeSleepTimer()
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import UIKit
|
|||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
|
||||||
class PlayerProgress {
|
class PlayerProgress {
|
||||||
|
|
||||||
public static let shared = PlayerProgress()
|
public static let shared = PlayerProgress()
|
||||||
|
public static let queue = DispatchQueue(label: "ABSPlayerProgressQueue")
|
||||||
|
|
||||||
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
|
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 {
|
public func syncFromPlayer(currentTime: Double, includesPlayProgress: Bool, isStopping: Bool) async {
|
||||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromPlayer")
|
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()
|
updateLocalMediaProgressFromLocalSession()
|
||||||
if let session = session {
|
if let session = session {
|
||||||
await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
||||||
@@ -45,7 +45,8 @@ class PlayerProgress {
|
|||||||
|
|
||||||
// MARK: - SYNC LOGIC
|
// MARK: - SYNC LOGIC
|
||||||
|
|
||||||
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) async -> PlaybackSession? {
|
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) -> PlaybackSession? {
|
||||||
|
PlayerProgress.queue.sync {
|
||||||
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
||||||
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
||||||
|
|
||||||
@@ -68,8 +69,10 @@ class PlayerProgress {
|
|||||||
|
|
||||||
return session.freeze()
|
return session.freeze()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateLocalMediaProgressFromLocalSession() {
|
private func updateLocalMediaProgressFromLocalSession() {
|
||||||
|
PlayerProgress.queue.sync {
|
||||||
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
||||||
guard session.isLocal else { return }
|
guard session.isLocal else { return }
|
||||||
|
|
||||||
@@ -88,6 +91,7 @@ class PlayerProgress {
|
|||||||
// Send the local progress back to front-end
|
// Send the local progress back to front-end
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateAllServerSessionFromLocalSession() async {
|
private func updateAllServerSessionFromLocalSession() async {
|
||||||
await withTaskGroup(of: Void.self) { [self] group in
|
await withTaskGroup(of: Void.self) { [self] group in
|
||||||
@@ -102,8 +106,9 @@ class PlayerProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async {
|
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async {
|
||||||
guard var session = session.thaw() else { return }
|
PlayerProgress.queue.sync {
|
||||||
var safeToSync = true
|
var safeToSync = true
|
||||||
|
guard var session = session.thaw() else { return }
|
||||||
|
|
||||||
// We need to update and check the server time in a transaction for thread-safety
|
// We need to update and check the server time in a transaction for thread-safety
|
||||||
session.update {
|
session.update {
|
||||||
@@ -125,8 +130,8 @@ class PlayerProgress {
|
|||||||
session.serverUpdatedAt = nowInMilliseconds
|
session.serverUpdatedAt = nowInMilliseconds
|
||||||
}
|
}
|
||||||
session = session.freeze()
|
session = session.freeze()
|
||||||
|
|
||||||
guard safeToSync else { return }
|
guard safeToSync else { return }
|
||||||
|
}
|
||||||
|
|
||||||
NSLog("Sending sessionId(\(session.id)) to server")
|
NSLog("Sending sessionId(\(session.id)) to server")
|
||||||
|
|
||||||
@@ -138,13 +143,16 @@ class PlayerProgress {
|
|||||||
success = await ApiClient.reportPlaybackProgress(report: playbackReport, sessionId: session.id)
|
success = await ApiClient.reportPlaybackProgress(report: playbackReport, sessionId: session.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Remove old sessions after they synced with the server
|
// Remove old sessions after they synced with the server
|
||||||
if success && !session.isActiveSession {
|
if success && !session.isActiveSession {
|
||||||
|
PlayerProgress.queue.sync {
|
||||||
if let session = session.thaw() {
|
if let session = session.thaw() {
|
||||||
session.delete()
|
session.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateLocalSessionFromServerMediaProgress() async {
|
private func updateLocalSessionFromServerMediaProgress() async {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
||||||
@@ -176,6 +184,7 @@ class PlayerProgress {
|
|||||||
|
|
||||||
// Update the session, if needed
|
// Update the session, if needed
|
||||||
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
||||||
|
PlayerProgress.queue.sync {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||||
guard let session = session.thaw() else { return }
|
guard let session = session.thaw() else { return }
|
||||||
session.update {
|
session.update {
|
||||||
@@ -184,6 +193,7 @@ class PlayerProgress {
|
|||||||
}
|
}
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
||||||
PlayerHandler.seek(amount: session.currentTime)
|
PlayerHandler.seek(amount: session.currentTime)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
|
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user