mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-30 14:49:47 +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()
|
"value": PlayerHandler.getCurrentTime()
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func sendSleepTimerSet() {
|
@objc func sendSleepTimerSet() {
|
||||||
self.notifyListeners("onSleepTimerSet", data: [
|
self.notifyListeners("onSleepTimerSet", data: [
|
||||||
"value": PlayerHandler.remainingSleepTime
|
"value": PlayerHandler.remainingSleepTime
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func onLocalMediaProgressUpdate(_ localMediaProgress: [String: Any]) {
|
||||||
|
self.notifyListeners("onLocalMediaProgressUpdate", data: localMediaProgress)
|
||||||
|
}
|
||||||
|
|
||||||
@objc func onPlaybackFailed() {
|
@objc func onPlaybackFailed() {
|
||||||
if (PlayerHandler.getPlayMethod() == PlayMethod.directplay.rawValue) {
|
if (PlayerHandler.getPlayMethod() == PlayMethod.directplay.rawValue) {
|
||||||
let playbackSession = PlayerHandler.getPlaybackSession()
|
let playbackSession = PlayerHandler.getPlaybackSession()
|
||||||
|
@ -219,6 +224,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
"playWhenReady": playWhenReady,
|
"playWhenReady": playWhenReady,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func sendPlaybackSession(session: [String: Any]) {
|
@objc func sendPlaybackSession(session: [String: Any]) {
|
||||||
self.notifyListeners("onPlaybackSession", data: session)
|
self.notifyListeners("onPlaybackSession", data: session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ public class AbsDatabase: CAPPlugin {
|
||||||
return
|
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 {
|
guard var localMediaProgress = localMediaProgress else {
|
||||||
call.reject("Local media progress not found or created")
|
call.reject("Local media progress not found or created")
|
||||||
return
|
return
|
||||||
|
@ -190,7 +190,7 @@ public class AbsDatabase: CAPPlugin {
|
||||||
|
|
||||||
NSLog("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
|
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 {
|
guard var localMediaProgress = localMediaProgress else {
|
||||||
call.resolve(["error": "Library Item not found"])
|
call.resolve(["error": "Library Item not found"])
|
||||||
return
|
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) {
|
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
||||||
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
||||||
let enableAltView = call.getBool("enableAltView") ?? false
|
let enableAltView = call.getBool("enableAltView") ?? false
|
||||||
|
|
|
@ -196,4 +196,16 @@ extension LocalMediaProgress {
|
||||||
self.finishedAt = serverMediaProgress.finishedAt
|
self.finishedAt = serverMediaProgress.finishedAt
|
||||||
self.startedAt = serverMediaProgress.startedAt
|
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
|
import Foundation
|
||||||
|
|
||||||
struct PlaybackSession: Decodable, Encodable {
|
struct PlaybackSession: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var userId: String?
|
var userId: String?
|
||||||
var libraryItemId: String?
|
var libraryItemId: String?
|
||||||
|
@ -30,10 +30,21 @@ struct PlaybackSession: Decodable, Encodable {
|
||||||
var serverConnectionConfigId: String?
|
var serverConnectionConfigId: String?
|
||||||
var serverAddress: 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 totalDuration: Double {
|
||||||
var total = 0.0
|
var total = 0.0
|
||||||
self.audioTracks.forEach { total += $0.duration }
|
self.audioTracks.forEach { total += $0.duration }
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
var progress: Double { self.currentTime / self.totalDuration }
|
var progress: Double { self.currentTime / self.totalDuration }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class PlayerHandler {
|
||||||
private static var player: AudioPlayer?
|
private static var player: AudioPlayer?
|
||||||
private static var session: PlaybackSession?
|
private static var session: PlaybackSession?
|
||||||
private static var timer: Timer?
|
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
|
private static var _remainingSleepTime: Int? = nil
|
||||||
public static var remainingSleepTime: Int? {
|
public static var remainingSleepTime: Int? {
|
||||||
|
@ -69,6 +69,7 @@ class PlayerHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func stopPlayback() {
|
public static func stopPlayback() {
|
||||||
player?.destroy()
|
player?.destroy()
|
||||||
player = nil
|
player = nil
|
||||||
|
@ -82,12 +83,15 @@ class PlayerHandler {
|
||||||
public static func getCurrentTime() -> Double? {
|
public static func getCurrentTime() -> Double? {
|
||||||
self.player?.getCurrentTime()
|
self.player?.getCurrentTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func setPlaybackSpeed(speed: Float) {
|
public static func setPlaybackSpeed(speed: Float) {
|
||||||
self.player?.setPlaybackRate(speed)
|
self.player?.setPlaybackRate(speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getPlayMethod() -> Int? {
|
public static func getPlayMethod() -> Int? {
|
||||||
self.player?.getPlayMethod()
|
self.player?.getPlayMethod()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getPlaybackSession() -> PlaybackSession? {
|
public static func getPlaybackSession() -> PlaybackSession? {
|
||||||
self.player?.getPlaybackSession()
|
self.player?.getPlaybackSession()
|
||||||
}
|
}
|
||||||
|
@ -100,6 +104,7 @@ class PlayerHandler {
|
||||||
let destinationTime = player.getCurrentTime() + amount
|
let destinationTime = player.getCurrentTime() + amount
|
||||||
player.seek(destinationTime, from: "handler")
|
player.seek(destinationTime, from: "handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func seekBackward(amount: Double) {
|
public static func seekBackward(amount: Double) {
|
||||||
guard let player = player else {
|
guard let player = player else {
|
||||||
return
|
return
|
||||||
|
@ -108,9 +113,11 @@ class PlayerHandler {
|
||||||
let destinationTime = player.getCurrentTime() - amount
|
let destinationTime = player.getCurrentTime() - amount
|
||||||
player.seek(destinationTime, from: "handler")
|
player.seek(destinationTime, from: "handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func seek(amount: Double) {
|
public static func seek(amount: Double) {
|
||||||
player?.seek(amount, from: "handler")
|
player?.seek(amount, from: "handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getMetdata() -> [String: Any] {
|
public static func getMetdata() -> [String: Any] {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
syncProgress()
|
syncProgress()
|
||||||
|
@ -140,15 +147,15 @@ class PlayerHandler {
|
||||||
remainingSleepTime! -= 1
|
remainingSleepTime! -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func syncProgress() {
|
public static func syncProgress() {
|
||||||
if session == nil { return }
|
if session == nil { return }
|
||||||
guard let player = player else { return }
|
guard let player = player else { return }
|
||||||
|
|
||||||
|
// Prevent a sync at the current time
|
||||||
let playerCurrentTime = player.getCurrentTime()
|
let playerCurrentTime = player.getCurrentTime()
|
||||||
if (lastSyncReport != nil && lastSyncReport?.currentTime == playerCurrentTime) {
|
let hasSyncAtCurrentTime = lastSyncReport?.currentTime.isEqual(to: playerCurrentTime) ?? false
|
||||||
// No need to syncProgress
|
if hasSyncAtCurrentTime { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent multiple sync requests
|
// Prevent multiple sync requests
|
||||||
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
|
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
|
||||||
|
@ -158,15 +165,44 @@ class PlayerHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSyncTime = Date().timeIntervalSince1970 // seconds
|
lastSyncTime = Date().timeIntervalSince1970 // seconds
|
||||||
|
|
||||||
let report = PlaybackReport(currentTime: playerCurrentTime, duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
let report = PlaybackReport(currentTime: playerCurrentTime, duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
||||||
|
|
||||||
session!.currentTime = playerCurrentTime
|
session!.currentTime = playerCurrentTime
|
||||||
listeningTimePassedSinceLastSync = 0
|
listeningTimePassedSinceLastSync = 0
|
||||||
lastSyncReport = report
|
lastSyncReport = report
|
||||||
|
|
||||||
// TODO: check if online
|
let sessionIsLocal = session!.isLocal
|
||||||
|
if !sessionIsLocal {
|
||||||
|
if Connectivity.isConnectedToInternet {
|
||||||
NSLog("sending playback report")
|
NSLog("sending playback report")
|
||||||
ApiClient.reportPlaybackProgress(report: report, sessionId: session!.id)
|
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) {
|
if (Store.serverConfig == nil) {
|
||||||
NSLog("Server config not set")
|
NSLog("Server config not set")
|
||||||
callback?(false)
|
callback?(false)
|
||||||
|
@ -144,6 +144,10 @@ class ApiClient {
|
||||||
try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: nil)
|
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) {
|
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
|
||||||
let localMediaProgressList = Database.shared.getAllLocalMediaProgress().filter {
|
let localMediaProgressList = Database.shared.getAllLocalMediaProgress().filter {
|
||||||
$0.serverConnectionConfigId == Store.serverConfig?.id
|
$0.serverConnectionConfigId == Store.serverConfig?.id
|
||||||
|
@ -205,3 +209,10 @@ struct LocalMediaProgressSyncResultsPayload: Codable {
|
||||||
var numServerProgressUpdates: Int?
|
var numServerProgressUpdates: Int?
|
||||||
var numLocalProgressUpdates: 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