[PR #1845] Fix Android chapter-relative notifications #1723

Open
opened 2026-04-25 00:00:50 +02:00 by adam · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/advplyr/audiobookshelf-app/pull/1845
Author: @Kyrluckechuck
Created: 4/13/2026
Status: 🔄 Open

Base: masterHead: fix/android-chapter-relative-notification


📝 Commits (3)

  • d959e8a Fix Android chapter-relative notifications
  • b789203 Also fix offline notification art
  • e90467a Ivalidate media session playback (force duration update)

📊 Changes

15 files changed (+408 additions, -49 deletions)

View changed files

📝 android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt (+67 -13)
📝 android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt (+31 -2)
android/app/src/main/java/com/audiobookshelf/app/player/ChapterAwareForwardingPlayer.kt (+50 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt (+14 -1)
📝 android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt (+2 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt (+162 -20)
📝 android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt (+18 -0)
📝 components/app/AudioPlayer.vue (+19 -5)
📝 ios/App/App/AppDelegate.swift (+7 -1)
📝 ios/App/App/plugins/AbsAudioPlayer.swift (+12 -0)
📝 ios/App/Shared/models/PlayerSettings.swift (+1 -0)
📝 ios/App/Shared/player/AudioPlayer.swift (+13 -3)
📝 ios/App/Shared/player/PlayerHandler.swift (+4 -0)
📝 ios/App/Shared/util/NowPlayingInfo.swift (+7 -4)
📝 strings/en-us.json (+1 -0)

📄 Description

Brief summary

Fixes the Android notification and Android Auto progress bar showing full-book progress for multi-chapter files (m4b, etc) instead of per-chapter progress. Brings the existing iOS chapter title display to Android, and adds a small new toggle for whether the secondary notification line shows the book title or the author.

Also fixes a minor second bug related to permission grants that stopped downloaded playback from being able to correctly stream the art/metadata since it wasn't an https URI.

Which issue is fixed?

Fixes #239

Pull Request Type

Mostly Android, with symmetric iOS changes. Touches both the native sides (Kotlin/Swift) and the JS frontend (Vue dropdown menu and backing code)

In-depth Description

iOS already handled this by populating MPNowPlayingInfoCenter directly with chapter-relative duration and current time. Android can't do the same trick because PlayerNotificationManager and MediaSessionConnector both pull position and duration straight from the Player interface, so the override has to live at the player boundary. The method wraps mPlayer (and the cast player) in a ChapterAwareForwardingPlayer that overrides getCurrentPosition, getDuration, and getBufferedPosition with chapter-relative values when chapter mode is on. The real player stays untouched so the rest of the service still operates in absolute book time.

A few other things in here:

  • A custom MediaMetadataProvider on the connector emits chapter-aware MediaMetadataCompat (chapter title in METADATA_KEY_TITLE, chapter duration, "Chapter X of Y" track number).
  • A 1-second ticker that mirrors iOS's addPeriodicTimeObserver pattern. It only runs while playing and invalidates the system UI when natural playback crosses a chapter boundary so the displayed chapter title and duration update.
  • AbMediaDescriptionAdapter reads directly from the playback session for the in-shade notification, since some Android versions populate that from Builder.mContentText rather than from the session metadata.

For sync, JS is the source of truth. Native caches in SharedPreferences (Android) and the existing Realm-backed PlayerSettings (iOS) so cold-start scenarios like Android Auto resuming before the JS app boots get the right behaviour. JS pushes the value to native on every toggle and app load

Also bundles a small fix for a pre-existing bug where offline-downloaded book covers were blank in Android Auto. The cause was that local FileProvider cover URIs were only being granted read permission to com.android.systemui (the lock screen), not to Android Auto's projection process, so Android Auto silently failed to load them and showed a blank background. Streamed covers were unaffected since those are HTTPS URLs that don't need a grant. The fix adds a small helper that grants permission to both processes and is shared between the metadata builder and the queue navigator. Happy to split this out into a separate PR if reviewers prefer.

A few decisions worth flagging:

  1. The new "Show author below chapter title" toggle defaults off, which means the secondary notification slot shows the book title by default. iOS previously always had the author in that slot (via the artist field) when chapter mode was on. So existing iOS users will see a small visible change after upgrading: the line under the chapter title becomes the book title. They can flip the toggle on to restore the old behaviour, or if it's preferred I can make this setting default to on.

  2. Setting METADATA_KEY_ARTIST to the book title in chapter mode is technically a small lie since "artist" semantically means author. The reason is that Notification.MediaStyle on Android pulls the secondary line from METADATA_KEY_ARTIST off the session and overrides whatever was set via Builder.mContentText. Author still flows through METADATA_KEY_ALBUM_ARTIST and METADATA_KEY_AUTHOR for any consumer that reads those (Bluetooth carkits typically read ALBUM_ARTIST as the secondary attribution).

  3. The Realm schema version on iOS had to be bumped from 20 to 21 with a migration block for the new useAuthorAsChapterSubtitle property. Without this, existing iOS users would crash on app upgrade. Followed the same pattern as the existing chapterTrack migration.

  4. One small pre-existing JS bug fixed along the way: AudioPlayer.vue loadPlayerSettings never pushed the persisted chapter-track value back down to native, so on cold-start the native side could be out of sync with the JS-stored value until the user toggled it. The push on load fixes this so it's the same on both platforms

How have you tested this?

Note

I do not have any iOS/CarPlay-compatible devices to test this with, so would appreciate if anyone can confirm that

Tested in various scenarios mostly involving m4b since that's what I have available to me. Tests include using Android Auto.
Would appreciate someone with iOS (and ideally CarPlay as well) to confirm the changes on that side work as expected, as additionally I do not have a Mac to build this on.

Screenshots

Existing behaviour, showing full-duration in notification (released, same in AA):
image

Android notification, chapter mode on, m4b playing, chapter-relative time
image

Android Auto now-playing screen with the same m4b playing, showing Chapter title, book title in the secondary line, and chapter-relative scrubber
image

The new toggle:
image

Android notification with the new toggle on (secondary line shows the author)
image

Offline media cover's not displaying (non-streaming), including chapter notification changes:
image
After fix, including chapter notification changes:
image

Notes

I did notice an additional bug with how the chapter skip-ahead works, where the skip to end of chapter doesn't actually progress the bar to the next chapter and sits at the end due to how the floor(timestamp) works if the chapter isn't rounded to a clean integer, and will follow that up in a future PR to keep this more tightly scoped with just the one additional bug fix for the metadata image.
Added PR https://github.com/advplyr/audiobookshelf-app/pull/1848 to fix this


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/advplyr/audiobookshelf-app/pull/1845 **Author:** [@Kyrluckechuck](https://github.com/Kyrluckechuck) **Created:** 4/13/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `fix/android-chapter-relative-notification` --- ### 📝 Commits (3) - [`d959e8a`](https://github.com/advplyr/audiobookshelf-app/commit/d959e8a0db5929205145abcfa0d62119504b0a7f) Fix Android chapter-relative notifications - [`b789203`](https://github.com/advplyr/audiobookshelf-app/commit/b789203e5232eb6168d2ae3c746f3fce4fe56788) Also fix offline notification art - [`e90467a`](https://github.com/advplyr/audiobookshelf-app/commit/e90467afefa4ec4fbedab978b81fa1e2028ac092) Ivalidate media session playback (force duration update) ### 📊 Changes **15 files changed** (+408 additions, -49 deletions) <details> <summary>View changed files</summary> 📝 `android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt` (+67 -13) 📝 `android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt` (+31 -2) ➕ `android/app/src/main/java/com/audiobookshelf/app/player/ChapterAwareForwardingPlayer.kt` (+50 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt` (+14 -1) 📝 `android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt` (+2 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt` (+162 -20) 📝 `android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt` (+18 -0) 📝 `components/app/AudioPlayer.vue` (+19 -5) 📝 `ios/App/App/AppDelegate.swift` (+7 -1) 📝 `ios/App/App/plugins/AbsAudioPlayer.swift` (+12 -0) 📝 `ios/App/Shared/models/PlayerSettings.swift` (+1 -0) 📝 `ios/App/Shared/player/AudioPlayer.swift` (+13 -3) 📝 `ios/App/Shared/player/PlayerHandler.swift` (+4 -0) 📝 `ios/App/Shared/util/NowPlayingInfo.swift` (+7 -4) 📝 `strings/en-us.json` (+1 -0) </details> ### 📄 Description ## Brief summary Fixes the Android notification and Android Auto progress bar showing full-book progress for multi-chapter files (m4b, etc) instead of per-chapter progress. Brings the existing iOS chapter title display to Android, and adds a small new toggle for whether the secondary notification line shows the book title or the author. Also fixes a minor second bug related to permission grants that stopped downloaded playback from being able to correctly stream the art/metadata since it wasn't an https URI. ## Which issue is fixed? Fixes #239 ## Pull Request Type Mostly Android, with symmetric iOS changes. Touches both the native sides (Kotlin/Swift) and the JS frontend (Vue dropdown menu and backing code) ## In-depth Description iOS already handled this by populating `MPNowPlayingInfoCenter` directly with chapter-relative duration and current time. Android can't do the same trick because `PlayerNotificationManager` and `MediaSessionConnector` both pull position and duration straight from the `Player` interface, so the override has to live at the player boundary. The method wraps `mPlayer` (and the cast player) in a `ChapterAwareForwardingPlayer` that overrides `getCurrentPosition`, `getDuration`, and `getBufferedPosition` with chapter-relative values when chapter mode is on. The real player stays untouched so the rest of the service still operates in absolute book time. A few other things in here: - A custom `MediaMetadataProvider` on the connector emits chapter-aware `MediaMetadataCompat` (chapter title in `METADATA_KEY_TITLE`, chapter duration, "Chapter X of Y" track number). - A 1-second ticker that mirrors iOS's `addPeriodicTimeObserver` pattern. It only runs while playing and invalidates the system UI when natural playback crosses a chapter boundary so the displayed chapter title and duration update. - `AbMediaDescriptionAdapter` reads directly from the playback session for the in-shade notification, since some Android versions populate that from `Builder.mContentText` rather than from the session metadata. For sync, JS is the source of truth. Native caches in `SharedPreferences` (Android) and the existing Realm-backed `PlayerSettings` (iOS) so cold-start scenarios like Android Auto resuming before the JS app boots get the right behaviour. JS pushes the value to native on every toggle and app load Also bundles a small fix for a pre-existing bug where offline-downloaded book covers were blank in Android Auto. The cause was that local FileProvider cover URIs were only being granted read permission to `com.android.systemui` (the lock screen), not to Android Auto's projection process, so Android Auto silently failed to load them and showed a blank background. Streamed covers were unaffected since those are HTTPS URLs that don't need a grant. The fix adds a small helper that grants permission to both processes and is shared between the metadata builder and the queue navigator. Happy to split this out into a separate PR if reviewers prefer. A few decisions worth flagging: 1. The new "Show author below chapter title" toggle defaults off, which means the secondary notification slot shows the book title by default. iOS previously always had the author in that slot (via the artist field) when chapter mode was on. So existing iOS users will see a small visible change after upgrading: the line under the chapter title becomes the book title. They can flip the toggle on to restore the old behaviour, or if it's preferred I can make this setting default to on. 2. Setting `METADATA_KEY_ARTIST` to the book title in chapter mode is technically a small lie since "artist" semantically means author. The reason is that `Notification.MediaStyle` on Android pulls the secondary line from `METADATA_KEY_ARTIST` off the session and overrides whatever was set via `Builder.mContentText`. Author still flows through `METADATA_KEY_ALBUM_ARTIST` and `METADATA_KEY_AUTHOR` for any consumer that reads those (Bluetooth carkits typically read `ALBUM_ARTIST` as the secondary attribution). 3. The Realm schema version on iOS had to be bumped from 20 to 21 with a migration block for the new `useAuthorAsChapterSubtitle` property. Without this, existing iOS users would crash on app upgrade. Followed the same pattern as the existing chapterTrack migration. 4. One small pre-existing JS bug fixed along the way: AudioPlayer.vue `loadPlayerSettings` never pushed the persisted chapter-track value back down to native, so on cold-start the native side could be out of sync with the JS-stored value until the user toggled it. The push on load fixes this so it's the same on both platforms ## How have you tested this? > [!NOTE] > I do not have any iOS/CarPlay-compatible devices to test this with, so would appreciate if anyone can confirm that Tested in various scenarios mostly involving m4b since that's what I have available to me. Tests include using Android Auto. Would appreciate someone with iOS (and ideally CarPlay as well) to confirm the changes on that side work as expected, as additionally I do not have a Mac to build this on. ## Screenshots Existing behaviour, showing full-duration in notification (released, same in AA): <img width="523" height="288" alt="image" src="https://github.com/user-attachments/assets/6257fc08-c37b-4f9b-9dee-6204c7f9562a" /> Android notification, chapter mode on, m4b playing, chapter-relative time <img width="565" height="300" alt="image" src="https://github.com/user-attachments/assets/e0291e3d-2ac9-419e-ba83-d189d0201f4c" /> Android Auto now-playing screen with the same m4b playing, showing Chapter title, book title in the secondary line, and chapter-relative scrubber <img width="315" height="482" alt="image" src="https://github.com/user-attachments/assets/12eff816-1e0a-4c9d-8aff-5575528a8581" /> The new toggle: <img width="484" height="639" alt="image" src="https://github.com/user-attachments/assets/3f92f429-b55b-44e7-971f-70add167fab5" /> Android notification with the new toggle on (secondary line shows the author) <img width="520" height="271" alt="image" src="https://github.com/user-attachments/assets/b8394622-8529-40a1-82f1-2f9bd073571a" /> Offline media cover's not displaying (non-streaming), including chapter notification changes: <img width="318" height="406" alt="image" src="https://github.com/user-attachments/assets/4c55f1a5-4980-4636-836f-2349c81471d2" /> After fix, including chapter notification changes: <img width="315" height="482" alt="image" src="https://github.com/user-attachments/assets/12eff816-1e0a-4c9d-8aff-5575528a8581" /> ## Notes I did notice an additional bug with how the chapter skip-ahead works, where the skip to end of chapter doesn't actually progress the bar to the next chapter and sits at the end due to how the `floor(timestamp)` works if the chapter isn't rounded to a clean integer, and will follow that up in a future PR to keep this more tightly scoped with just the one additional bug fix for the metadata image. Added PR https://github.com/advplyr/audiobookshelf-app/pull/1848 to fix this --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
adam added the pull-request label 2026-04-25 00:00:50 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf-app#1723