Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS Now Playing Chapter Track (updated #372) #1002

Merged
merged 6 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions components/app/AudioPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -695,22 +695,29 @@ export default {
this.updateTimestamp()
this.updateTrack()
this.updateReadyTrack()
this.updateUseChapterTrack()
this.$localStore.setUseTotalTrack(this.useTotalTrack)
this.$localStore.setUseChapterTrack(this.useChapterTrack)
} else if (action === 'total_track') {
this.useTotalTrack = !this.useTotalTrack
this.useChapterTrack = !this.useTotalTrack || this.useChapterTrack

this.updateTimestamp()
this.updateTrack()
this.updateReadyTrack()
this.updateUseChapterTrack()
this.$localStore.setUseTotalTrack(this.useTotalTrack)
this.$localStore.setUseChapterTrack(this.useChapterTrack)
} else if (action === 'close') {
this.closePlayback()
}
})
},
updateUseChapterTrack() {
this.$localStore.setUseChapterTrack(this.useChapterTrack)
// Chapter track in NowPlaying only supported on iOS for now
if (this.$platform === 'ios') {
AbsAudioPlayer.setChapterTrack({ enabled: this.useChapterTrack })
}
},
forceCloseDropdownMenu() {
if (this.$refs.dropdownMenu && this.$refs.dropdownMenu.closeMenu) {
this.$refs.dropdownMenu.closeMenu()
Expand Down
19 changes: 13 additions & 6 deletions ios/App/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions ios/App/App/plugins/AbsAudioPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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);
Expand Down
11 changes: 11 additions & 0 deletions ios/App/App/plugins/AbsAudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions ios/App/Shared/models/PlaybackSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
2 changes: 2 additions & 0 deletions ios/App/Shared/models/PlayerSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions ios/App/Shared/models/server/Chapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
26 changes: 24 additions & 2 deletions ios/App/Shared/player/AudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -662,7 +668,14 @@ class AudioPlayer: NSObject {
return .noSuchContent
}

self?.seek(event.positionTime, from: "remote")
// Adjust seek time if chapter track is being used
var seekTime = event.positionTime
if PlayerSettings.main().chapterTrack {
if let session = self?.getPlaybackSession(), let currentChapter = session.getCurrentChapter() {
seekTime += currentChapter.start
}
}
self?.seek(seekTime, from: "remote")
return .success
}

Expand All @@ -679,7 +692,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)
}
}
Expand Down
4 changes: 4 additions & 0 deletions ios/App/Shared/player/PlayerHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
16 changes: 14 additions & 2 deletions ios/App/Shared/util/NowPlayingInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion plugins/capacitor/AbsAudioPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -262,4 +267,4 @@ export { AbsAudioPlayer }
export default ({ app, store }, inject) => {
$axios = app.$axios
vuexStore = store
}
}
2 changes: 1 addition & 1 deletion plugins/localStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,4 @@ class LocalStorage {

export default ({ app, store }, inject) => {
inject('localStore', new LocalStorage(store))
}
}
Loading