advplyr.audiobookshelf-app/ios/App/Shared/player/PlayerProgress.swift

168 lines
7.2 KiB
Swift
Raw Normal View History

//
// PlayerProgressSync.swift
// App
//
// Created by Ron Heft on 8/19/22.
//
import Foundation
import UIKit
import RealmSwift
class PlayerProgress {
public static let shared = PlayerProgress()
2022-08-22 17:04:48 -04:00
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
private init() {}
2022-08-22 17:04:48 -04:00
// MARK: - SYNC HOOKS
public func syncFromPlayer(currentTime: Double, includesPlayProgress: Bool, isStopping: Bool) async {
2022-08-21 12:36:29 -04:00
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromPlayer")
2022-08-22 17:04:48 -04:00
let session = await updateLocalSessionFromPlayer(currentTime: currentTime, includesPlayProgress: includesPlayProgress)
2022-08-19 23:00:40 -04:00
updateLocalMediaProgressFromLocalSession()
2022-08-22 17:04:48 -04:00
if let session = session {
await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
}
2022-08-21 12:36:29 -04:00
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
public func syncToServer() async {
2022-08-19 23:00:40 -04:00
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncToServer")
updateAllServerSessionFromLocalSession()
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
public func syncFromServer() async {
2022-08-19 23:00:40 -04:00
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer")
await updateLocalSessionFromServerMediaProgress()
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
2022-08-22 17:04:48 -04:00
// MARK: - SYNC LOGIC
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) async -> PlaybackSession? {
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
2022-08-22 17:27:55 -04:00
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
2022-08-21 12:36:29 -04:00
2022-08-22 17:04:48 -04:00
let now = Date().timeIntervalSince1970 * 1000
let lastUpdate = session.updatedAt ?? now
let timeSinceLastUpdate = now - lastUpdate
session.update {
session.currentTime = currentTime
session.updatedAt = now
if includesPlayProgress {
session.timeListening += timeSinceLastUpdate
}
}
return session.freeze()
2022-08-21 12:36:29 -04:00
}
private func updateLocalMediaProgressFromLocalSession() {
2022-08-19 23:00:40 -04:00
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")
2022-08-19 23:00:40 -04:00
// Send the local progress back to front-end
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
}
private func updateAllServerSessionFromLocalSession() {
2022-08-19 23:00:40 -04:00
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) }
}
}
2022-08-22 17:04:48 -04:00
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async {
// If required, rate limit requests based on session last update
if rateLimitSync {
let now = Date().timeIntervalSince1970 * 1000
let lastUpdate = session.updatedAt ?? now
let timeSinceLastSync = now - lastUpdate
let timeBetweenSessionSync = PlayerProgress.TIME_BETWEEN_SESSION_SYNC_IN_SECONDS * 1000
guard timeSinceLastSync > timeBetweenSessionSync else {
// Skipping sync since last occurred within session sync time
return
}
}
2022-08-19 23:00:40 -04:00
NSLog("Sending sessionId(\(session.id)) to server")
2022-08-19 23:00:40 -04:00
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("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
guard let session = try! await Realm().objects(PlaybackSession.self).last(where: { $0.isActiveSession == true })?.freeze() else {
NSLog("updateLocalSessionFromServerMediaProgress: Failed to get session")
return
}
// Fetch the current progress
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
guard let progress = progress else {
NSLog("updateLocalSessionFromServerMediaProgress: No progress object")
return
}
// Determine which session is newer
let serverLastUpdate = progress.lastUpdate
guard let localLastUpdate = session.updatedAt else {
NSLog("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
return
}
let serverCurrentTime = progress.currentTime
let localCurrentTime = session.currentTime
let serverIsNewerThanLocal = serverLastUpdate > localLastUpdate
let currentTimeIsDifferent = serverCurrentTime != localCurrentTime
// Update the session, if needed
if serverIsNewerThanLocal && currentTimeIsDifferent {
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
2022-08-19 23:00:40 -04:00
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)
} else {
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
}
}
}