feat: now playing chapter track

This commit is contained in:
benonymity 2023-12-22 21:01:37 -05:00
parent 793f0c05f7
commit 2b1667e532
11 changed files with 91 additions and 11 deletions

View file

@ -4,17 +4,17 @@ import RealmSwift
@UIApplicationMain @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
private let logger = AppLogger(category: "AppDelegate") private let logger = AppLogger(category: "AppDelegate")
lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
var backgroundCompletionHandler: (() -> Void)? var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch. // Override point for customization after application launch.
let configuration = Realm.Configuration( let configuration = Realm.Configuration(
schemaVersion: 15, schemaVersion: 16,
migrationBlock: { [weak self] migration, oldSchemaVersion in migrationBlock: { [weak self] migration, oldSchemaVersion in
if (oldSchemaVersion < 1) { if (oldSchemaVersion < 1) {
self?.logger.log("Realm schema version was \(oldSchemaVersion)") self?.logger.log("Realm schema version was \(oldSchemaVersion)")
@ -48,10 +48,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
newObject?["languageCode"] = "en-us" 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 Realm.Configuration.defaultConfiguration = configuration
return true return true
} }
@ -93,7 +100,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// tracking app url opens, make sure to keep this call // tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
} }
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
// Stores the completion handler for background downloads // 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 // The identifier of this method can be ignored at this time as we only have one background url session

View file

@ -15,6 +15,7 @@ CAP_PLUGIN(AbsAudioPlayer, "AbsAudioPlayer",
CAP_PLUGIN_METHOD(closePlayback, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(closePlayback, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(setChapterTrack, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise);

View file

@ -119,6 +119,17 @@ public class AbsAudioPlayer: CAPPlugin {
PlayerHandler.setPlaybackSpeed(speed: settings.playbackRate) PlayerHandler.setPlaybackSpeed(speed: settings.playbackRate)
call.resolve() 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) { @objc func playPlayer(_ call: CAPPluginCall) {
PlayerHandler.paused = false PlayerHandler.paused = false

View file

@ -160,3 +160,11 @@ class PlaybackSession: Object, Codable, Deletable {
try container.encode(localMediaProgressId, forKey: .localMediaProgressId) try container.encode(localMediaProgressId, forKey: .localMediaProgressId)
} }
} }
extension PlaybackSession {
func getCurrentChapter() -> Chapter? {
return chapters.first { chapter in
chapter.start <= self.currentTime && chapter.end > self.currentTime
}
}
}

View file

@ -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 // 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 // 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 playbackRate: Float = 1.0
@Persisted var chapterTrack: Bool = true
// Singleton pattern for Realm objects // Singleton pattern for Realm objects
static func main() -> PlayerSettings { static func main() -> PlayerSettings {

View file

@ -39,3 +39,13 @@ class Chapter: EmbeddedObject, Codable {
try container.encode(title, forKey: .title) 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
}
}

View file

@ -224,6 +224,8 @@ class AudioPlayer: NSObject {
// Update the UI // Update the UI
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil) 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? { public func getCurrentTime() -> Double? {
guard let playbackSession = self.getPlaybackSession() else { return nil } guard let playbackSession = self.getPlaybackSession() else { return nil }
let currentTrackTime = self.audioPlayer.currentTime().seconds let currentTrackTime = self.audioPlayer.currentTime().seconds
@ -657,7 +663,16 @@ 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)
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) NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate)
} }
} }

View file

@ -61,6 +61,10 @@ class PlayerHandler {
self.player?.setPlaybackRate(speed) self.player?.setPlaybackRate(speed)
} }
public static func setChapterTrack() {
self.player?.setChapterTrack()
}
public static func getSleepTimeRemaining() -> Double? { public static func getSleepTimeRemaining() -> Double? {
return self.player?.getSleepTimeRemaining() return self.player?.getSleepTimeRemaining()
} }

View file

@ -53,7 +53,7 @@ class NowPlayingInfo {
self.setMetadata(artwork: artwork, metadata: metadata) 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 // Update on the main to prevent access collisions
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
if let self = self { if let self = self {
@ -62,6 +62,18 @@ class NowPlayingInfo {
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate
self.nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = 1.0 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 MPNowPlayingInfoCenter.default().nowPlayingInfo = self.nowPlayingInfo
} }
} }
@ -89,7 +101,7 @@ class NowPlayingInfo {
nowPlayingInfo[MPMediaItemPropertyTitle] = metadata!.title nowPlayingInfo[MPMediaItemPropertyTitle] = metadata!.title
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown" nowPlayingInfo[MPMediaItemPropertyArtist] = metadata!.author ?? "unknown"
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.series nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata!.title
} }
private func shouldFetchCover(id: String) -> Bool { private func shouldFetchCover(id: String) -> Bool {

View file

@ -141,6 +141,11 @@ class AbsAudioPlayerWeb extends WebPlugin {
if (this.player) this.player.playbackRate = value if (this.player) this.player.playbackRate = value
} }
// PluginMethod
setChapterTrack({ enabled }) {
this.useChapterTrack = enabled
}
// PluginMethod // PluginMethod
async getCurrentTime() { async getCurrentTime() {
return { return {
@ -262,4 +267,4 @@ export { AbsAudioPlayer }
export default ({ app, store }, inject) => { export default ({ app, store }, inject) => {
$axios = app.$axios $axios = app.$axios
vuexStore = store vuexStore = store
} }

View file

@ -1,4 +1,6 @@
import { Preferences } from '@capacitor/preferences' import { Preferences } from '@capacitor/preferences'
import { AbsAudioPlayer } from '@/plugins/capacitor'
class LocalStorage { class LocalStorage {
constructor(vuexStore) { constructor(vuexStore) {
@ -45,6 +47,9 @@ class LocalStorage {
async setUseChapterTrack(useChapterTrack) { async setUseChapterTrack(useChapterTrack) {
try { try {
await Preferences.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' }) await Preferences.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
console.log("ooooooo")
console.log(useChapterTrack)
AbsAudioPlayer.setChapterTrack({ enabled: useChapterTrack })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set use chapter track', error) console.error('[LocalStorage] Failed to set use chapter track', error)
} }
@ -184,4 +189,4 @@ class LocalStorage {
export default ({ app, store }, inject) => { export default ({ app, store }, inject) => {
inject('localStore', new LocalStorage(store)) inject('localStore', new LocalStorage(store))
} }