Merge remote-tracking branch 'upstream/master' into ios-general-fixes

This commit is contained in:
ronaldheft
2022-09-14 19:55:42 -04:00
14 changed files with 233 additions and 136 deletions
+31 -27
View File
@@ -19,6 +19,7 @@ enum PlayMethod:Int {
class AudioPlayer: NSObject {
internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue")
internal let logger = AppLogger(category: "AudioPlayer")
// enums and @objc are not compatible
@objc dynamic var status: Int
@@ -68,7 +69,7 @@ class AudioPlayer: NSObject {
let playbackSession = self.getPlaybackSession()
guard let playbackSession = playbackSession else {
NSLog("Failed to fetch playback session. Player will not initialize")
logger.error("Failed to fetch playback session. Player will not initialize")
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
@@ -86,10 +87,10 @@ class AudioPlayer: NSObject {
}
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
NSLog("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
logger.log("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
let playerItems = self.allPlayerItems[self.currentTrackIndex..<self.allPlayerItems.count]
NSLog("Setting player items \(playerItems.count)")
logger.log("Setting player items \(playerItems.count)")
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
@@ -99,7 +100,7 @@ class AudioPlayer: NSObject {
setupQueueObserver()
setupQueueItemStatusObserver()
NSLog("Audioplayer ready")
logger.log("Audioplayer ready")
}
deinit {
@@ -120,8 +121,8 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch {
NSLog("Failed to set AVAudioSession inactive")
print(error)
logger.error("Failed to set AVAudioSession inactive")
logger.error(error)
}
self.removeAudioSessionNotifications()
@@ -214,14 +215,14 @@ class AudioPlayer: NSObject {
self.audioPlayer.currentItem.map { item in
self.currentTrackIndex = self.allPlayerItems.firstIndex(of:item) ?? 0
if (self.currentTrackIndex != prevTrackIndex) {
NSLog("New Current track index \(self.currentTrackIndex)")
self.logger.log("New Current track index \(self.currentTrackIndex)")
}
}
}
}
private func setupQueueItemStatusObserver() {
NSLog("queueStatusObserver: Setting up")
logger.log("queueStatusObserver: Setting up")
// Listen for player item updates
self.queueItemStatusObserver?.invalidate()
@@ -236,13 +237,13 @@ class AudioPlayer: NSObject {
}
private func handleQueueItemStatus(playerItem: AVPlayerItem) {
NSLog("queueStatusObserver: Current item status changed")
logger.log("queueStatusObserver: Current item status changed")
guard let playbackSession = self.getPlaybackSession() else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
if (playerItem.status == .readyToPlay) {
NSLog("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
logger.log("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
// Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session
let firstReady = self.status < 0
@@ -261,7 +262,7 @@ class AudioPlayer: NSObject {
self.play()
}
} else if (playerItem.status == .failed) {
NSLog("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
}
}
@@ -269,8 +270,8 @@ class AudioPlayer: NSObject {
private func startPausedTimer() {
guard self.pausedTimer == nil else { return }
self.queue.async {
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
NSLog("PAUSE TIMER: Syncing from server")
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] timer in
self?.logger.log("PAUSE TIMER: Syncing from server")
Task { await PlayerProgress.shared.syncFromServer() }
}
}
@@ -334,7 +335,7 @@ class AudioPlayer: NSObject {
}
private func resumePlayback() {
NSLog("PLAY: Resuming playback")
logger.log("PLAY: Resuming playback")
// Stop the paused timer
self.stopPausedTimer()
@@ -353,7 +354,7 @@ class AudioPlayer: NSObject {
public func pause() {
guard self.isInitialized() else { return }
NSLog("PAUSE: Pausing playback")
logger.log("PAUSE: Pausing playback")
DispatchQueue.runOnMainQueue {
self.audioPlayer.pause()
}
@@ -377,17 +378,17 @@ class AudioPlayer: NSObject {
self.pause()
NSLog("SEEK: Seek to \(to) from \(from)")
logger.log("SEEK: Seek to \(to) from \(from)")
guard let playbackSession = self.getPlaybackSession() else { return }
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
let ctso = currentTrack.startOffset ?? 0.0
let trackEnd = ctso + currentTrack.duration
NSLog("SEEK: Seek current track END = \(trackEnd)")
logger.log("SEEK: Seek current track END = \(trackEnd)")
let indexOfSeek = getItemIndexForTime(time: to)
NSLog("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
// Reconstruct queue if seeking to a different track
if (self.currentTrackIndex != indexOfSeek) {
@@ -410,13 +411,16 @@ class AudioPlayer: NSObject {
setupQueueItemStatusObserver()
} else {
NSLog("SEEK: Seeking in current item \(to)")
logger.log("SEEK: Seeking in current item \(to)")
let currentTrackStartOffset = playbackSession.audioTracks[self.currentTrackIndex].startOffset ?? 0.0
let seekTime = to - currentTrackStartOffset
DispatchQueue.runOnMainQueue {
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
guard completed else { return NSLog("SEEK: WARNING: seeking not completed (to \(seekTime)") }
guard completed else {
self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)")
return
}
guard let self = self else { return }
if continuePlaying {
@@ -433,7 +437,7 @@ class AudioPlayer: NSObject {
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
if self.audioPlayer.rate != rate {
NSLog("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
DispatchQueue.runOnMainQueue {
self.audioPlayer.rate = rate
}
@@ -490,7 +494,7 @@ class AudioPlayer: NSObject {
} else if (playbackSession.playMethod == PlayMethod.local.rawValue) {
guard let localFile = track.getLocalFile() else {
// Worst case we can stream the file
NSLog("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
logger.log("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
let filename = track.metadata?.filename ?? ""
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
@@ -510,8 +514,8 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio)
} catch {
NSLog("Failed to set AVAudioSession category")
print(error)
logger.error("Failed to set AVAudioSession category")
logger.error(error)
}
}
@@ -519,7 +523,7 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setActive(active)
} catch {
NSLog("Failed to set audio session as active=\(active)")
logger.error("Failed to set audio session as active=\(active)")
}
}
@@ -650,11 +654,11 @@ class AudioPlayer: NSObject {
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &playerContext {
if keyPath == #keyPath(AVPlayer.rate) {
NSLog("playerContext observer player rate")
logger.log("playerContext observer player rate")
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
} else if keyPath == #keyPath(AVPlayer.currentItem) {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
NSLog("WARNING: Item ended")
logger.log("WARNING: Item ended")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
@@ -31,7 +31,7 @@ extension AudioPlayer {
}
public func setSleepTimer(secondsUntilSleep: Double) {
NSLog("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
logger.log("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
self.removeSleepTimer()
self.sleepTimeRemaining = secondsUntilSleep
@@ -48,7 +48,7 @@ extension AudioPlayer {
}
public func setChapterSleepTimer(stopAt: Double) {
NSLog("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
logger.log("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
self.removeSleepTimer()
// Schedule the observation time
@@ -124,7 +124,7 @@ extension AudioPlayer {
}
private func handleSleepEnd() {
NSLog("SLEEP TIMER: Pausing audio")
logger.log("SLEEP TIMER: Pausing audio")
self.pause()
self.removeSleepTimer()
}
+17 -15
View File
@@ -14,6 +14,8 @@ class PlayerProgress {
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
private let logger = AppLogger(category: "PlayerProgress")
private init() {}
@@ -28,8 +30,8 @@ class PlayerProgress {
try await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
}
} catch {
debugPrint("Failed to syncFromPlayer")
debugPrint(error)
logger.error("Failed to syncFromPlayer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -39,8 +41,8 @@ class PlayerProgress {
do {
try await updateAllServerSessionFromLocalSession()
} catch {
debugPrint("Failed to syncToServer")
debugPrint(error)
logger.error("Failed to syncToServer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -50,8 +52,8 @@ class PlayerProgress {
do {
try await updateLocalSessionFromServerMediaProgress()
} catch {
debugPrint("Failed to syncFromServer")
debugPrint(error)
logger.error("Failed to syncFromServer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -96,7 +98,7 @@ class PlayerProgress {
try localMediaProgress.updateFromPlaybackSession(session)
NSLog("Local progress saved to the database")
logger.log("Local progress saved to the database")
// Send the local progress back to front-end
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
@@ -141,7 +143,7 @@ class PlayerProgress {
session = session.freeze()
guard safeToSync else { return }
NSLog("Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
logger.log("Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
var success = false
if session.isLocal {
@@ -161,25 +163,25 @@ class PlayerProgress {
}
private func updateLocalSessionFromServerMediaProgress() async throws {
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
logger.log("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
guard let session = try await Realm().objects(PlaybackSession.self).last(where: {
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
})?.freeze() else {
NSLog("updateLocalSessionFromServerMediaProgress: Failed to get session")
logger.log("updateLocalSessionFromServerMediaProgress: Failed to get session")
return
}
// Fetch the current progress
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
guard let progress = progress else {
NSLog("updateLocalSessionFromServerMediaProgress: No progress object")
logger.log("updateLocalSessionFromServerMediaProgress: No progress object")
return
}
// Determine which session is newer
let serverLastUpdate = progress.lastUpdate
guard let localLastUpdate = session.updatedAt else {
NSLog("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
logger.log("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
return
}
let serverCurrentTime = progress.currentTime
@@ -190,16 +192,16 @@ class PlayerProgress {
// Update the session, if needed
if serverIsNewerThanLocal && currentTimeIsDifferent {
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
logger.log("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
guard let session = session.thaw() else { return }
try session.update {
session.currentTime = serverCurrentTime
session.updatedAt = serverLastUpdate
}
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
logger.log("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
PlayerHandler.seek(amount: session.currentTime)
} else {
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
logger.log("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
}
}
+17 -15
View File
@@ -9,6 +9,8 @@ import Foundation
import Alamofire
class ApiClient {
private static let logger = AppLogger(category: "ApiClient")
public static func getData(from url: URL, completion: @escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let data = data {
@@ -19,7 +21,7 @@ class ApiClient {
public static func postResource<T: Decodable>(endpoint: String, parameters: [String: Any], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
return
}
@@ -32,7 +34,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -40,7 +42,7 @@ 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")
logger.error("Server config not set")
return
}
@@ -53,7 +55,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -69,7 +71,7 @@ class ApiClient {
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(false)
return
}
@@ -83,7 +85,7 @@ class ApiClient {
case .success(_):
callback?(true)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
callback?(false)
@@ -93,7 +95,7 @@ class ApiClient {
public static func patchResource<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(false)
return
}
@@ -107,7 +109,7 @@ class ApiClient {
case .success(_):
callback?(true)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
callback?(false)
}
@@ -124,7 +126,7 @@ class ApiClient {
public static func getResource<T: Decodable>(endpoint: String, decodable: T.Type = T.self, callback: ((_ param: T?) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(nil)
return
}
@@ -138,7 +140,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -193,10 +195,10 @@ class ApiClient {
if ( !localMediaProgressList.isEmpty ) {
let payload = LocalMediaProgressSyncPayload(localMediaProgress: localMediaProgressList)
NSLog("Sending sync local progress request with \(localMediaProgressList.count) progress items")
logger.log("Sending sync local progress request with \(localMediaProgressList.count) progress items")
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()))")
logger.log("Media Progress Sync | \(String(describing: try? resultsPayload.asDictionary()))")
if let updates = response.localProgressUpdates {
for update in updates {
@@ -212,13 +214,13 @@ class ApiClient {
callback(resultsPayload)
}
} else {
NSLog("No local media progress to sync")
logger.log("No local media progress to sync")
callback(LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: 0, numServerProgressUpdates: 0, numLocalProgressUpdates: 0))
}
}
public static func updateMediaProgress<T:Encodable>(libraryItemId: String, episodeId: String?, payload: T, callback: @escaping () -> Void) {
NSLog("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
logger.log("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
patchResource(endpoint: endpoint, parameters: payload) { success in
callback()
@@ -226,7 +228,7 @@ class ApiClient {
}
public static func getMediaProgress(libraryItemId: String, episodeId: String?) async -> MediaProgress? {
NSLog("getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
logger.log("getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
return await getResource(endpoint: endpoint, decodable: MediaProgress.self)
}
+7 -5
View File
@@ -12,6 +12,8 @@ class Database {
public static var shared = {
return Database()
}()
private let logger = AppLogger(category: "Database")
private init() {}
@@ -30,7 +32,7 @@ class Database {
existing.token = config.token
}
} catch {
NSLog("failed to update server config")
logger.error("failed to update server config")
debugPrint(error)
}
@@ -51,7 +53,7 @@ class Database {
realm.add(config)
}
} catch(let exception) {
NSLog("failed to save server config")
logger.error("failed to save server config")
debugPrint(exception)
}
@@ -70,7 +72,7 @@ class Database {
}
}
} catch(let exception) {
NSLog("failed to delete server config")
logger.error("failed to delete server config")
debugPrint(exception)
}
}
@@ -101,7 +103,7 @@ class Database {
}
}
} catch(let exception) {
NSLog("failed to save server config active index")
logger.error("failed to save server config active index")
debugPrint(exception)
}
}
@@ -121,7 +123,7 @@ class Database {
realm.add(deviceSettings)
}
} catch {
NSLog("failed to save device settings")
logger.error("failed to save device settings")
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ extension CAPPluginCall {
let json = try JSONSerialization.data(withJSONObject: value)
return try JSONDecoder().decode(type, from: json)
} catch {
NSLog("Failed to get json for \(key)")
AppLogger().error("Failed to get json for \(key)")
debugPrint(error)
return nil
}
+72
View File
@@ -0,0 +1,72 @@
//
// AppLogger.swift
// AppLogger
//
// Created by Fernando Fernandes on 29.06.20.
//
import Foundation
import os
/// Logs app info by using the newest Swift unified logging APIs.
///
/// Reference:
/// - [Explore logging in Swift (WWDC20)](https://developer.apple.com/wwdc20/10168)
/// - [Unified Logging](https://developer.apple.com/documentation/os/logging)
/// - [OSLog](https://developer.apple.com/documentation/os/oslog)
/// - [Logger](https://developer.apple.com/documentation/os/logger)
public struct AppLogger {
// MARK: - Properties
/// Default values used by the `AppLogger`.
public struct Defaults {
public static let subsystem = Bundle.main.bundleIdentifier ?? "ABS"
public static let category = "default"
public static let isPrivate = false
}
// MARK: - Private Properties
private let logger: Logger
// MARK: - Lifecycle
/// Creates an `AppLogger` instance.
///
/// - Parameters:
/// - subsystem: `String`. Organizes large topic areas within the app or apps. For example, you might define
/// a subsystem for each process that you create. The default is `Bundle.main.bundleIdentifier ?? "AppLogger"`.
/// - category: `String`. Within a `subsystem`, you define categories to further distinguish parts of that
/// subsystem. For example, if you used a single subsystem for your app, you might create separate categories for
/// model code and user-interface code. In a game, you might use categories to distinguish between physics, AI,
/// world simulation, and rendering. The default is `default`.
public init(subsystem: String = Defaults.subsystem, category: String = Defaults.category) {
self.logger = Logger(subsystem: subsystem, category: category)
}
}
// MARK: - Interface
public extension AppLogger {
func log(_ information: String, isPrivate: Bool = Defaults.isPrivate) {
if isPrivate {
logger.log("\(information, privacy: .private)")
} else {
logger.log("\(information, privacy: .public)")
}
}
func error(_ information: String, isPrivate: Bool = Defaults.isPrivate) {
if isPrivate {
logger.error("\(information, privacy: .private)")
} else {
logger.error("\(information, privacy: .public)")
}
}
func error(_ error: Error) {
logger.error("\(String(describing: error))")
}
}