mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 23:27:17 +02:00
Small improvements
This commit is contained in:
@@ -53,7 +53,7 @@ public class AbsDatabase: CAPPlugin {
|
|||||||
}
|
}
|
||||||
@objc func removeServerConnectionConfig(_ call: CAPPluginCall) {
|
@objc func removeServerConnectionConfig(_ call: CAPPluginCall) {
|
||||||
let id = call.getString("serverConnectionConfigId", "")
|
let id = call.getString("serverConnectionConfigId", "")
|
||||||
Database.deleteServerConnectionConfig(id: id)
|
Database.shared.deleteServerConnectionConfig(id: id)
|
||||||
|
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
@@ -63,13 +63,12 @@ public class AbsDatabase: CAPPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func getDeviceData(_ call: CAPPluginCall) {
|
@objc func getDeviceData(_ call: CAPPluginCall) {
|
||||||
let configs = Database.getServerConnectionConfigs()
|
let configs = Database.shared.getServerConnectionConfigs()
|
||||||
let index = Database.getLastActiveConfigIndex()
|
let index = Database.shared.getLastActiveConfigIndex()
|
||||||
|
|
||||||
call.resolve([
|
call.resolve([
|
||||||
"serverConnectionConfigs": configs.map { config in convertServerConnectionConfigToJSON(config: config) },
|
"serverConnectionConfigs": configs.map { config in convertServerConnectionConfigToJSON(config: config) },
|
||||||
"lastServerConnectionConfigId": configs.first { config in config.index == index }?.id,
|
"lastServerConnectionConfigId": configs.first { config in config.index == index }?.id as Any,
|
||||||
// Luckily this isn't implemented yet
|
|
||||||
// "currentLocalPlaybackSession": nil,
|
// "currentLocalPlaybackSession": nil,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class AudioPlayer: NSObject {
|
|||||||
public func destroy() {
|
public func destroy() {
|
||||||
// Pause is not synchronous causing this error on below lines:
|
// Pause is not synchronous causing this error on below lines:
|
||||||
// AVAudioSession_iOS.mm:1206 Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session
|
// AVAudioSession_iOS.mm:1206 Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session
|
||||||
|
// It is related to L79 `AVAudioSession.sharedInstance().setActive(false)`
|
||||||
pause()
|
pause()
|
||||||
audioPlayer.replaceCurrentItem(with: nil)
|
audioPlayer.replaceCurrentItem(with: nil)
|
||||||
|
|
||||||
@@ -80,11 +81,9 @@ class AudioPlayer: NSObject {
|
|||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throws error Possibly related to the error above
|
DispatchQueue.runOnMainQueue {
|
||||||
// DispatchQueue.main.sync {
|
UIApplication.shared.endReceivingRemoteControlEvents()
|
||||||
// UIApplication.shared.endReceivingRemoteControlEvents()
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,9 +185,9 @@ class AudioPlayer: NSObject {
|
|||||||
|
|
||||||
// MARK: - Now playing
|
// MARK: - Now playing
|
||||||
private func setupRemoteTransportControls() {
|
private func setupRemoteTransportControls() {
|
||||||
// DispatchQueue.main.sync {
|
DispatchQueue.runOnMainQueue {
|
||||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||||
// }
|
}
|
||||||
let commandCenter = MPRemoteCommandCenter.shared()
|
let commandCenter = MPRemoteCommandCenter.shared()
|
||||||
|
|
||||||
commandCenter.playCommand.isEnabled = true
|
commandCenter.playCommand.isEnabled = true
|
||||||
@@ -246,7 +245,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
private func updateNowPlaying() {
|
private func updateNowPlaying() {
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
NowPlayingInfo.update(duration: getDuration(), currentTime: getCurrentTime(), rate: rate)
|
NowPlayingInfo.shared.update(duration: getDuration(), currentTime: getCurrentTime(), rate: rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ class PlayerHandler {
|
|||||||
player = nil
|
player = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
NowPlayingInfo.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
NowPlayingInfo.shared.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, playbackRate: playbackRate)
|
player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
|
|
||||||
// DispatchQueue.main.sync {
|
DispatchQueue.runOnMainQueue {
|
||||||
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
self.tick()
|
self.tick()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
public static func stopPlayback() {
|
public static func stopPlayback() {
|
||||||
player?.destroy()
|
player?.destroy()
|
||||||
@@ -38,7 +38,7 @@ class PlayerHandler {
|
|||||||
timer?.invalidate()
|
timer?.invalidate()
|
||||||
timer = nil
|
timer = nil
|
||||||
|
|
||||||
NowPlayingInfo.reset()
|
NowPlayingInfo.shared.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getCurrentTime() -> Double? {
|
public static func getCurrentTime() -> Double? {
|
||||||
@@ -63,20 +63,20 @@ class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func seekForward(amount: Double) {
|
public static func seekForward(amount: Double) {
|
||||||
if player == nil {
|
guard let player = player else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let destinationTime = player!.getCurrentTime() + amount
|
let destinationTime = player.getCurrentTime() + amount
|
||||||
player!.seek(destinationTime)
|
player.seek(destinationTime)
|
||||||
}
|
}
|
||||||
public static func seekBackward(amount: Double) {
|
public static func seekBackward(amount: Double) {
|
||||||
if player == nil {
|
guard let player = player else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let destinationTime = player!.getCurrentTime() - amount
|
let destinationTime = player.getCurrentTime() - amount
|
||||||
player!.seek(destinationTime)
|
player.seek(destinationTime)
|
||||||
}
|
}
|
||||||
public static func seek(amount: Double) {
|
public static func seek(amount: Double) {
|
||||||
player?.seek(amount)
|
player?.seek(amount)
|
||||||
@@ -109,13 +109,16 @@ class PlayerHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func syncProgress() {
|
public static func syncProgress() {
|
||||||
if player == nil || session == nil {
|
if session == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let player = player else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let report = PlaybackReport(currentTime: player!.getCurrentTime(), duration: player!.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
let report = PlaybackReport(currentTime: player.getCurrentTime(), duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
||||||
|
|
||||||
session!.currentTime = player!.getCurrentTime()
|
session!.currentTime = player.getCurrentTime()
|
||||||
listeningTimePassedSinceLastSync = 0
|
listeningTimePassedSinceLastSync = 0
|
||||||
|
|
||||||
// TODO: check if online
|
// TODO: check if online
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ import Foundation
|
|||||||
import Alamofire
|
import Alamofire
|
||||||
|
|
||||||
class ApiClient {
|
class 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 {
|
||||||
|
completion(UIImage(data:data))
|
||||||
|
}
|
||||||
|
}).resume()
|
||||||
|
}
|
||||||
|
|
||||||
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)?) {
|
||||||
if (Store.serverConfig == nil) {
|
if (Store.serverConfig == nil) {
|
||||||
NSLog("Server config not set")
|
NSLog("Server config not set")
|
||||||
@@ -54,7 +62,6 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
if episodeId != nil {
|
if episodeId != nil {
|
||||||
endpoint += "/\(episodeId!)"
|
endpoint += "/\(episodeId!)"
|
||||||
|
|||||||
@@ -10,16 +10,25 @@ import RealmSwift
|
|||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
// All DB releated actions must be executed on "realm-queue"
|
// All DB releated actions must be executed on "realm-queue"
|
||||||
public static let realmQueue = DispatchQueue(label: "realm-queue")
|
public static let realmQueue: DispatchQueue = DispatchQueue(label: "realm-queue")
|
||||||
private static var instance: Realm = try! Realm(queue: realmQueue)
|
public static var shared = {
|
||||||
|
realmQueue.sync {
|
||||||
|
return Database()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
public static func setServerConnectionConfig(config: ServerConnectionConfig) {
|
private var instance: Realm
|
||||||
|
private init() {
|
||||||
|
self.instance = try! Realm(queue: Database.realmQueue)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setServerConnectionConfig(config: ServerConnectionConfig) {
|
||||||
var refrence: ThreadSafeReference<ServerConnectionConfig>?
|
var refrence: ThreadSafeReference<ServerConnectionConfig>?
|
||||||
if config.realm != nil {
|
if config.realm != nil {
|
||||||
refrence = ThreadSafeReference(to: config)
|
refrence = ThreadSafeReference(to: config)
|
||||||
}
|
}
|
||||||
|
|
||||||
realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let existing: ServerConnectionConfig? = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: config.id)
|
let existing: ServerConnectionConfig? = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: config.id)
|
||||||
|
|
||||||
if config.index == 0 {
|
if config.index == 0 {
|
||||||
@@ -55,8 +64,8 @@ class Database {
|
|||||||
setLastActiveConfigIndex(index: config.index)
|
setLastActiveConfigIndex(index: config.index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func deleteServerConnectionConfig(id: String) {
|
public func deleteServerConnectionConfig(id: String) {
|
||||||
realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let config = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
|
let config = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: id)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -71,10 +80,10 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
||||||
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
||||||
|
|
||||||
realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let configs = instance.objects(ServerConnectionConfig.self)
|
let configs = instance.objects(ServerConnectionConfig.self)
|
||||||
refrences = configs.map { config in
|
refrences = configs.map { config in
|
||||||
return ThreadSafeReference(to: config)
|
return ThreadSafeReference(to: config)
|
||||||
@@ -94,12 +103,12 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func setLastActiveConfigIndexToNil() {
|
public func setLastActiveConfigIndexToNil() {
|
||||||
realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
setLastActiveConfigIndex(index: nil)
|
setLastActiveConfigIndex(index: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func setLastActiveConfigIndex(index: Int?) {
|
public func setLastActiveConfigIndex(index: Int?) {
|
||||||
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
||||||
let obj = ServerConnectionConfigActiveIndex()
|
let obj = ServerConnectionConfigActiveIndex()
|
||||||
obj.index = index
|
obj.index = index
|
||||||
@@ -114,8 +123,8 @@ class Database {
|
|||||||
debugPrint(exception)
|
debugPrint(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func getLastActiveConfigIndex() -> Int? {
|
public func getLastActiveConfigIndex() -> Int? {
|
||||||
return realmQueue.sync {
|
return Database.realmQueue.sync {
|
||||||
return instance.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
|
return instance.objects(ServerConnectionConfigActiveIndex.self).first?.index ?? nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,14 @@ extension Encodable {
|
|||||||
return dictionary
|
return dictionary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
extension DispatchQueue {
|
||||||
|
static func runOnMainQueue(callback: @escaping (() -> Void)) {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,14 +8,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
|
|
||||||
func getData(from url: URL, completion: @escaping (UIImage?) -> Void) {
|
|
||||||
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
|
|
||||||
if let data = data {
|
|
||||||
completion(UIImage(data:data))
|
|
||||||
}
|
|
||||||
}).resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NowPlayingMetadata {
|
struct NowPlayingMetadata {
|
||||||
var id: String
|
var id: String
|
||||||
var itemId: String
|
var itemId: String
|
||||||
@@ -26,22 +18,22 @@ struct NowPlayingMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NowPlayingInfo {
|
class NowPlayingInfo {
|
||||||
private static var nowPlayingInfo: [String: Any] = [:]
|
static var shared = {
|
||||||
|
return NowPlayingInfo()
|
||||||
|
}()
|
||||||
|
|
||||||
public static func setSessionMetadata(metadata: NowPlayingMetadata) {
|
private var nowPlayingInfo: [String: Any]
|
||||||
|
private init() {
|
||||||
|
self.nowPlayingInfo = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSessionMetadata(metadata: NowPlayingMetadata) {
|
||||||
setMetadata(artwork: nil, metadata: metadata)
|
setMetadata(artwork: nil, metadata: metadata)
|
||||||
|
|
||||||
/*
|
|
||||||
if !shouldFetchCover(id: metadata.id) || metadata.artworkUrl == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
guard let url = URL(string: "\(Store.serverConfig!.address)/api/items/\(metadata.itemId)/cover?token=\(Store.serverConfig!.token)") else {
|
guard let url = URL(string: "\(Store.serverConfig!.address)/api/items/\(metadata.itemId)/cover?token=\(Store.serverConfig!.token)") else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ApiClient.getData(from: url) { [self] image in
|
||||||
getData(from: url) { [self] image in
|
|
||||||
guard let downloadedImage = image else {
|
guard let downloadedImage = image else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -52,7 +44,7 @@ class NowPlayingInfo {
|
|||||||
self.setMetadata(artwork: artwork, metadata: metadata)
|
self.setMetadata(artwork: artwork, metadata: metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func update(duration: Double, currentTime: Double, rate: Float) {
|
public func update(duration: Double, currentTime: Double, rate: Float) {
|
||||||
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
||||||
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
||||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
||||||
@@ -60,12 +52,12 @@ class NowPlayingInfo {
|
|||||||
|
|
||||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||||
}
|
}
|
||||||
public static func reset() {
|
public func reset() {
|
||||||
nowPlayingInfo = [:]
|
nowPlayingInfo = [:]
|
||||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func setMetadata(artwork: MPMediaItemArtwork?, metadata: NowPlayingMetadata?) {
|
private func setMetadata(artwork: MPMediaItemArtwork?, metadata: NowPlayingMetadata?) {
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -84,7 +76,7 @@ class NowPlayingInfo {
|
|||||||
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown"
|
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown"
|
||||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.series
|
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.series
|
||||||
}
|
}
|
||||||
private static func shouldFetchCover(id: String) -> Bool {
|
private func shouldFetchCover(id: String) -> Bool {
|
||||||
nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] as? String != id || nowPlayingInfo[MPMediaItemPropertyArtwork] == nil
|
nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] as? String != id || nowPlayingInfo[MPMediaItemPropertyArtwork] == nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class Store {
|
|||||||
}
|
}
|
||||||
set(updated) {
|
set(updated) {
|
||||||
if updated != nil {
|
if updated != nil {
|
||||||
Database.setServerConnectionConfig(config: updated!)
|
Database.shared.setServerConnectionConfig(config: updated!)
|
||||||
} else {
|
} else {
|
||||||
Database.setLastActiveConfigIndexToNil()
|
Database.shared.setLastActiveConfigIndexToNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
|
|||||||
Reference in New Issue
Block a user