mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 07:07:22 +02:00
Sync playback position (online only)
This commit is contained in:
@@ -9,10 +9,10 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||||
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */; };
|
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */; };
|
||||||
|
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90295E280968E700E1D427 /* PlaybackReport.swift */; };
|
||||||
3AB34053280829BF0039308B /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34052280829BF0039308B /* Extensions.swift */; };
|
3AB34053280829BF0039308B /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34052280829BF0039308B /* Extensions.swift */; };
|
||||||
3AB34055280832720039308B /* PlayerEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34054280832720039308B /* PlayerEvents.swift */; };
|
3AB34055280832720039308B /* PlayerEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34054280832720039308B /* PlayerEvents.swift */; };
|
||||||
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */; };
|
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */; };
|
||||||
3ABF580B2805A837005DFBE5 /* PlaybackReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF580A2805A837005DFBE5 /* PlaybackReport.swift */; };
|
|
||||||
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF618E2804325C0070250E /* PlayerHandler.swift */; };
|
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF618E2804325C0070250E /* PlayerHandler.swift */; };
|
||||||
3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE428043E50006DB301 /* AbsDatabase.swift */; };
|
3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE428043E50006DB301 /* AbsDatabase.swift */; };
|
||||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE628043E72006DB301 /* AbsDatabase.m */; };
|
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE628043E72006DB301 /* AbsDatabase.m */; };
|
||||||
@@ -35,10 +35,10 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
||||||
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
||||||
|
3A90295E280968E700E1D427 /* PlaybackReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackReport.swift; sourceTree = "<group>"; };
|
||||||
3AB34052280829BF0039308B /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
3AB34052280829BF0039308B /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||||
3AB34054280832720039308B /* PlayerEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerEvents.swift; sourceTree = "<group>"; };
|
3AB34054280832720039308B /* PlayerEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerEvents.swift; sourceTree = "<group>"; };
|
||||||
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSession.swift; sourceTree = "<group>"; };
|
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSession.swift; sourceTree = "<group>"; };
|
||||||
3ABF580A2805A837005DFBE5 /* PlaybackReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackReport.swift; sourceTree = "<group>"; };
|
|
||||||
3ABF618E2804325C0070250E /* PlayerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHandler.swift; sourceTree = "<group>"; };
|
3ABF618E2804325C0070250E /* PlayerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHandler.swift; sourceTree = "<group>"; };
|
||||||
3AD4FCE428043E50006DB301 /* AbsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsDatabase.swift; sourceTree = "<group>"; };
|
3AD4FCE428043E50006DB301 /* AbsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsDatabase.swift; sourceTree = "<group>"; };
|
||||||
3AD4FCE628043E72006DB301 /* AbsDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsDatabase.m; sourceTree = "<group>"; };
|
3AD4FCE628043E72006DB301 /* AbsDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsDatabase.m; sourceTree = "<group>"; };
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
children = (
|
children = (
|
||||||
3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */,
|
3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */,
|
||||||
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */,
|
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */,
|
||||||
3ABF580A2805A837005DFBE5 /* PlaybackReport.swift */,
|
3A90295E280968E700E1D427 /* PlaybackReport.swift */,
|
||||||
);
|
);
|
||||||
path = models;
|
path = models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -296,10 +296,10 @@
|
|||||||
files = (
|
files = (
|
||||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
||||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||||
|
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
|
||||||
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
|
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
|
||||||
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */,
|
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */,
|
||||||
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */,
|
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */,
|
||||||
3ABF580B2805A837005DFBE5 /* PlaybackReport.swift in Sources */,
|
|
||||||
3AF1970E2806E3CA0096F747 /* AbsAudioPlayer.swift in Sources */,
|
3AF1970E2806E3CA0096F747 /* AbsAudioPlayer.swift in Sources */,
|
||||||
3AD4FCE928043FD7006DB301 /* ServerConnectionConfig.swift in Sources */,
|
3AD4FCE928043FD7006DB301 /* ServerConnectionConfig.swift in Sources */,
|
||||||
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */,
|
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */,
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
// PlaybackReport.swift
|
// PlaybackReport.swift
|
||||||
// App
|
// App
|
||||||
//
|
//
|
||||||
// Created by Rasmus Krämer on 12.04.22.
|
// Created by Rasmus Krämer on 15.04.22.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
|
||||||
|
|
||||||
class PlaybackReport: Object {
|
struct PlaybackReport: Decodable, Encodable {
|
||||||
@Persisted var token: String
|
var currentTime: Double
|
||||||
|
var duration: Double
|
||||||
|
var timeListened: Double
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class AudioPlayer: NSObject {
|
|||||||
playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: &playerItemContext)
|
playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: &playerItemContext)
|
||||||
|
|
||||||
self.audioPlayer.replaceCurrentItem(with: playerItem)
|
self.audioPlayer.replaceCurrentItem(with: playerItem)
|
||||||
seek(self.playbackSession.currentTime)
|
seek(playbackSession.currentTime)
|
||||||
|
|
||||||
NSLog("Audioplayer ready")
|
NSLog("Audioplayer ready")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import Foundation
|
|||||||
class PlayerHandler {
|
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 listningTimePassedSinceLastSync = 0.0
|
||||||
|
|
||||||
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool) {
|
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool) {
|
||||||
if player != nil {
|
if player != nil {
|
||||||
@@ -18,13 +21,23 @@ class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NowPlayingInfo.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
NowPlayingInfo.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
||||||
|
|
||||||
self.session = session
|
self.session = session
|
||||||
player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady)
|
player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady)
|
||||||
|
|
||||||
|
// DispatchQueue.main.sync {
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
|
self.tick()
|
||||||
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
public static func stopPlayback() {
|
public static func stopPlayback() {
|
||||||
player?.destroy()
|
player?.destroy()
|
||||||
player = nil
|
player = nil
|
||||||
|
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
|
||||||
NowPlayingInfo.reset()
|
NowPlayingInfo.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +87,10 @@ class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func getMetdata() -> [String: Any] {
|
public static func getMetdata() -> [String: Any] {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
syncProgress()
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"duration": player?.getDuration() ?? 0,
|
"duration": player?.getDuration() ?? 0,
|
||||||
"currentTime": player?.getCurrentTime() ?? 0,
|
"currentTime": player?.getCurrentTime() ?? 0,
|
||||||
@@ -81,4 +98,28 @@ class PlayerHandler {
|
|||||||
"currentRate": player?.rate ?? 0,
|
"currentRate": player?.rate ?? 0,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func tick() {
|
||||||
|
if !paused() {
|
||||||
|
listningTimePassedSinceLastSync += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if listningTimePassedSinceLastSync > 3 {
|
||||||
|
syncProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func syncProgress() {
|
||||||
|
if player == nil || session == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let report = PlaybackReport(currentTime: player!.getCurrentTime(), duration: player!.getDuration(), timeListened: listningTimePassedSinceLastSync)
|
||||||
|
|
||||||
|
session!.currentTime = player!.getCurrentTime()
|
||||||
|
listningTimePassedSinceLastSync = 0
|
||||||
|
|
||||||
|
// TODO: check if online
|
||||||
|
NSLog("sending playback report")
|
||||||
|
ApiClient.reportPlaybackProgress(report: report, sessionId: session!.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,6 @@ import Foundation
|
|||||||
import Alamofire
|
import Alamofire
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
/*
|
|
||||||
public static func getResource<T: Decodable>(endpoint: String, decodable: T.Type = T.self, callback: ((_ param: DataRequest) -> Void)?) {
|
|
||||||
let headers: HTTPHeaders = [
|
|
||||||
"Authorization": "Bearer \(Store.serverConfig.token)"
|
|
||||||
]
|
|
||||||
|
|
||||||
AF.request("\(Store.serverConfig.address)/\(endpoint)", headers: headers).responseDecodable(of: decodable) { response in
|
|
||||||
// callback(response)
|
|
||||||
debugPrint("Response: \(response)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static func postResource<T: Decodable>(endpoint: String, parameters: [String: String], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) {
|
public static func postResource<T: Decodable>(endpoint: String, parameters: [String: String], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) {
|
||||||
let headers: HTTPHeaders = [
|
let headers: HTTPHeaders = [
|
||||||
"Authorization": "Bearer \(Store.serverConfig!.token)"
|
"Authorization": "Bearer \(Store.serverConfig!.token)"
|
||||||
@@ -37,6 +24,23 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func postResource(endpoint: String, parameters: [String: String], callback: ((_ success: Bool) -> Void)?) {
|
||||||
|
let headers: HTTPHeaders = [
|
||||||
|
"Authorization": "Bearer \(Store.serverConfig!.token)"
|
||||||
|
]
|
||||||
|
|
||||||
|
AF.request("\(Store.serverConfig!.address)/\(endpoint)", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, headers: headers).response { response in
|
||||||
|
switch response.result {
|
||||||
|
case .success(let _):
|
||||||
|
callback?(true)
|
||||||
|
case .failure(let error):
|
||||||
|
NSLog("api request to \(endpoint) failed")
|
||||||
|
print(error)
|
||||||
|
|
||||||
|
callback?(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static func startPlaybackSession(libraryItemId: String, episodeId: String?, callback: @escaping (_ param: PlaybackSession) -> Void) {
|
public static func startPlaybackSession(libraryItemId: String, episodeId: String?, callback: @escaping (_ param: PlaybackSession) -> Void) {
|
||||||
var endpoint = "api/items/\(libraryItemId)/play"
|
var endpoint = "api/items/\(libraryItemId)/play"
|
||||||
@@ -56,4 +60,7 @@ class ApiClient {
|
|||||||
callback(session)
|
callback(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) {
|
||||||
|
try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ class Store {
|
|||||||
Database.setLastActiveConfigIndexToNil()
|
Database.setLastActiveConfigIndexToNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
_serverConfig = nil
|
Database.realmQueue.sync {
|
||||||
|
_serverConfig = updated
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user