From 363bfd206c6af0be4006d01cc985fc0961d316ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rasmus=20Kr=C3=A4mer?= Date: Fri, 15 Apr 2022 12:21:46 +0200 Subject: [PATCH] Sync playback position (online only) --- ios/App/App.xcodeproj/project.pbxproj | 8 ++--- ios/App/Shared/models/PlaybackReport.swift | 9 ++--- ios/App/Shared/player/AudioPlayer.swift | 2 +- ios/App/Shared/player/PlayerHandler.swift | 41 ++++++++++++++++++++++ ios/App/Shared/util/ApiClient.swift | 33 ++++++++++------- ios/App/Shared/util/Store.swift | 4 ++- 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index d84a8825..5b55446c 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -9,10 +9,10 @@ /* Begin PBXBuildFile section */ 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; 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 */; }; 3AB34055280832720039308B /* PlayerEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34054280832720039308B /* PlayerEvents.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 */; }; 3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE428043E50006DB301 /* AbsDatabase.swift */; }; 3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE628043E72006DB301 /* AbsDatabase.m */; }; @@ -35,10 +35,10 @@ /* Begin PBXFileReference section */ 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; 3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; + 3A90295E280968E700E1D427 /* PlaybackReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackReport.swift; sourceTree = ""; }; 3AB34052280829BF0039308B /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 3AB34054280832720039308B /* PlayerEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerEvents.swift; sourceTree = ""; }; 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSession.swift; sourceTree = ""; }; - 3ABF580A2805A837005DFBE5 /* PlaybackReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackReport.swift; sourceTree = ""; }; 3ABF618E2804325C0070250E /* PlayerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHandler.swift; sourceTree = ""; }; 3AD4FCE428043E50006DB301 /* AbsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsDatabase.swift; sourceTree = ""; }; 3AD4FCE628043E72006DB301 /* AbsDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsDatabase.m; sourceTree = ""; }; @@ -118,7 +118,7 @@ children = ( 3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */, 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */, - 3ABF580A2805A837005DFBE5 /* PlaybackReport.swift */, + 3A90295E280968E700E1D427 /* PlaybackReport.swift */, ); path = models; sourceTree = ""; @@ -296,10 +296,10 @@ files = ( 3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */, 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + 3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */, 3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */, 3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */, 3AD4FCED28044E6C006DB301 /* Store.swift in Sources */, - 3ABF580B2805A837005DFBE5 /* PlaybackReport.swift in Sources */, 3AF1970E2806E3CA0096F747 /* AbsAudioPlayer.swift in Sources */, 3AD4FCE928043FD7006DB301 /* ServerConnectionConfig.swift in Sources */, 3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */, diff --git a/ios/App/Shared/models/PlaybackReport.swift b/ios/App/Shared/models/PlaybackReport.swift index f9c41ab0..f9773c57 100644 --- a/ios/App/Shared/models/PlaybackReport.swift +++ b/ios/App/Shared/models/PlaybackReport.swift @@ -2,12 +2,13 @@ // PlaybackReport.swift // App // -// Created by Rasmus Krämer on 12.04.22. +// Created by Rasmus Krämer on 15.04.22. // import Foundation -import RealmSwift -class PlaybackReport: Object { - @Persisted var token: String +struct PlaybackReport: Decodable, Encodable { + var currentTime: Double + var duration: Double + var timeListened: Double } diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index bb155e21..30363788 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -57,7 +57,7 @@ class AudioPlayer: NSObject { playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: &playerItemContext) self.audioPlayer.replaceCurrentItem(with: playerItem) - seek(self.playbackSession.currentTime) + seek(playbackSession.currentTime) NSLog("Audioplayer ready") } diff --git a/ios/App/Shared/player/PlayerHandler.swift b/ios/App/Shared/player/PlayerHandler.swift index eef1cf45..4a3a9c71 100644 --- a/ios/App/Shared/player/PlayerHandler.swift +++ b/ios/App/Shared/player/PlayerHandler.swift @@ -10,6 +10,9 @@ import Foundation class PlayerHandler { private static var player: AudioPlayer? private static var session: PlaybackSession? + private static var timer: Timer? + + private static var listningTimePassedSinceLastSync = 0.0 public static func startPlayback(session: PlaybackSession, playWhenReady: Bool) { 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)) + self.session = session player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady) + + // DispatchQueue.main.sync { + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in + self.tick() + } + // } } public static func stopPlayback() { player?.destroy() player = nil + timer?.invalidate() + timer = nil + NowPlayingInfo.reset() } @@ -74,6 +87,10 @@ class PlayerHandler { } public static func getMetdata() -> [String: Any] { + DispatchQueue.main.async { + syncProgress() + } + return [ "duration": player?.getDuration() ?? 0, "currentTime": player?.getCurrentTime() ?? 0, @@ -81,4 +98,28 @@ class PlayerHandler { "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) + } } diff --git a/ios/App/Shared/util/ApiClient.swift b/ios/App/Shared/util/ApiClient.swift index e9e01aea..ffe1ff27 100644 --- a/ios/App/Shared/util/ApiClient.swift +++ b/ios/App/Shared/util/ApiClient.swift @@ -9,19 +9,6 @@ import Foundation import Alamofire class ApiClient { - /* - public static func getResource(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(endpoint: String, parameters: [String: String], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) { let headers: HTTPHeaders = [ "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) { var endpoint = "api/items/\(libraryItemId)/play" @@ -56,4 +60,7 @@ class ApiClient { 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) + } } diff --git a/ios/App/Shared/util/Store.swift b/ios/App/Shared/util/Store.swift index 400ee06d..a4ae9863 100644 --- a/ios/App/Shared/util/Store.swift +++ b/ios/App/Shared/util/Store.swift @@ -21,7 +21,9 @@ class Store { Database.setLastActiveConfigIndexToNil() } - _serverConfig = nil + Database.realmQueue.sync { + _serverConfig = updated + } } } }