Skip to content

Feat: Android Auto support, speed scaling and customization - #307

Merged
van-geaux merged 45 commits into
van-geaux:mainfrom
whattheschnell:feat/android-auto-support
Oct 28, 2026
Merged

van-geaux merged 45 commits into
van-geaux:mainfrom
whattheschnell:feat/android-auto-support

Conversation

@whattheschnell

@whattheschnell whattheschnell commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer-requested exception

  • A maintainer requested this pull request or confirmed the exception.
  • The change is scoped to the requested work.
  • The change is compatible with the multi-server/provider-module refactor.
  • Tests and manual-validation status are documented.
  • No credentials, cookies, API keys, private server URLs, or generated local data are included.

Summary

his PR introduces robust Android Auto media browsing and playback support to Lagrange Reader, adds a dedicated speed-cycling control, fixes multi-library cover art loading, and ensures playback progress, remaining time, and total durations scale correctly with playback speed across both Android Auto and the base app UI.

Key Changes
Android Auto Custom Layout & Controls:

  • Implemented Cycle Playback Speed button (0.8x to 2.0x) alongside core controls (-10s, +30s) in the secondary playback row.

  • Set secondary actions to exactly 3 items (-10s, +30s, and Cycle Speed), completely eliminating the vertical ⋮ overflow drawer. Architecture for additional configurable items in the overflow is still in place for future features.

  • Mapped vehicle steering-wheel/head-unit skip actions to reliable 10s rewind / 30s fast-forward steps. Also included architecture to make this a user modifiable option in the future.

Speed-Scaled Timestamps & Total Durations:

  • New function scaleProgressByPlaybackSpeed() to allow for real world progress of tracks relative to selected playback speed. Default value is set to disabled. This can be found in the options menu.

  • When enabled, both elapsed positions, remaining countdowns, and total track/audiobook durations scale dynamically across the full player, compact mini-player, and Android Auto.

  • Updated LagrangeLibrarySessionPlayer to dispatch timeline/metadata updates on speed changes so Android Auto head units instantly recalculate total lengths (e.g., scaled .m4b book lengths) rather than caching unscaled totals.

Cover Art Caching & Catalog Bridges:

  • Fixed cover lookup stampedes by introducing an in-memory book index and short-lived snapshot cache, preventing overlappingContentProvider requests

  • Added fallback query keys via BookDetailModule to ensure books outside the immediate home shelf load their covers reliably.

M4B Single-File Chapter Progression:

  • Fixed single-file .m4b chapter skipping so jumping across chapters advances the progress bar along the overall book timeline instead of resetting elapsed time to 00:00:00.

Verification

Automated CI Build Verification: All code changes are pushed to GitHub (feat/android-auto-support) to run automated Gradle build verification and unit tests.

Android Auto Head-Unit UI Validation: Validated the custom media button layout on car head units to confirm that the secondary playback bar displays exactly three custom items (-10s, +30s, and the stateful Cycle Speed button) without overflow menu.

Speed-Scaling & Timeline Synchronization Checks: Verified that when speed-scaling is toggled, LagrangeLibrarySessionPlayer correctly dispatches onTimelineChanged and onMediaMetadataChanged events so Android Auto head units instantly recalculate and render scaled total durations alongside elapsed positions.

Single-File M4B & Hardware Key Assertions: Confirmed that single-file .m4b chapter skipping advances progress correctly along the overall book timeline without resetting to 00:00:00, and that vehicle steering-wheel and native head-unit buttons execute reliable 10s rewind / 30s fast-forward steps.

_Side note: This PR was intended to just be a simple way to add AA support, but it definitely snowballed. Somewhere around debugging album cover caches and suppressing the file extensions, I lost my place a bit and ended up tweaking helper classes that probably didn't strictly need a rewrite but it made sense at the time.

I still want to add some additional features, especially for user configuration, but I think the implementation as is will address what most people are minimally looking for in Android Auto Support

Next features

  • More configuration options for primary/secondary/overflow menu. (Sleep Timer, Favorite/Bookmark, Configurable Forward/Reverse skip values)
  • Fix AA Track/Chapter Queue
  • AA Playback scrubbing capability_

Screenshot 2026-10-16 105519'

closes #136

…port

- Upgrade ReadiumAudioPlaybackService to MediaLibraryService with a persistent
  MediaLibrarySession and LagrangeLibrarySessionPlayer (ForwardingPlayer) so
  Android Auto can connect, browse, and start playback before MainActivity
  opens without resetting pre-prepared Readium or Media3 playlists.
- Add AndroidAutoMediaLibrary with a four-tab browse hierarchy (Continue
  Listening, Downloaded, Recent, and Libraries/Series), chapter browsing,
  completion and download status metadata extras, chapter skip calculation,
  and voice search ranking.
- Add AndroidAutoCatalogBridge for cache-first catalog resolution, online and
  offline audiobook preparation (including multi-format books with audio
  files), playback resumption, and headless progress persistence when
  MainActivity is not running.
- Add AndroidAutoCoverProvider to serve cached and authenticated BookOrbit and
  Komga cover images to Android Auto via read-granted content:// URIs.
- Declare automotive_app_desc.xml, MediaLibraryService/MediaBrowserService
  intent filters, and car application metadata in AndroidManifest.xml.
- Add unit and instrumented test coverage in AndroidAutoMediaLibraryTest and
  AndroidAutoMediaSessionInstrumentedTest.
…dary rewind/forward/speed/bookmark UI actions
@whattheschnell

Copy link
Copy Markdown
Contributor Author

I'll resubmit. Found a few things that need to be addressed while doing more unit test

@whattheschnell

whattheschnell commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

While the core Android Auto feature is implemented in AndroidAutoMediaLibrary.kt, ReadiumAudioPlayback.kt has been updated to route the playback engine into Media3's MediaLibraryService architecture. Here is a summary of the changes there:

  • Upgraded ReadiumAudioPlaybackService from a standard MediaSessionService to a MediaLibraryService to allow the head units to connect and browse the catalog before playback starts.

  • Routed Android Auto browsing, search, and queue commands as pass-thru to AndroidAutoMediaLibrary.kt.

  • Wrapped active players in LagrangeLibrarySessionPlayer to intercept speed scaling, timeline durations, and vehicle transport/steering-wheel skip controls.

  • Injected artwork URIs, sanitized titles, and series info into MediaItem so head unit shows the correct album art and titles.

  • Added fallback handlers for progress recording and sleep timers when starting playback directly from the head unit.

@van-geaux

van-geaux commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Thanks @whattheschnell, this is a huge PR and it took me a while to review. The Android Auto browse and playback work is looking promising.

Before merging, could you address these things?

  • The media service is exported for Android Auto, but it currently accepts any Media3 controller. Please restrict browsing, searching, and playback to Android Auto or other trusted system controllers, so another app cannot access the user’s audiobook library.
  • Please link this PR to the relevant Android Auto tracking issue so the implementation and scope are connected.

Also, could you confirm that the speed-scaling, M4B progression, cover caching, and other playback changes are intentionally included in this Android Auto scope?

@whattheschnell

whattheschnell commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Good catch. I'll add some helper function(s) in AndroidAutoMediaLibrary and a something in ReadiumAudioPlayback to catch it.

Speed Scaling: This was a preference on my end. SmartAudioBookPlayer implemented this by default and I always found it particularly useful, especially when in the car. If you're driving and your Audiobook chapter is 60min and you're listening at 1.5x, its very convenient to know whether you're going to finish it by the time you get to your destination since you can't rely on the remaining time if its hasn't been adjusted for playback speed. This also prevents users from needing to look at their phone to see how much actual time is left while they're driving.

That being said, I haven't found a way to override the total track. I think its a hard limitation in the head unit when it grabs MediaMetadataCompat.METADATA_KEY_DURATION. Even if I force an update with thre recalculated version by overriding getCurrentMediaItem(), its going to get refreshed back to the default by PlaybackStateCompat. My current idea is to inject the speed-scaled remaining time into the Subtitle/Artist line. Whenever the user cycles the playback speed, the exact scaled remaining time gets updated too (something like: Author Name/Book Title/Chapter # −1h 1m left)

M4B Progression isn't really a feature. When I implemented the custom Timeline window overrides required for Android Auto's queue and speed-scaled durations, it broke single-file M4B chapter skipping (causing progress to reset to 00:00:00). This change was necessary to stabilize the timeline.

The Android Auto MediaLibraryService expects synchronous metadata. Without the in-memory snapshot and caching layer, the head unit times out while waiting for disk/network reads, resulting in missing artwork for books outside the immediate home shelf. I still need to work out a few kinks with this, but its much better.

@whattheschnell
whattheschnell force-pushed the feat/android-auto-support branch from 49cd2dd to d37c0db Compare October 22, 2026 19:17
@whattheschnell

whattheschnell commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Here's what I added:
isAcceptedAudiobookController & isAuthorizedBrowseOrPlaybackController are the validation helpers that verify if a client is authorized. They check for the app Media3's isTrusted flag, UIDs, & AA companion flags

I added a isSystemOrGoogleSignedPackage check that validates incoming 3rd party packages against the list of known Google SHA-256 signing certificates that are provided in the UAMP and Media3 sample apps to prevent package spoofing.

I had to make some additions to ReadiumAudioPlayback a to intercept this.

  • AudiobookMediaSessionCallback.onConnect takes it and returns MediaSession.ConnectionResult.reject() if the client fails the isAcceptedAudiobookController validation.

  • ReadAlongMediaSessionCallback.onConnect locks down strictly to the app's own UID and isTrusted system.

I also added security to MediaLibraryService for all the parent library functions to return SessionError.ERROR_PERMISSION_DENIED or empty lists if an unverified controller bypasses the initial connection handshake

@van-geaux

Copy link
Copy Markdown
Owner

Thanks @whattheschnell, the Android Auto browse and playback work has come a long way, and the controller validation is a solid improvement.

I found two entry points that still bypass the authorization check:

  • ReadiumAudioPlayback.kt:437-442, onGetLibraryRoot forwards directly to onGetAndroidAutoLibraryRoot without checking the requesting controller.
  • AndroidAutoMediaLibrary.kt:2511-2521, onAndroidAutoPlaybackResumption can resolve and start the active or resumable audiobook without checking the controller.

Please apply the same authorization gate to both paths, then add regression coverage for rejected controllers, including root browsing and playback resumption.

@whattheschnell
whattheschnell force-pushed the feat/android-auto-support branch from 3338d7e to 926e337 Compare October 27, 2026 19:13
…cle controls and implement security controls with regression test
@whattheschnell
whattheschnell force-pushed the feat/android-auto-support branch from 926e337 to c5eda06 Compare October 27, 2026 19:14
@whattheschnell

Copy link
Copy Markdown
Contributor Author

I've added the isAuthorizedBrowseOrPlaybackController gates to both onGetLibraryRoot and onAndroidAutoPlaybackResumption.

I also rebased and resolved the couple merge conflicts along the way. I needed to merge the new scaleProgressByPlaybackSpeed setting alongside the recent showOnDeckWhenShelfSyncDisabled addition in main. But i also needed to preserve the Android Auto layout (customLayout) in AudiobookMediaSessionCallback while using the updated localized string resources that were made for the UI.

@van-geaux

Copy link
Copy Markdown
Owner

Hi @whattheschnell, thanks for the substantial Android Auto work. I reviewed the PR and found two blocking localization issues that need to be addressed before merge:

  1. app/src/main/res/values/strings.xml:96 and :98 contain unescaped ampersands:
Downloads & Offline
Network & Sync

These should use &. This currently causes the localization validation to fail with an XML parse error.

  1. app/src/main/res/values-zh-rCN/strings.xml is missing the 12 new settings_category_* strings added in values/strings.xml:88-99. The localization validator reports these as missing once the XML parse error is fixed.

The failing CI run is here:

https://github.com/van-geaux/lagrange-reader/actions/runs/40620409918

One additional point to clarify: the PR description says Android Auto uses -10s and +30s controls, but the implementation changes the global audiobook rewind behavior from 10 seconds to 30 seconds in ReadiumAudioPlayback.kt. This also affects the regular audiobook player and notifications. Please confirm whether that broader behavior change is intentional.

Once the localization issues are fixed and the rewind behavior is clarified, I can re-review the PR.

@whattheschnell

Copy link
Copy Markdown
Contributor Author

The localization issues should be fixed now.

Changing the global constants in ReadiumAudioPlayback.kt was somewhat unintentional. I've reverted all that back to the 10-second baseline so standard playback and notifications aren't affected.

For Android Auto specifically, I scoped a 30-second rewind inside LagrangeLibrarySessionPlayer (AndroidAutoMediaLibrary.kt) I'd like to make the case that 30s is probably more useful in audiobook playback (compared to music anyway) but especially when in-vehicle. There's usually a longer reaction window before tapping the steering wheel button, so -30s/+30s skip feels much more practical on the road than having to tap a 10s rewind multiple times.

I think the best long term option is to make the forward/backward skip increments user-configurable in Playback settings. I'm willing to tackle that in a future PR if you're open to it.

@whattheschnell
whattheschnell force-pushed the feat/android-auto-support branch from 6867d95 to 07aa880 Compare October 28, 2026 14:15
@van-geaux

Copy link
Copy Markdown
Owner

Thanks @whattheschnell for the substantial Android Auto implementation. I found no blocking issues, and the PR is ready to merge. The Android Auto browse, playback, controller authorization, speed controls, and audiobook timeline changes are in good shape. Thank you for working through the review feedback and CI fixes.

@van-geaux
van-geaux merged commit c07c1c7 into van-geaux:main Oct 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Research and implement Android Auto support for the audiobook player

2 participants