mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-06 02:55:58 +02:00
Fix all kinds of syncing errors
This commit is contained in:
parent
c029e519e9
commit
e0e2f02e0b
6 changed files with 83 additions and 43 deletions
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -267,6 +267,7 @@ class AudioPlayer: NSObject {
|
|||
let startOffset = audioTrack.startOffset ?? 0.0
|
||||
return startOffset + currentTrackTime
|
||||
}
|
||||
|
||||
public func getPlayMethod() -> Int {
|
||||
return self.playbackSession.playMethod
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue