diff --git a/ios/App/App/plugins/AbsAudioPlayer.swift b/ios/App/App/plugins/AbsAudioPlayer.swift index 6cd8b2e4..32c989f3 100644 --- a/ios/App/App/plugins/AbsAudioPlayer.swift +++ b/ios/App/App/plugins/AbsAudioPlayer.swift @@ -40,7 +40,7 @@ public class AbsAudioPlayer: CAPPlugin { // Fetch the most recent active session let activeSession = try await Realm().objects(PlaybackSession.self).where({ $0.isActiveSession == true }).last if let activeSession = activeSession { - await PlayerProgress.syncLocalFromServer() + await PlayerProgress.syncFromServer() try self.startPlaybackSession(activeSession, playWhenReady: false, playbackRate: PlayerSettings.main().playbackRate) } } catch { diff --git a/ios/App/Shared/player/PlayerHandler.swift b/ios/App/Shared/player/PlayerHandler.swift index 87752fff..d8be0d3f 100644 --- a/ios/App/Shared/player/PlayerHandler.swift +++ b/ios/App/Shared/player/PlayerHandler.swift @@ -10,7 +10,7 @@ import RealmSwift class PlayerHandler { private static var player: AudioPlayer? - private static var timer: Timer? + private static var playingTimer: Timer? private static var pausedTimer: Timer? private static var lastSyncTime: Double = 0.0 @@ -240,64 +240,11 @@ class PlayerHandler { listeningTimePassedSinceLastSync = 0 // Persist items in the database and sync to the server - if session.isLocal { syncLocalProgress() } - syncServerProgress() + if session.isLocal { PlayerProgress.syncFromPlayer() } + Task { await PlayerProgress.syncToServer() } } - private static func syncLocalProgress() { - DispatchQueue.global(qos: .utility).async { - 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() } + @objc public static func syncServerProgressDuringPause() { + Task { await PlayerProgress.syncFromServer() } } } diff --git a/ios/App/Shared/player/PlayerProgress.swift b/ios/App/Shared/player/PlayerProgress.swift index 9266bf61..ef1a5d46 100644 --- a/ios/App/Shared/player/PlayerProgress.swift +++ b/ios/App/Shared/player/PlayerProgress.swift @@ -13,16 +13,18 @@ class PlayerProgress { 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 { - let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:updateLocalSessionFromServerMediaProgress") + public static func syncFromServer() async { + let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer") await updateLocalSessionFromServerMediaProgress() await UIApplication.shared.endBackgroundTask(backgroundToken) } @@ -32,16 +34,54 @@ class PlayerProgress { } 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 { 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 let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId) @@ -58,6 +98,7 @@ class PlayerProgress { // Update the session, if needed if serverIsNewerThanLocal && currentTimeIsDifferent { + guard let session = session.thaw() else { return } session.update { session.currentTime = serverCurrentTime session.updatedAt = serverLastUpdate diff --git a/ios/App/Shared/util/ApiClient.swift b/ios/App/Shared/util/ApiClient.swift index ad8be8b0..2d5258b0 100644 --- a/ios/App/Shared/util/ApiClient.swift +++ b/ios/App/Shared/util/ApiClient.swift @@ -59,6 +59,14 @@ class ApiClient { } } + public static func postResource(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(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) { if (Store.serverConfig == nil) { NSLog("Server config not set") @@ -170,13 +178,12 @@ class ApiClient { } } - public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String, callback: @escaping (_ success: Bool) -> Void) { - try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: callback) + public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) async -> Bool { + return await postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report) } - public static func reportLocalPlaybackProgress(_ session: PlaybackSession, callback: @escaping (_ success: Bool) -> Void) { - let progress = session.freeze() - postResource(endpoint: "api/session/local", parameters: progress, callback: callback) + public static func reportLocalPlaybackProgress(_ session: PlaybackSession) async -> Bool { + return await postResource(endpoint: "api/session/local", parameters: session) } public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {