Fix all kinds of syncing errors

This commit is contained in:
ronaldheft 2022-08-13 12:41:20 -04:00
parent c029e519e9
commit e0e2f02e0b
6 changed files with 83 additions and 43 deletions

View file

@ -157,13 +157,11 @@ public class AbsDatabase: CAPPlugin {
let localMediaProgressId = call.getString("localMediaProgressId")
do {
guard let localLibraryItemId = localLibraryItemId else {
call.reject("localLibraryItemId not specified")
return
}
guard let serverMediaProgress = serverMediaProgress else {
call.reject("serverMediaProgress not specified")
return
return call.reject("serverMediaProgress not specified")
}
guard localLibraryItemId != nil || localMediaProgressId != nil else {
return call.reject("localLibraryItemId or localMediaProgressId must be specified")
}
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)

View file

@ -342,13 +342,13 @@ struct MediaProgress: Realmable, Codable {
id = try values.decode(String.self, forKey: .id)
libraryItemId = try values.decode(String.self, forKey: .libraryItemId)
episodeId = try? values.decode(String.self, forKey: .episodeId)
duration = try MediaProgress.doubleOrStringDecoder(from: decoder, with: values, key: .duration)
progress = try MediaProgress.doubleOrStringDecoder(from: decoder, with: values, key: .progress)
currentTime = try MediaProgress.doubleOrStringDecoder(from: decoder, with: values, key: .currentTime)
duration = try values.doubleOrStringDecoder(key: .duration)
progress = try values.doubleOrStringDecoder(key: .progress)
currentTime = try values.doubleOrStringDecoder(key: .currentTime)
isFinished = try values.decode(Bool.self, forKey: .isFinished)
lastUpdate = try MediaProgress.intOrStringDecoder(from: decoder, with: values, key: .lastUpdate)
startedAt = try MediaProgress.intOrStringDecoder(from: decoder, with: values, key: .startedAt)
finishedAt = try? MediaProgress.intOrStringDecoder(from: decoder, with: values, key: .finishedAt)
lastUpdate = try values.intOrStringDecoder(key: .lastUpdate)
startedAt = try values.intOrStringDecoder(key: .startedAt)
finishedAt = try? values.intOrStringDecoder(key: .finishedAt)
}
func encode(to encoder: Encoder) throws {
@ -364,24 +364,6 @@ struct MediaProgress: Realmable, Codable {
try container.encode(startedAt, forKey: .startedAt)
try container.encode(finishedAt, forKey: .finishedAt)
}
static private func doubleOrStringDecoder(from decoder: Decoder, with values: KeyedDecodingContainer<CodingKeys>, key: MediaProgress.CodingKeys) throws -> Double {
do {
return try values.decode(Double.self, forKey: key)
} catch {
let stringDuration = try values.decode(String.self, forKey: key)
return Double(stringDuration) ?? 0.0
}
}
static private func intOrStringDecoder(from decoder: Decoder, with values: KeyedDecodingContainer<CodingKeys>, key: MediaProgress.CodingKeys) throws -> Int {
do {
return try values.decode(Int.self, forKey: key)
} catch {
let stringDuration = try values.decode(String.self, forKey: key)
return Int(stringDuration) ?? 0
}
}
}
struct PlaybackMetadata: Realmable, Codable {

View file

@ -267,6 +267,7 @@ class AudioPlayer: NSObject {
let startOffset = audioTrack.startOffset ?? 0.0
return startOffset + currentTrackTime
}
public func getPlayMethod() -> Int {
return self.playbackSession.playMethod
}

View file

@ -71,12 +71,16 @@ class PlayerHandler {
}
public static func stopPlayback() {
player?.destroy()
player = nil
// Pause playback first, so we can sync our current progress
player?.pause()
// Stop updating progress before we destory the player, so we don't receive bad data
timer?.invalidate()
timer = nil
player?.destroy()
player = nil
NowPlayingInfo.shared.reset()
}
@ -164,6 +168,9 @@ class PlayerHandler {
return
}
// Prevent a sync if we got junk data from the player (occurs when exiting out of memory
guard !playerCurrentTime.isNaN else { return }
lastSyncTime = Date().timeIntervalSince1970 // seconds
let report = PlaybackReport(currentTime: playerCurrentTime, duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)

View file

@ -38,6 +38,27 @@ class ApiClient {
}
}
public static func postResource<T: Encodable, U: Decodable>(endpoint: String, parameters: T, decodable: U.Type = U.self, callback: ((_ param: U) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
return
}
let headers: HTTPHeaders = [
"Authorization": "Bearer \(Store.serverConfig!.token)"
]
AF.request("\(Store.serverConfig!.address)/\(endpoint)", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, headers: headers).responseDecodable(of: decodable) { response in
switch response.result {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
print(error)
}
}
}
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
@ -62,7 +83,7 @@ class ApiClient {
}
}
public static func patchResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
public static func patchResource<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
callback?(false)
@ -121,7 +142,7 @@ class ApiClient {
}
}
ApiClient.postResource(endpoint: endpoint, parameters: [
let parameters: [String: Any] = [
"forceDirectPlay": !forceTranscode ? "1" : "",
"forceTranscode": forceTranscode ? "1" : "",
"mediaPlayer": "AVPlayer",
@ -130,7 +151,8 @@ class ApiClient {
"model": modelCode,
"clientVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
]
], decodable: PlaybackSession.self) { obj in
]
ApiClient.postResource(endpoint: endpoint, parameters: parameters, decodable: PlaybackSession.self) { obj in
var session = obj
session.serverConnectionConfigId = Store.serverConfig!.id
@ -145,7 +167,7 @@ class ApiClient {
}
public static func reportLocalMediaProgress(_ localMediaProgress: LocalMediaProgress, callback: @escaping (_ success: Bool) -> Void) {
postResource(endpoint: "/api/session/local", parameters: localMediaProgress, callback: callback)
postResource(endpoint: "api/session/local", parameters: localMediaProgress, callback: callback)
}
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
@ -156,8 +178,7 @@ class ApiClient {
if ( !localMediaProgressList.isEmpty ) {
let payload = LocalMediaProgressSyncPayload(localMediaProgress: localMediaProgressList)
NSLog("Sending sync local progress request with \(localMediaProgressList.count) progress items")
try? postResource(endpoint: "/api/me/sync-local-progress", parameters: payload.asDictionary(), decodable: MediaProgressSyncResponsePayload.self) { response in
postResource(endpoint: "api/me/sync-local-progress", parameters: payload, decodable: MediaProgressSyncResponsePayload.self) { response in
let resultsPayload = LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: localMediaProgressList.count, numServerProgressUpdates: response.numServerProgressUpdates, numLocalProgressUpdates: response.localProgressUpdates?.count)
NSLog("Media Progress Sync | \(String(describing: try? resultsPayload.asDictionary()))")
@ -177,7 +198,7 @@ class ApiClient {
public static func updateMediaProgress<T:Encodable>(libraryItemId: String, episodeId: String?, payload: T, callback: @escaping () -> Void) {
NSLog("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
let endpoint = episodeId?.isEmpty ?? true ? "/api/me/progress/\(libraryItemId)" : "/api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
patchResource(endpoint: endpoint, parameters: payload) { success in
callback()
}
@ -199,9 +220,19 @@ struct LocalMediaProgressSyncPayload: Codable {
var localMediaProgress: [LocalMediaProgress]
}
struct MediaProgressSyncResponsePayload: Codable {
struct MediaProgressSyncResponsePayload: Decodable {
var numServerProgressUpdates: Int?
var localProgressUpdates: [LocalMediaProgress]?
private enum CodingKeys : String, CodingKey {
case numServerProgressUpdates, localProgressUpdates
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
numServerProgressUpdates = try? values.intOrStringDecoder(key: .numServerProgressUpdates)
localProgressUpdates = try? values.decode([LocalMediaProgress].self, forKey: .localProgressUpdates)
}
}
struct LocalMediaProgressSyncResultsPayload: Codable {

View file

@ -8,6 +8,7 @@
import Foundation
import RealmSwift
import Capacitor
import CoreMedia
extension String: Error {}
@ -31,11 +32,31 @@ extension Collection where Iterator.Element: Encodable {
}
}
extension KeyedDecodingContainer {
func doubleOrStringDecoder(key: KeyedDecodingContainer<K>.Key) throws -> Double {
do {
return try decode(Double.self, forKey: key)
} catch {
let stringValue = try decode(String.self, forKey: key)
return Double(stringValue) ?? 0.0
}
}
func intOrStringDecoder(key: KeyedDecodingContainer<K>.Key) throws -> Int {
do {
return try decode(Int.self, forKey: key)
} catch {
let stringValue = try decode(String.self, forKey: key)
return Int(stringValue) ?? 0
}
}
}
extension CAPPluginCall {
func getJson<T: Decodable>(_ key: String, type: T.Type) -> T? {
guard let value = getString(key) else { return nil }
guard let valueData = value.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(type, from: valueData)
guard let value = getObject(key) else { return nil }
guard let json = try? JSONSerialization.data(withJSONObject: value) else { return nil }
return try? JSONDecoder().decode(type, from: json)
}
}