diff --git a/ios/App/App/AppDelegate.swift b/ios/App/App/AppDelegate.swift index e1423b13..26a74dcc 100644 --- a/ios/App/App/AppDelegate.swift +++ b/ios/App/App/AppDelegate.swift @@ -4,17 +4,17 @@ import RealmSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - + private let logger = AppLogger(category: "AppDelegate") lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) var backgroundCompletionHandler: (() -> Void)? - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - + let configuration = Realm.Configuration( - schemaVersion: 15, + schemaVersion: 16, migrationBlock: { [weak self] migration, oldSchemaVersion in if (oldSchemaVersion < 1) { self?.logger.log("Realm schema version was \(oldSchemaVersion)") @@ -48,10 +48,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { newObject?["languageCode"] = "en-us" } } + if (oldSchemaVersion < 16) { + self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding chapterTrack setting") + migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in + newObject?["chapterTrack"] = false + } + } + } ) Realm.Configuration.defaultConfiguration = configuration - + return true } @@ -93,7 +100,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) } - + func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { // Stores the completion handler for background downloads // The identifier of this method can be ignored at this time as we only have one background url session diff --git a/ios/App/App/plugins/AbsAudioPlayer.m b/ios/App/App/plugins/AbsAudioPlayer.m index 49aaff7a..8170d059 100644 --- a/ios/App/App/plugins/AbsAudioPlayer.m +++ b/ios/App/App/plugins/AbsAudioPlayer.m @@ -15,6 +15,7 @@ CAP_PLUGIN(AbsAudioPlayer, "AbsAudioPlayer", CAP_PLUGIN_METHOD(closePlayback, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(setChapterTrack, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise); diff --git a/ios/App/App/plugins/AbsAudioPlayer.swift b/ios/App/App/plugins/AbsAudioPlayer.swift index 5ebe376c..edc4e274 100644 --- a/ios/App/App/plugins/AbsAudioPlayer.swift +++ b/ios/App/App/plugins/AbsAudioPlayer.swift @@ -119,6 +119,17 @@ public class AbsAudioPlayer: CAPPlugin { PlayerHandler.setPlaybackSpeed(speed: settings.playbackRate) call.resolve() } + + @objc func setChapterTrack(_ call: CAPPluginCall) { + let chapterTrack = call.getBool("enabled", true) + logger.log(String(chapterTrack)) + let settings = PlayerSettings.main() + try? settings.update { + settings.chapterTrack = chapterTrack + } + PlayerHandler.setChapterTrack() + call.resolve() + } @objc func playPlayer(_ call: CAPPluginCall) { PlayerHandler.paused = false diff --git a/ios/App/Shared/models/PlaybackSession.swift b/ios/App/Shared/models/PlaybackSession.swift index baa89553..ad6812ae 100644 --- a/ios/App/Shared/models/PlaybackSession.swift +++ b/ios/App/Shared/models/PlaybackSession.swift @@ -160,3 +160,11 @@ class PlaybackSession: Object, Codable, Deletable { try container.encode(localMediaProgressId, forKey: .localMediaProgressId) } } + +extension PlaybackSession { + func getCurrentChapter() -> Chapter? { + return chapters.first { chapter in + chapter.start <= self.currentTime && chapter.end > self.currentTime + } + } +} diff --git a/ios/App/Shared/models/PlayerSettings.swift b/ios/App/Shared/models/PlayerSettings.swift index 0a8b3788..c768c50c 100644 --- a/ios/App/Shared/models/PlayerSettings.swift +++ b/ios/App/Shared/models/PlayerSettings.swift @@ -12,6 +12,8 @@ class PlayerSettings: Object { // The webapp has a persisted setting for playback speed, but it's not always available to the native code // Lets track it natively as well, so we never have a situation where the UI and native player are out of sync @Persisted var playbackRate: Float = 1.0 + @Persisted var chapterTrack: Bool = true + // Singleton pattern for Realm objects static func main() -> PlayerSettings { diff --git a/ios/App/Shared/models/server/Chapter.swift b/ios/App/Shared/models/server/Chapter.swift index 2e4d1e02..537ce6c7 100644 --- a/ios/App/Shared/models/server/Chapter.swift +++ b/ios/App/Shared/models/server/Chapter.swift @@ -39,3 +39,13 @@ class Chapter: EmbeddedObject, Codable { try container.encode(title, forKey: .title) } } + +extension Chapter { + func getRelativeChapterCurrentTime(sessionCurrentTime: Double) -> Double { + return sessionCurrentTime - self.start + } + + func getRelativeChapterEndTime() -> Double { + return self.end - self.start + } +} diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 665d776c..3ebe001b 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -224,6 +224,8 @@ class AudioPlayer: NSObject { // Update the UI NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil) } + // Update the now playing and chapter info + self.updateNowPlaying() } } } @@ -443,6 +445,10 @@ class AudioPlayer: NSObject { } } + public func setChapterTrack() { + self.updateNowPlaying() + } + public func getCurrentTime() -> Double? { guard let playbackSession = self.getPlaybackSession() else { return nil } let currentTrackTime = self.audioPlayer.currentTime().seconds @@ -657,7 +663,16 @@ class AudioPlayer: NSObject { } private func updateNowPlaying() { NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil) - if let duration = self.getDuration(), let currentTime = self.getCurrentTime() { + if let session = self.getPlaybackSession(), let currentChapter = session.getCurrentChapter(), PlayerSettings.main().chapterTrack { + NowPlayingInfo.shared.update( + duration: currentChapter.getRelativeChapterEndTime(), + currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime), + rate: rate, + chapterName: currentChapter.title, + chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1, + chapterCount: session.chapters.count + ) + } else if let duration = self.getDuration(), let currentTime = self.getCurrentTime() { NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate) } } diff --git a/ios/App/Shared/player/PlayerHandler.swift b/ios/App/Shared/player/PlayerHandler.swift index 8b6541bc..718cdf95 100644 --- a/ios/App/Shared/player/PlayerHandler.swift +++ b/ios/App/Shared/player/PlayerHandler.swift @@ -61,6 +61,10 @@ class PlayerHandler { self.player?.setPlaybackRate(speed) } + public static func setChapterTrack() { + self.player?.setChapterTrack() + } + public static func getSleepTimeRemaining() -> Double? { return self.player?.getSleepTimeRemaining() } diff --git a/ios/App/Shared/util/NowPlayingInfo.swift b/ios/App/Shared/util/NowPlayingInfo.swift index 6393611c..86ca8ad4 100644 --- a/ios/App/Shared/util/NowPlayingInfo.swift +++ b/ios/App/Shared/util/NowPlayingInfo.swift @@ -53,7 +53,7 @@ class NowPlayingInfo { self.setMetadata(artwork: artwork, metadata: metadata) } } - public func update(duration: Double, currentTime: Double, rate: Float) { + public func update(duration: Double, currentTime: Double, rate: Float, chapterName: String? = nil, chapterNumber: Int? = nil, chapterCount: Int? = nil) { // Update on the main to prevent access collisions DispatchQueue.main.async { [weak self] in if let self = self { @@ -62,6 +62,18 @@ class NowPlayingInfo { self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate self.nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = 1.0 + + if let chapterName = chapterName, let chapterNumber = chapterNumber, let chapterCount = chapterCount { + self.nowPlayingInfo[MPMediaItemPropertyTitle] = chapterName + self.nowPlayingInfo[MPNowPlayingInfoPropertyChapterNumber] = chapterNumber + self.nowPlayingInfo[MPNowPlayingInfoPropertyChapterCount] = chapterCount + } else { + // Set the title back to the book title + self.nowPlayingInfo[MPMediaItemPropertyTitle] = self.nowPlayingInfo[MPMediaItemPropertyAlbumTitle] + self.nowPlayingInfo[MPNowPlayingInfoPropertyChapterNumber] = nil + self.nowPlayingInfo[MPNowPlayingInfoPropertyChapterCount] = nil + } + MPNowPlayingInfoCenter.default().nowPlayingInfo = self.nowPlayingInfo } } @@ -89,7 +101,7 @@ class NowPlayingInfo { nowPlayingInfo[MPMediaItemPropertyTitle] = metadata!.title nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown" - nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.series + nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.title } private func shouldFetchCover(id: String) -> Bool { diff --git a/plugins/capacitor/AbsAudioPlayer.js b/plugins/capacitor/AbsAudioPlayer.js index 7018d91d..fa9215a9 100644 --- a/plugins/capacitor/AbsAudioPlayer.js +++ b/plugins/capacitor/AbsAudioPlayer.js @@ -141,6 +141,11 @@ class AbsAudioPlayerWeb extends WebPlugin { if (this.player) this.player.playbackRate = value } + // PluginMethod + setChapterTrack({ enabled }) { + this.useChapterTrack = enabled + } + // PluginMethod async getCurrentTime() { return { @@ -262,4 +267,4 @@ export { AbsAudioPlayer } export default ({ app, store }, inject) => { $axios = app.$axios vuexStore = store -} \ No newline at end of file +} diff --git a/plugins/localStore.js b/plugins/localStore.js index d5eee3bd..d87c59e1 100644 --- a/plugins/localStore.js +++ b/plugins/localStore.js @@ -1,4 +1,6 @@ import { Preferences } from '@capacitor/preferences' +import { AbsAudioPlayer } from '@/plugins/capacitor' + class LocalStorage { constructor(vuexStore) { @@ -45,6 +47,9 @@ class LocalStorage { async setUseChapterTrack(useChapterTrack) { try { await Preferences.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' }) + console.log("ooooooo") + console.log(useChapterTrack) + AbsAudioPlayer.setChapterTrack({ enabled: useChapterTrack }) } catch (error) { console.error('[LocalStorage] Failed to set use chapter track', error) } @@ -184,4 +189,4 @@ class LocalStorage { export default ({ app, store }, inject) => { inject('localStore', new LocalStorage(store)) -} \ No newline at end of file +}