mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-19 00:58:54 +02:00
Refactor and clean up sync logic
This commit is contained in:
parent
062a217946
commit
972fbd42ee
4 changed files with 67 additions and 72 deletions
|
@ -40,7 +40,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
// Fetch the most recent active session
|
// Fetch the most recent active session
|
||||||
let activeSession = try await Realm().objects(PlaybackSession.self).where({ $0.isActiveSession == true }).last
|
let activeSession = try await Realm().objects(PlaybackSession.self).where({ $0.isActiveSession == true }).last
|
||||||
if let activeSession = activeSession {
|
if let activeSession = activeSession {
|
||||||
await PlayerProgress.syncLocalFromServer()
|
await PlayerProgress.syncFromServer()
|
||||||
try self.startPlaybackSession(activeSession, playWhenReady: false, playbackRate: PlayerSettings.main().playbackRate)
|
try self.startPlaybackSession(activeSession, playWhenReady: false, playbackRate: PlayerSettings.main().playbackRate)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import RealmSwift
|
||||||
|
|
||||||
class PlayerHandler {
|
class PlayerHandler {
|
||||||
private static var player: AudioPlayer?
|
private static var player: AudioPlayer?
|
||||||
private static var timer: Timer?
|
private static var playingTimer: Timer?
|
||||||
private static var pausedTimer: Timer?
|
private static var pausedTimer: Timer?
|
||||||
private static var lastSyncTime: Double = 0.0
|
private static var lastSyncTime: Double = 0.0
|
||||||
|
|
||||||
|
@ -240,64 +240,11 @@ class PlayerHandler {
|
||||||
listeningTimePassedSinceLastSync = 0
|
listeningTimePassedSinceLastSync = 0
|
||||||
|
|
||||||
// Persist items in the database and sync to the server
|
// Persist items in the database and sync to the server
|
||||||
if session.isLocal { syncLocalProgress() }
|
if session.isLocal { PlayerProgress.syncFromPlayer() }
|
||||||
syncServerProgress()
|
Task { await PlayerProgress.syncToServer() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func syncLocalProgress() {
|
@objc public static func syncServerProgressDuringPause() {
|
||||||
DispatchQueue.global(qos: .utility).async {
|
Task { await PlayerProgress.syncFromServer() }
|
||||||
guard let session = 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func syncServerProgress() {
|
|
||||||
guard Connectivity.isConnectedToInternet else { return }
|
|
||||||
DispatchQueue.global(qos: .utility).async {
|
|
||||||
let realm = try! Realm()
|
|
||||||
for session in realm.objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id }) {
|
|
||||||
NSLog("Sending sessionId(\(session.id)) to server")
|
|
||||||
let sessionRef = ThreadSafeReference(to: session)
|
|
||||||
|
|
||||||
func handleSyncSuccess(_ success: Bool) {
|
|
||||||
// Remove old sessions after they synced with the server
|
|
||||||
let session = try! Realm().resolve(sessionRef)
|
|
||||||
if success && !(session?.isActiveSession ?? false) {
|
|
||||||
NSLog("Deleting sessionId(\(session!.id)) as is no longer active")
|
|
||||||
session?.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if session.isLocal {
|
|
||||||
ApiClient.reportLocalPlaybackProgress(session.freeze()) { success in
|
|
||||||
handleSyncSuccess(success)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let playbackReport = PlaybackReport(currentTime: session.currentTime, duration: session.duration, timeListened: session.timeListening)
|
|
||||||
ApiClient.reportPlaybackProgress(report: playbackReport, sessionId: session.id) { success in
|
|
||||||
handleSyncSuccess(success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private static func syncServerProgressDuringPause() {
|
|
||||||
Task { await PlayerProgress.syncLocalFromServer() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,16 +13,18 @@ class PlayerProgress {
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
public static func syncLocalFromPlayer() async {
|
public static func syncFromPlayer() {
|
||||||
|
updateLocalMediaProgressFromLocalSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func syncServerFromLocal() async {
|
public static func syncToServer() async {
|
||||||
|
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncToServer")
|
||||||
|
updateAllServerSessionFromLocalSession()
|
||||||
|
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func syncLocalFromServer() async {
|
public static func syncFromServer() async {
|
||||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:updateLocalSessionFromServerMediaProgress")
|
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer")
|
||||||
await updateLocalSessionFromServerMediaProgress()
|
await updateLocalSessionFromServerMediaProgress()
|
||||||
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||||
}
|
}
|
||||||
|
@ -32,16 +34,54 @@ class PlayerProgress {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func updateLocalMediaProgressFromLocalSession() {
|
private static 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 static func updateServerSessionFromLocalSession() {
|
private static func updateAllServerSessionFromLocalSession() {
|
||||||
|
let sessions = try! Realm().objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id })
|
||||||
|
for session in sessions {
|
||||||
|
let session = session.freeze()
|
||||||
|
Task { await updateServerSessionFromLocalSession(session) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func updateServerSessionFromLocalSession(_ session: PlaybackSession) async {
|
||||||
|
NSLog("Sending sessionId(\(session.id)) to server")
|
||||||
|
|
||||||
|
var success = false
|
||||||
|
if session.isLocal {
|
||||||
|
success = await ApiClient.reportLocalPlaybackProgress(session)
|
||||||
|
} else {
|
||||||
|
let playbackReport = PlaybackReport(currentTime: session.currentTime, duration: session.duration, timeListened: session.timeListening)
|
||||||
|
success = await ApiClient.reportPlaybackProgress(report: playbackReport, sessionId: session.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old sessions after they synced with the server
|
||||||
|
if success && !session.isActiveSession {
|
||||||
|
NSLog("Deleting sessionId(\(session.id)) as is no longer active")
|
||||||
|
session.thaw()?.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func updateLocalSessionFromServerMediaProgress() async {
|
private static func updateLocalSessionFromServerMediaProgress() async {
|
||||||
NSLog("checkCurrentSessionProgress: Checking if local media progress was updated on server")
|
NSLog("checkCurrentSessionProgress: Checking if local media progress was updated on server")
|
||||||
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
guard let session = PlayerHandler.getPlaybackSession()?.freeze() else { return }
|
||||||
|
|
||||||
// Fetch the current progress
|
// Fetch the current progress
|
||||||
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
|
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
|
||||||
|
@ -58,6 +98,7 @@ class PlayerProgress {
|
||||||
|
|
||||||
// Update the session, if needed
|
// Update the session, if needed
|
||||||
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
||||||
|
guard let session = session.thaw() else { return }
|
||||||
session.update {
|
session.update {
|
||||||
session.currentTime = serverCurrentTime
|
session.currentTime = serverCurrentTime
|
||||||
session.updatedAt = serverLastUpdate
|
session.updatedAt = serverLastUpdate
|
||||||
|
|
|
@ -59,6 +59,14 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func postResource<T:Encodable>(endpoint: String, parameters: T) async -> Bool {
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
postResource(endpoint: endpoint, parameters: parameters) { success in
|
||||||
|
continuation.resume(returning: success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||||
if (Store.serverConfig == nil) {
|
if (Store.serverConfig == nil) {
|
||||||
NSLog("Server config not set")
|
NSLog("Server config not set")
|
||||||
|
@ -170,13 +178,12 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String, callback: @escaping (_ success: Bool) -> Void) {
|
public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) async -> Bool {
|
||||||
try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: callback)
|
return await postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func reportLocalPlaybackProgress(_ session: PlaybackSession, callback: @escaping (_ success: Bool) -> Void) {
|
public static func reportLocalPlaybackProgress(_ session: PlaybackSession) async -> Bool {
|
||||||
let progress = session.freeze()
|
return await postResource(endpoint: "api/session/local", parameters: session)
|
||||||
postResource(endpoint: "api/session/local", parameters: progress, callback: callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
|
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue