mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-27 13:54:01 +02:00
@@ -460,13 +460,13 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = N8AA4S3S96;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.43;
|
MARKETING_VERSION = 0.9.43;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.development;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "App/App-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "App/App-Bridging-Header.h";
|
||||||
@@ -484,7 +484,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = "";
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
|||||||
@@ -14,13 +14,19 @@ CAP_PLUGIN(AbsAudioPlayer, "AbsAudioPlayer",
|
|||||||
|
|
||||||
CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise);
|
||||||
|
|
||||||
CAP_PLUGIN_METHOD(playPause, CAPPluginReturnPromise);
|
|
||||||
CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise);
|
||||||
CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise);
|
||||||
|
CAP_PLUGIN_METHOD(playPause, CAPPluginReturnPromise);
|
||||||
|
|
||||||
CAP_PLUGIN_METHOD(seek, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(seek, CAPPluginReturnPromise);
|
||||||
CAP_PLUGIN_METHOD(seekForward, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(seekForward, CAPPluginReturnPromise);
|
||||||
CAP_PLUGIN_METHOD(seekBackward, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(seekBackward, CAPPluginReturnPromise);
|
||||||
|
|
||||||
CAP_PLUGIN_METHOD(getCurrentTime, CAPPluginReturnPromise);
|
CAP_PLUGIN_METHOD(getCurrentTime, CAPPluginReturnPromise);
|
||||||
|
|
||||||
|
CAP_PLUGIN_METHOD(cancelSleepTimer, CAPPluginReturnPromise);
|
||||||
|
CAP_PLUGIN_METHOD(decreaseSleepTime, CAPPluginReturnPromise);
|
||||||
|
CAP_PLUGIN_METHOD(increaseSleepTime, CAPPluginReturnPromise);
|
||||||
|
CAP_PLUGIN_METHOD(getSleepTimerTime, CAPPluginReturnPromise);
|
||||||
|
CAP_PLUGIN_METHOD(setSleepTimer, CAPPluginReturnPromise);
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
override public func load() {
|
override public func load() {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendPlaybackClosedEvent), name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendPlaybackClosedEvent), name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
||||||
self.bridge?.webView?.allowsBackForwardNavigationGestures = true;
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.didBecomeActiveNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.didBecomeActiveNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.willEnterForegroundNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(sendSleepTimerSet), name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(sendSleepTimerEnded), name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: nil)
|
||||||
|
|
||||||
|
self.bridge?.webView?.allowsBackForwardNavigationGestures = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func prepareLibraryItem(_ call: CAPPluginCall) {
|
@objc func prepareLibraryItem(_ call: CAPPluginCall) {
|
||||||
@@ -68,18 +71,19 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func playPause(_ call: CAPPluginCall) {
|
|
||||||
PlayerHandler.playPause()
|
|
||||||
call.resolve([ "playing": !PlayerHandler.paused() ])
|
|
||||||
}
|
|
||||||
@objc func playPlayer(_ call: CAPPluginCall) {
|
@objc func playPlayer(_ call: CAPPluginCall) {
|
||||||
PlayerHandler.play()
|
PlayerHandler.paused = false
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
@objc func pausePlayer(_ call: CAPPluginCall) {
|
@objc func pausePlayer(_ call: CAPPluginCall) {
|
||||||
PlayerHandler.pause()
|
PlayerHandler.paused = true
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
// I have no clue why but after i moved this block of code from above "playPlayer" to here the app stopped crashing. Move it back up if you want to
|
||||||
|
@objc func playPause(_ call: CAPPluginCall) {
|
||||||
|
PlayerHandler.paused = !PlayerHandler.paused
|
||||||
|
call.resolve([ "playing": !PlayerHandler.paused ])
|
||||||
|
}
|
||||||
|
|
||||||
@objc func seek(_ call: CAPPluginCall) {
|
@objc func seek(_ call: CAPPluginCall) {
|
||||||
PlayerHandler.seek(amount: call.getDouble("value", 0.0))
|
PlayerHandler.seek(amount: call.getDouble("value", 0.0))
|
||||||
@@ -95,13 +99,69 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func sendMetadata() {
|
@objc func sendMetadata() {
|
||||||
self.notifyListeners("onPlayingUpdate", data: [ "value": !PlayerHandler.paused() ])
|
self.notifyListeners("onPlayingUpdate", data: [ "value": !PlayerHandler.paused ])
|
||||||
self.notifyListeners("onMetadata", data: PlayerHandler.getMetdata())
|
self.notifyListeners("onMetadata", data: PlayerHandler.getMetdata())
|
||||||
}
|
}
|
||||||
@objc func sendPlaybackClosedEvent() {
|
@objc func sendPlaybackClosedEvent() {
|
||||||
self.notifyListeners("onPlaybackClosed", data: [ "value": true ])
|
self.notifyListeners("onPlaybackClosed", data: [ "value": true ])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func decreaseSleepTime(_ call: CAPPluginCall) {
|
||||||
|
guard let timeString = call.getString("time") else { return call.resolve([ "success": false ]) }
|
||||||
|
guard let time = Int(timeString) else { return call.resolve([ "success": false ]) }
|
||||||
|
guard let currentSleepTime = PlayerHandler.remainingSleepTime else { return call.resolve([ "success": false ]) }
|
||||||
|
|
||||||
|
PlayerHandler.remainingSleepTime = currentSleepTime - (time / 1000)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
@objc func increaseSleepTime(_ call: CAPPluginCall) {
|
||||||
|
guard let timeString = call.getString("time") else { return call.resolve([ "success": false ]) }
|
||||||
|
guard let time = Int(timeString) else { return call.resolve([ "success": false ]) }
|
||||||
|
guard let currentSleepTime = PlayerHandler.remainingSleepTime else { return call.resolve([ "success": false ]) }
|
||||||
|
|
||||||
|
PlayerHandler.remainingSleepTime = currentSleepTime + (time / 1000)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
@objc func setSleepTimer(_ call: CAPPluginCall) {
|
||||||
|
guard let timeString = call.getString("time") else { return call.resolve([ "success": false ]) }
|
||||||
|
guard let time = Int(timeString) else { return call.resolve([ "success": false ]) }
|
||||||
|
|
||||||
|
NSLog("chapter time: \(call.getBool("isChapterTime", false))")
|
||||||
|
|
||||||
|
if call.getBool("isChapterTime", false) {
|
||||||
|
let timeToPause = time / 1000 - Int(PlayerHandler.getCurrentTime() ?? 0)
|
||||||
|
if timeToPause < 0 { return call.resolve([ "success": false ]) }
|
||||||
|
|
||||||
|
NSLog("oof \(timeToPause)")
|
||||||
|
|
||||||
|
PlayerHandler.remainingSleepTime = timeToPause
|
||||||
|
return call.resolve([ "success": true ])
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerHandler.remainingSleepTime = time / 1000
|
||||||
|
call.resolve([ "success": true ])
|
||||||
|
}
|
||||||
|
@objc func cancelSleepTimer(_ call: CAPPluginCall) {
|
||||||
|
PlayerHandler.remainingSleepTime = nil
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
@objc func getSleepTimerTime(_ call: CAPPluginCall) {
|
||||||
|
call.resolve([
|
||||||
|
"value": PlayerHandler.remainingSleepTime
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func sendSleepTimerEnded() {
|
||||||
|
self.notifyListeners("onSleepTimerEnded", data: [
|
||||||
|
"value": PlayerHandler.getCurrentTime()
|
||||||
|
])
|
||||||
|
}
|
||||||
|
@objc func sendSleepTimerSet() {
|
||||||
|
self.notifyListeners("onSleepTimerSet", data: [
|
||||||
|
"value": PlayerHandler.remainingSleepTime
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
@objc func sendPrepareMetadataEvent(itemId: String, playWhenReady: Bool) {
|
@objc func sendPrepareMetadataEvent(itemId: String, playWhenReady: Bool) {
|
||||||
self.notifyListeners("onPrepareMedia", data: [
|
self.notifyListeners("onPrepareMedia", data: [
|
||||||
"audiobookId": itemId,
|
"audiobookId": itemId,
|
||||||
@@ -111,32 +171,4 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
@objc func sendPlaybackSession(session: [String: Any]) {
|
@objc func sendPlaybackSession(session: [String: Any]) {
|
||||||
self.notifyListeners("onPlaybackSession", data: session)
|
self.notifyListeners("onPlaybackSession", data: session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
IMPLEMENTED:
|
|
||||||
|
|
||||||
cancelSleepTimer
|
|
||||||
decreaseSleepTime
|
|
||||||
increaseSleepTime
|
|
||||||
getSleepTimerTime
|
|
||||||
setSleepTimer
|
|
||||||
* closePlayback
|
|
||||||
* setPlaybackSpeed
|
|
||||||
* seekBackward
|
|
||||||
* seekForward
|
|
||||||
* seek
|
|
||||||
* playPause
|
|
||||||
* playPlayer
|
|
||||||
* pausePlayer
|
|
||||||
* getCurrentTime
|
|
||||||
|
|
||||||
* onPlaybackSession
|
|
||||||
* onPrepareMedia
|
|
||||||
|
|
||||||
* onPlaybackClosed
|
|
||||||
* onPlayingUpdate
|
|
||||||
* onMetadata
|
|
||||||
onSleepTimerEnded
|
|
||||||
onSleepTimerSet
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,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)
|
||||||
|
|
||||||
@@ -90,11 +91,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +186,7 @@ class AudioPlayer: NSObject {
|
|||||||
lastPlayTime = Date.timeIntervalSinceReferenceDate
|
lastPlayTime = Date.timeIntervalSinceReferenceDate
|
||||||
}
|
}
|
||||||
|
|
||||||
public func seek(_ to: Double, from:String) {
|
public func seek(_ to: Double, from: String) {
|
||||||
let continuePlaying = rate > 0.0
|
let continuePlaying = rate > 0.0
|
||||||
|
|
||||||
pause()
|
pause()
|
||||||
@@ -247,7 +246,6 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.rate = rate
|
self.rate = rate
|
||||||
|
|
||||||
self.updateNowPlaying()
|
self.updateNowPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,9 +287,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
|
||||||
@@ -349,7 +347,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
|
||||||
@@ -359,6 +357,7 @@ class AudioPlayer: NSObject {
|
|||||||
NSLog("TEST: playerContext observer player rate")
|
NSLog("TEST: playerContext observer player rate")
|
||||||
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
|
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
|
||||||
} else if keyPath == #keyPath(AVPlayer.currentItem) {
|
} else if keyPath == #keyPath(AVPlayer.currentItem) {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
NSLog("WARNING: Item ended")
|
NSLog("WARNING: Item ended")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,8 +12,44 @@ class PlayerHandler {
|
|||||||
private static var session: PlaybackSession?
|
private static var session: PlaybackSession?
|
||||||
private static var timer: Timer?
|
private static var timer: Timer?
|
||||||
|
|
||||||
private static var listeningTimePassedSinceLastSync = 0.0
|
private static var _remainingSleepTime: Int? = nil
|
||||||
private static var lastSyncReport:PlaybackReport?
|
public static var remainingSleepTime: Int? {
|
||||||
|
get {
|
||||||
|
return _remainingSleepTime
|
||||||
|
}
|
||||||
|
set(time) {
|
||||||
|
if time != nil && time! < 0 {
|
||||||
|
_remainingSleepTime = nil
|
||||||
|
} else {
|
||||||
|
_remainingSleepTime = time
|
||||||
|
}
|
||||||
|
|
||||||
|
if _remainingSleepTime == nil {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepEnded.rawValue), object: _remainingSleepTime)
|
||||||
|
} else {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: _remainingSleepTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static var listeningTimePassedSinceLastSync: Double = 0.0
|
||||||
|
private static var lastSyncReport: PlaybackReport?
|
||||||
|
|
||||||
|
public static var paused: Bool {
|
||||||
|
get {
|
||||||
|
guard let player = player else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return player.rate == 0.0
|
||||||
|
}
|
||||||
|
set(paused) {
|
||||||
|
if paused {
|
||||||
|
self.player?.pause()
|
||||||
|
} else {
|
||||||
|
self.player?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool, playbackRate: Float) {
|
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool, playbackRate: Float) {
|
||||||
if player != nil {
|
if player != nil {
|
||||||
@@ -21,16 +57,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()
|
||||||
@@ -39,7 +75,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? {
|
||||||
@@ -49,44 +85,25 @@ class PlayerHandler {
|
|||||||
self.player?.setPlaybackRate(speed)
|
self.player?.setPlaybackRate(speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func play() {
|
|
||||||
self.player?.play()
|
|
||||||
}
|
|
||||||
public static func pause() {
|
|
||||||
self.player?.play()
|
|
||||||
}
|
|
||||||
public static func playPause() {
|
|
||||||
if paused() {
|
|
||||||
self.player?.play()
|
|
||||||
} else {
|
|
||||||
self.player?.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, from: "handler")
|
player.seek(destinationTime, from: "handler")
|
||||||
}
|
}
|
||||||
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, 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 paused() -> Bool {
|
|
||||||
player?.rate == 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getMetdata() -> [String: Any] {
|
public static func getMetdata() -> [String: Any] {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
syncProgress()
|
syncProgress()
|
||||||
@@ -95,32 +112,38 @@ class PlayerHandler {
|
|||||||
return [
|
return [
|
||||||
"duration": player?.getDuration() ?? 0,
|
"duration": player?.getDuration() ?? 0,
|
||||||
"currentTime": player?.getCurrentTime() ?? 0,
|
"currentTime": player?.getCurrentTime() ?? 0,
|
||||||
"playerState": !paused(),
|
"playerState": !paused,
|
||||||
"currentRate": player?.rate ?? 0,
|
"currentRate": player?.rate ?? 0,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func tick() {
|
private static func tick() {
|
||||||
if !paused() {
|
if !paused {
|
||||||
listeningTimePassedSinceLastSync += 1
|
listeningTimePassedSinceLastSync += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if listeningTimePassedSinceLastSync > 3 {
|
if listeningTimePassedSinceLastSync > 3 {
|
||||||
syncProgress()
|
syncProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remainingSleepTime != nil {
|
||||||
|
if remainingSleepTime! == 0 {
|
||||||
|
paused = true
|
||||||
|
}
|
||||||
|
remainingSleepTime! -= 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static func syncProgress() {
|
public static func syncProgress() {
|
||||||
if player == nil || session == nil {
|
if session == nil { return }
|
||||||
return
|
guard let player = player else { return }
|
||||||
}
|
|
||||||
|
|
||||||
let playerCurrentTime = player!.getCurrentTime()
|
let playerCurrentTime = player.getCurrentTime()
|
||||||
if (lastSyncReport != nil && lastSyncReport?.currentTime == playerCurrentTime) {
|
if (lastSyncReport != nil && lastSyncReport?.currentTime == playerCurrentTime) {
|
||||||
// No need to syncProgress
|
// No need to syncProgress
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var instance: Realm
|
||||||
|
private init() {
|
||||||
|
self.instance = try! Realm(queue: Database.realmQueue)
|
||||||
|
}
|
||||||
|
|
||||||
public static func setServerConnectionConfig(config: ServerConnectionConfig) {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ import Foundation
|
|||||||
enum PlayerEvents: String {
|
enum PlayerEvents: String {
|
||||||
case update = "com.audiobookshelf.app.player.update"
|
case update = "com.audiobookshelf.app.player.update"
|
||||||
case closed = "com.audiobookshelf.app.player.closed"
|
case closed = "com.audiobookshelf.app.player.closed"
|
||||||
|
case sleepSet = "com.audiobookshelf.app.player.sleep.set"
|
||||||
|
case sleepEnded = "com.audiobookshelf.app.player.sleep.ended"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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