2022-08-19 22:15:06 -04:00
//
// P l a y e r P r o g r e s s S y n c . s w i f t
// A p p
//
// C r e a t e d b y R o n H e f t o n 8 / 1 9 / 2 2 .
//
import Foundation
import UIKit
import RealmSwift
class PlayerProgress {
2022-08-21 12:06:37 -04:00
public static let shared = PlayerProgress ( )
2022-08-22 17:04:48 -04:00
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
2022-08-19 22:15:06 -04:00
private init ( ) { }
2022-08-22 17:04:48 -04:00
// MARK: - S Y N C H O O K S
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 )
2022-08-19 22:15:06 -04:00
}
2022-08-21 12:06:37 -04:00
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 )
2022-08-19 22:15:06 -04:00
}
2022-08-21 12:06:37 -04:00
public func syncFromServer ( ) async {
2022-08-19 23:00:40 -04:00
let backgroundToken = await UIApplication . shared . beginBackgroundTask ( withName : " ABS:syncFromServer " )
2022-08-19 22:15:06 -04:00
await updateLocalSessionFromServerMediaProgress ( )
await UIApplication . shared . endBackgroundTask ( backgroundToken )
}
2022-08-22 17:04:48 -04:00
// MARK: - S Y N C L O G I C
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 } // P r e v e n t b a d d a t a o n p l a y e r s t o p
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
}
2022-08-21 12:06:37 -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 {
// L o c a l m e d i a p r o g r e s s s h o u l d h a v e b e e n c r e a t e d
// I f w e ' r e h e r e , i t m e a n s a l i b r a r y i d i s i n v a l i d
return
}
localMediaProgress . updateFromPlaybackSession ( session )
Database . shared . saveLocalMediaProgress ( localMediaProgress )
NSLog ( " Local progress saved to the database " )
2022-08-19 22:15:06 -04:00
2022-08-19 23:00:40 -04:00
// S e n d t h e l o c a l p r o g r e s s b a c k t o f r o n t - e n d
NotificationCenter . default . post ( name : NSNotification . Name ( PlayerEvents . localProgress . rawValue ) , object : nil )
2022-08-19 22:15:06 -04:00
}
2022-08-21 12:06:37 -04:00
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 {
// I f r e q u i r e d , r a t e l i m i t r e q u e s t s b a s e d o n s e s s i o n l a s t u p d a t e
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 {
// S k i p p i n g s y n c s i n c e l a s t o c c u r r e d w i t h i n s e s s i o n s y n c t i m e
return
}
}
2022-08-19 23:00:40 -04:00
NSLog ( " Sending sessionId( \( session . id ) ) to server " )
2022-08-19 22:15:06 -04:00
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 )
}
// R e m o v e o l d s e s s i o n s a f t e r t h e y s y n c e d w i t h t h e s e r v e r
if success && ! session . isActiveSession {
NSLog ( " Deleting sessionId( \( session . id ) ) as is no longer active " )
session . thaw ( ) ? . delete ( )
}
2022-08-19 22:15:06 -04:00
}
private static func updateLocalSessionFromServerMediaProgress ( ) async {
2022-08-21 10:59:43 -04:00
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
}
2022-08-19 22:15:06 -04:00
// F e t c h t h e c u r r e n t p r o g r e s s
let progress = await ApiClient . getMediaProgress ( libraryItemId : session . libraryItemId ! , episodeId : session . episodeId )
2022-08-21 10:59:43 -04:00
guard let progress = progress else {
NSLog ( " updateLocalSessionFromServerMediaProgress: No progress object " )
return
}
2022-08-19 22:15:06 -04:00
// D e t e r m i n e w h i c h s e s s i o n i s n e w e r
let serverLastUpdate = progress . lastUpdate
2022-08-21 10:59:43 -04:00
guard let localLastUpdate = session . updatedAt else {
NSLog ( " updateLocalSessionFromServerMediaProgress: No local session updatedAt " )
return
}
2022-08-19 22:15:06 -04:00
let serverCurrentTime = progress . currentTime
let localCurrentTime = session . currentTime
let serverIsNewerThanLocal = serverLastUpdate > localLastUpdate
let currentTimeIsDifferent = serverCurrentTime != localCurrentTime
// U p d a t e t h e s e s s i o n , i f n e e d e d
if serverIsNewerThanLocal && currentTimeIsDifferent {
2022-08-21 10:59:43 -04:00
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 }
2022-08-19 22:15:06 -04:00
session . update {
session . currentTime = serverCurrentTime
session . updatedAt = serverLastUpdate
}
2022-08-21 10:59:43 -04:00
NSLog ( " updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime= \( serverCurrentTime ) previousCurrentTime= \( localCurrentTime ) " )
2022-08-19 22:15:06 -04:00
PlayerHandler . seek ( amount : session . currentTime )
2022-08-21 10:59:43 -04:00
} else {
NSLog ( " updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress " )
2022-08-19 22:15:06 -04:00
}
}
}