mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-30 06:39:35 +02:00
Sync local progress with server progress
This commit is contained in:
parent
8d38f3358e
commit
ef661bba37
6 changed files with 89 additions and 26 deletions
|
@ -179,12 +179,17 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||
"value": PlayerHandler.getCurrentTime()
|
||||
])
|
||||
}
|
||||
|
||||
@objc func sendSleepTimerSet() {
|
||||
self.notifyListeners("onSleepTimerSet", data: [
|
||||
"value": PlayerHandler.remainingSleepTime
|
||||
])
|
||||
}
|
||||
|
||||
@objc func onLocalMediaProgressUpdate(_ localMediaProgress: [String: Any]) {
|
||||
self.notifyListeners("onLocalMediaProgressUpdate", data: localMediaProgress)
|
||||
}
|
||||
|
||||
@objc func onPlaybackFailed() {
|
||||
if (PlayerHandler.getPlayMethod() == PlayMethod.directplay.rawValue) {
|
||||
let playbackSession = PlayerHandler.getPlaybackSession()
|
||||
|
@ -219,6 +224,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||
"playWhenReady": playWhenReady,
|
||||
])
|
||||
}
|
||||
|
||||
@objc func sendPlaybackSession(session: [String: Any]) {
|
||||
self.notifyListeners("onPlaybackSession", data: session)
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ public class AbsDatabase: CAPPlugin {
|
|||
return
|
||||
}
|
||||
|
||||
let localMediaProgress = fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||
guard var localMediaProgress = localMediaProgress else {
|
||||
call.reject("Local media progress not found or created")
|
||||
return
|
||||
|
@ -190,7 +190,7 @@ public class AbsDatabase: CAPPlugin {
|
|||
|
||||
NSLog("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
|
||||
|
||||
let localMediaProgress = fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||
guard var localMediaProgress = localMediaProgress else {
|
||||
call.resolve(["error": "Library Item not found"])
|
||||
return
|
||||
|
@ -218,19 +218,6 @@ public class AbsDatabase: CAPPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
private func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) -> LocalMediaProgress? {
|
||||
if let localMediaProgressId = localMediaProgressId {
|
||||
return Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId)
|
||||
} else if let localLibraryItemId = localLibraryItemId {
|
||||
guard let localLibraryItem = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) else { return nil }
|
||||
let episode = localLibraryItem.getPodcastEpisode(episodeId: localEpisodeId)
|
||||
return LocalMediaProgress(localLibraryItem: localLibraryItem, episode: episode)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
||||
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
||||
let enableAltView = call.getBool("enableAltView") ?? false
|
||||
|
|
|
@ -196,4 +196,16 @@ extension LocalMediaProgress {
|
|||
self.finishedAt = serverMediaProgress.finishedAt
|
||||
self.startedAt = serverMediaProgress.startedAt
|
||||
}
|
||||
|
||||
static func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) -> LocalMediaProgress? {
|
||||
if let localMediaProgressId = localMediaProgressId {
|
||||
return Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId)
|
||||
} else if let localLibraryItemId = localLibraryItemId {
|
||||
guard let localLibraryItem = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) else { return nil }
|
||||
let episode = localLibraryItem.getPodcastEpisode(episodeId: localEpisodeId)
|
||||
return LocalMediaProgress(localLibraryItem: localLibraryItem, episode: episode)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct PlaybackSession: Decodable, Encodable {
|
||||
struct PlaybackSession: Codable {
|
||||
var id: String
|
||||
var userId: String?
|
||||
var libraryItemId: String?
|
||||
|
@ -30,10 +30,21 @@ struct PlaybackSession: Decodable, Encodable {
|
|||
var serverConnectionConfigId: String?
|
||||
var serverAddress: String?
|
||||
|
||||
var isLocal: Bool { self.localLibraryItem != nil }
|
||||
|
||||
var localMediaProgressId: String {
|
||||
if let episodeId = episodeId {
|
||||
return "\(localLibraryItem!.id)-\(episodeId)"
|
||||
} else {
|
||||
return localLibraryItem!.id
|
||||
}
|
||||
}
|
||||
|
||||
var totalDuration: Double {
|
||||
var total = 0.0
|
||||
self.audioTracks.forEach { total += $0.duration }
|
||||
return total
|
||||
}
|
||||
|
||||
var progress: Double { self.currentTime / self.totalDuration }
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class PlayerHandler {
|
|||
private static var player: AudioPlayer?
|
||||
private static var session: PlaybackSession?
|
||||
private static var timer: Timer?
|
||||
private static var lastSyncTime:Double = 0.0
|
||||
private static var lastSyncTime: Double = 0.0
|
||||
|
||||
private static var _remainingSleepTime: Int? = nil
|
||||
public static var remainingSleepTime: Int? {
|
||||
|
@ -69,6 +69,7 @@ class PlayerHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func stopPlayback() {
|
||||
player?.destroy()
|
||||
player = nil
|
||||
|
@ -82,12 +83,15 @@ class PlayerHandler {
|
|||
public static func getCurrentTime() -> Double? {
|
||||
self.player?.getCurrentTime()
|
||||
}
|
||||
|
||||
public static func setPlaybackSpeed(speed: Float) {
|
||||
self.player?.setPlaybackRate(speed)
|
||||
}
|
||||
|
||||
public static func getPlayMethod() -> Int? {
|
||||
self.player?.getPlayMethod()
|
||||
}
|
||||
|
||||
public static func getPlaybackSession() -> PlaybackSession? {
|
||||
self.player?.getPlaybackSession()
|
||||
}
|
||||
|
@ -100,6 +104,7 @@ class PlayerHandler {
|
|||
let destinationTime = player.getCurrentTime() + amount
|
||||
player.seek(destinationTime, from: "handler")
|
||||
}
|
||||
|
||||
public static func seekBackward(amount: Double) {
|
||||
guard let player = player else {
|
||||
return
|
||||
|
@ -108,9 +113,11 @@ class PlayerHandler {
|
|||
let destinationTime = player.getCurrentTime() - amount
|
||||
player.seek(destinationTime, from: "handler")
|
||||
}
|
||||
|
||||
public static func seek(amount: Double) {
|
||||
player?.seek(amount, from: "handler")
|
||||
}
|
||||
|
||||
public static func getMetdata() -> [String: Any] {
|
||||
DispatchQueue.main.async {
|
||||
syncProgress()
|
||||
|
@ -140,15 +147,15 @@ class PlayerHandler {
|
|||
remainingSleepTime! -= 1
|
||||
}
|
||||
}
|
||||
|
||||
public static func syncProgress() {
|
||||
if session == nil { return }
|
||||
guard let player = player else { return }
|
||||
|
||||
// Prevent a sync at the current time
|
||||
let playerCurrentTime = player.getCurrentTime()
|
||||
if (lastSyncReport != nil && lastSyncReport?.currentTime == playerCurrentTime) {
|
||||
// No need to syncProgress
|
||||
return
|
||||
}
|
||||
let hasSyncAtCurrentTime = lastSyncReport?.currentTime.isEqual(to: playerCurrentTime) ?? false
|
||||
if hasSyncAtCurrentTime { return }
|
||||
|
||||
// Prevent multiple sync requests
|
||||
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
|
||||
|
@ -158,15 +165,44 @@ class PlayerHandler {
|
|||
}
|
||||
|
||||
lastSyncTime = Date().timeIntervalSince1970 // seconds
|
||||
|
||||
let report = PlaybackReport(currentTime: playerCurrentTime, duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
||||
|
||||
session!.currentTime = playerCurrentTime
|
||||
listeningTimePassedSinceLastSync = 0
|
||||
lastSyncReport = report
|
||||
|
||||
// TODO: check if online
|
||||
let sessionIsLocal = session!.isLocal
|
||||
if !sessionIsLocal {
|
||||
if Connectivity.isConnectedToInternet {
|
||||
NSLog("sending playback report")
|
||||
ApiClient.reportPlaybackProgress(report: report, sessionId: session!.id)
|
||||
}
|
||||
} else {
|
||||
if let localMediaProgress = syncLocalProgress() {
|
||||
if Connectivity.isConnectedToInternet {
|
||||
ApiClient.reportLocalMediaProgress(localMediaProgress) { success in
|
||||
NSLog("Synced local media progress: \(success)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func syncLocalProgress() -> LocalMediaProgress? {
|
||||
guard let session = session else { return nil }
|
||||
|
||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
|
||||
guard var localMediaProgress = localMediaProgress else {
|
||||
// Local media progress should have been created
|
||||
// If we're here, it means a library id is invalid
|
||||
return nil
|
||||
}
|
||||
|
||||
localMediaProgress.updateFromPlaybackSession(session)
|
||||
Database.shared.saveLocalMediaProgress(localMediaProgress)
|
||||
|
||||
// TODO: Send local media progress back to UI
|
||||
|
||||
return localMediaProgress
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class ApiClient {
|
|||
}
|
||||
}
|
||||
|
||||
public static func postResource(endpoint: String, parameters: [String: String], callback: ((_ success: Bool) -> Void)?) {
|
||||
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
NSLog("Server config not set")
|
||||
callback?(false)
|
||||
|
@ -144,6 +144,10 @@ class ApiClient {
|
|||
try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: nil)
|
||||
}
|
||||
|
||||
public static func reportLocalMediaProgress(_ localMediaProgress: LocalMediaProgress, callback: @escaping (_ success: Bool) -> Void) {
|
||||
postResource(endpoint: "/api/session/local", parameters: localMediaProgress, callback: callback)
|
||||
}
|
||||
|
||||
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
|
||||
let localMediaProgressList = Database.shared.getAllLocalMediaProgress().filter {
|
||||
$0.serverConnectionConfigId == Store.serverConfig?.id
|
||||
|
@ -205,3 +209,10 @@ struct LocalMediaProgressSyncResultsPayload: Codable {
|
|||
var numServerProgressUpdates: Int?
|
||||
var numLocalProgressUpdates: Int?
|
||||
}
|
||||
|
||||
struct Connectivity {
|
||||
static private let sharedInstance = NetworkReachabilityManager()!
|
||||
static var isConnectedToInternet:Bool {
|
||||
return self.sharedInstance.isReachable
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue