[PR #1747] Introduce Media3 Playback Architecture for Android App Modernization #1681

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

📋 Pull Request Information

Original PR: https://github.com/advplyr/audiobookshelf-app/pull/1747
Author: @hypnotoad08
Created: 12/9/2025
Status: 🔄 Open

Base: masterHead: feature/Media3-Conversion


📝 Commits (10+)

  • a04e424 feat(media3): scaffold PlayerWrapper, ExoPlayerWrapper and migrate service to wrapper (migration seam)
  • 3ce0ab9 chore(media3): encapsulate ExoPlayer internals and migrate MediaSessionCallback to playerWrapper
  • a65e573 fix(data): restore ExoPlayer imports for PlaybackSession media helpers
  • ba6b107 fix(player): convert PlayerMediaItem -> Exo MediaItem for cast and media source creation
  • 46cc503 chore(player): centralize DTO->native conversions in wrappers; CastPlayer accepts DTOs
  • bdf30b1 chore(player): wire factory to construct Media3Wrapper when feature flag enabled
  • a05dfb6 Media3: wrapper-managed wiring + distinct label; trim debug logs
  • 826c1ba Media3 (short-term): basic rollout metrics + lower-risk resource tweaks; doc plan aligned with Patreon
  • 6fe6f3e Fix: encapsulate playback metrics via service helpers; update listener to use helpers (no private field access)
  • b29df25 Refactor: streamline player listener methods and update PlayerWrapperFactory documentation for clarity

📊 Changes

63 files changed (+8895 additions, -844 deletions)

View changed files

📝 .github/workflows/build-apk.yml (+13 -7)
📝 .github/workflows/deploy-apk.yml (+13 -7)
📝 android/app/build.gradle (+38 -2)
📝 android/app/src/main/AndroidManifest.xml (+24 -7)
android/app/src/main/java/com/audiobookshelf/app/CastConstants.kt (+8 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/CastOptionsProvider.kt (+1 -3)
📝 android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt (+36 -23)
📝 android/app/src/main/java/com/audiobookshelf/app/MediaPlayerWidget.kt (+68 -10)
📝 android/app/src/main/java/com/audiobookshelf/app/data/AudioTrack.kt (+20 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/data/CollapsedSeries.kt (+24 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt (+91 -9)
📝 android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt (+9 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/data/LibraryAuthorItem.kt (+51 -2)
📝 android/app/src/main/java/com/audiobookshelf/app/data/LibraryCollection.kt (+23 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/data/LibraryItem.kt (+138 -11)
📝 android/app/src/main/java/com/audiobookshelf/app/data/LibrarySeriesItem.kt (+51 -2)
📝 android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt (+75 -11)
📝 android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt (+10 -52)
📝 android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt (+19 -17)
📝 android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt (+51 -36)

...and 43 more files

📄 Description

Brief summary

This PR adds Media3 as a new audio playback backend for Android alongside the existing ExoPlayer2 implementation. Both backends are available as separate build flavors (exov2 and media3), allowing for gradual migration and side-by-side testing. Media3 is Google's actively maintained successor to ExoPlayer2 and provides better Android Auto integration, modern APIs, and ongoing support.

Which issue is fixed?

This addresses the need to migrate from ExoPlayer2, which is now in maintenance mode. Media3 is the supported path forward for Android media playback.

Pull Request Type

Platform: Android only
Component: Backend audio playback system
Type: Feature enhancement (fully backward compatible - ExoPlayer2 remains default)

In-depth Description

Why Media3?

ExoPlayer2 is now in maintenance mode with minimal future development. Media3 is Google's recommended replacement, offering:

  • Active maintenance and ongoing feature development
  • Better Android Auto integration (browse tree, search, improved navigation)
  • Modern casting APIs
  • Built-in telemetry for performance monitoring
  • Cleaner architecture aligned with current Android media best practices

Implementation Approach

I've implemented this as two build flavors rather than replacing ExoPlayer2 outright:

  • exov2 (default) - ExoPlayer2 implementation with refactored shared components
  • media3 - New Media3 implementation with feature parity

Both flavors can be installed simultaneously with different application IDs, making it easy to compare behavior and performance.

Key Architectural Difference

The main difference between the two flavors is service management:

ExoPlayer2 (exov2):

  • MainActivity binds to PlayerNotificationService on startup
  • Traditional foreground service model
  • Plugin waits for service ready callback

Media3 (media3):

  • No service binding in MainActivity
  • MediaLibraryService is system-managed (per Media3 design)
  • Plugin initializes PlaybackController directly when needed

The Capacitor plugin interface is completely unchanged, so no web or iOS changes are required.

What's New with Media3

Enhanced Android Auto:

  • Full browse tree implementation for library navigation
  • Search support for finding content
  • Improved metadata display
  • Pull-based architecture (Media3 queries on demand) vs push-based (ExoPlayer2 pre-loads)

Improved Features:

  • Native Media3 cast components (cleaner casting implementation)
  • Rewritten progress syncing to handle Media3's async nature (UnifiedMediaProgressSyncer)
  • Centralized sleep timer with shake detection support
  • Built-in telemetry (PlaybackMetricsRecorder) for performance comparison

Code Quality Improvements:

  • Interface-based design for dual-stack support (PlaybackTelemetryHost, SleepTimerHost, etc.)
  • Cleaner separation of concerns with dedicated coordinators
  • Shared components work with both backends (sleep timer, widget, progress sync)

Media3 API Stability

Media3 APIs are currently marked @UnstableApi as they haven't reached 1.0 stable yet. I've added the required @OptIn(UnstableApi::class) annotations throughout and structured the code to handle future API changes. This is expected for Media3 at this stage.

Major Components Added

Core Playback (new files):

  • Media3PlaybackService - Entry point for Media3 playback
  • PlaybackController - Main controller coordinating Media3 components
  • PlaybackPipeline - State management and playback flow
  • SessionController - Media session lifecycle management
  • Media3SessionManager - Session configuration and setup

Android Auto Support (new files):

  • Media3BrowseTree - Browse tree structure
  • Media3BrowseItemBuilder - Constructs browsable media items
  • Media3BrowseDataLoader - Loads library data for browsing
  • Media3AutoLibraryCoordinator - Coordinates Auto integration

Shared Infrastructure (new files):

  • UnifiedMediaProgressSyncer - Rewritten progress sync to handle Media3's async APIs
  • SleepTimerCoordinator - Centralized sleep timer logic (used by both stacks)
  • PlaybackTelemetryHost - Interface enabling telemetry across both backends
  • NetworkMonitor - Network state monitoring
  • WidgetPlaybackSnapshot - Immutable widget state

Refactored Components:

  • Data classes now include getMediaItem() methods for Media3 browsing
  • Managers refactored with interfaces to support both backends
  • MainActivity conditionally binds services based on build flavor
  • Sleep timer extracted into shared coordinator
  • Progress syncing abstracted for dual-stack support

Dependencies Added

All Media3 libraries are version 1.9.0 (Apache 2.0):

  • androidx.media3:media3-exoplayer
  • androidx.media3:media3-session
  • androidx.media3:media3-ui
  • androidx.media3:media3-cast
  • androidx.media3:media3-exoplayer-hls

Build Configuration

# Build ExoPlayer2 (default)
./gradlew assembleExov2Debug

# Build Media3
./gradlew assembleMedia3Debug

# Install both simultaneously for comparison
./gradlew installExov2Debug installMedia3Debug

# List installed variants
adb shell pm list packages | grep audiobookshelf

Migration Notes

  • ExoPlayer2 remains the default flavor for stability
  • Android 13+ now requires POST_NOTIFICATIONS permission
  • Both flavors achieve full feature parity for audiobook playback

How have you tested this?

I've been using the Media3 flavor as my daily driver during development to validate real-world stability and catch edge cases.

Specific testing performed:

  1. Local Playback:

    • Verified playback start, pause, resume
    • Tested seeking (forward/backward)
    • Confirmed playback speed changes
    • Validated chapter navigation
  2. Android Auto:

    • Used Desktop Head Unit (DHU) to test browsing
    • Verified search functionality
    • Tested library navigation (continue listening, books, podcasts, series)
    • Confirmed playback controls from Auto interface
  3. Casting:

    • Tested cast device discovery
    • Verified playback transfer to cast device
    • Confirmed cast controls (play/pause/seek)
    • Validated resume on disconnect
  4. Sleep Timer:

    • Tested countdown timer
    • Verified shake to reset functionality
    • Confirmed end-of-chapter mode
    • Validated notification updates
  5. Progress Syncing:

    • Verified sync across multiple devices
    • Tested local progress updates
    • Confirmed server sync behavior
  6. Build Verification:

    • Both flavors compile cleanly
    • Both install and run simultaneously
    • Correct service binding per flavor
    • No crashes or ANRs during daily use
  7. Notification Behavior:

    • Tested on Android 13+ with notification permissions
    • Verified playback controls from notification
    • Confirmed notification updates during playback

Screenshots


🔄 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/1747 **Author:** [@hypnotoad08](https://github.com/hypnotoad08) **Created:** 12/9/2025 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feature/Media3-Conversion` --- ### 📝 Commits (10+) - [`a04e424`](https://github.com/advplyr/audiobookshelf-app/commit/a04e424f9d968882513778ad7cd8a027d1524b90) feat(media3): scaffold PlayerWrapper, ExoPlayerWrapper and migrate service to wrapper (migration seam) - [`3ce0ab9`](https://github.com/advplyr/audiobookshelf-app/commit/3ce0ab9493ff662a508f9e05f52fcce99eaa0f3d) chore(media3): encapsulate ExoPlayer internals and migrate MediaSessionCallback to playerWrapper - [`a65e573`](https://github.com/advplyr/audiobookshelf-app/commit/a65e573e7301f1489aeb75705699827813c5d4d1) fix(data): restore ExoPlayer imports for PlaybackSession media helpers - [`ba6b107`](https://github.com/advplyr/audiobookshelf-app/commit/ba6b1077ed703f60f858b455cd0486e54af98b8c) fix(player): convert PlayerMediaItem -> Exo MediaItem for cast and media source creation - [`46cc503`](https://github.com/advplyr/audiobookshelf-app/commit/46cc503b46f7877bbbf1d082916847176f715375) chore(player): centralize DTO->native conversions in wrappers; CastPlayer accepts DTOs - [`bdf30b1`](https://github.com/advplyr/audiobookshelf-app/commit/bdf30b1d9165938d0c31f31da1d915b6b9aab388) chore(player): wire factory to construct Media3Wrapper when feature flag enabled - [`a05dfb6`](https://github.com/advplyr/audiobookshelf-app/commit/a05dfb6a2b343389262eb529b6a4025382dc38fe) Media3: wrapper-managed wiring + distinct label; trim debug logs - [`826c1ba`](https://github.com/advplyr/audiobookshelf-app/commit/826c1babc5629879bdd87ae9e121175fbed9ba6f) Media3 (short-term): basic rollout metrics + lower-risk resource tweaks; doc plan aligned with Patreon - [`6fe6f3e`](https://github.com/advplyr/audiobookshelf-app/commit/6fe6f3e8adef1180bf20a6d221c16cefe3d7e601) Fix: encapsulate playback metrics via service helpers; update listener to use helpers (no private field access) - [`b29df25`](https://github.com/advplyr/audiobookshelf-app/commit/b29df2560332e8782c0f181fe2765258171cb503) Refactor: streamline player listener methods and update PlayerWrapperFactory documentation for clarity ### 📊 Changes **63 files changed** (+8895 additions, -844 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflows/build-apk.yml` (+13 -7) 📝 `.github/workflows/deploy-apk.yml` (+13 -7) 📝 `android/app/build.gradle` (+38 -2) 📝 `android/app/src/main/AndroidManifest.xml` (+24 -7) ➕ `android/app/src/main/java/com/audiobookshelf/app/CastConstants.kt` (+8 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/CastOptionsProvider.kt` (+1 -3) 📝 `android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt` (+36 -23) 📝 `android/app/src/main/java/com/audiobookshelf/app/MediaPlayerWidget.kt` (+68 -10) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/AudioTrack.kt` (+20 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/CollapsedSeries.kt` (+24 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt` (+91 -9) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt` (+9 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/LibraryAuthorItem.kt` (+51 -2) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/LibraryCollection.kt` (+23 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/LibraryItem.kt` (+138 -11) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/LibrarySeriesItem.kt` (+51 -2) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt` (+75 -11) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt` (+10 -52) 📝 `android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt` (+19 -17) 📝 `android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt` (+51 -36) _...and 43 more files_ </details> ### 📄 Description <!-- For Work In Progress Pull Requests, please use the Draft PR feature, see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details. If you do not follow this template, the PR may be closed without review. Please ensure all checks pass. If you are a new contributor, the workflows will need to be manually approved before they run. --> ## Brief summary This PR adds Media3 as a new audio playback backend for Android alongside the existing ExoPlayer2 implementation. Both backends are available as separate build flavors (exov2 and media3), allowing for gradual migration and side-by-side testing. Media3 is Google's actively maintained successor to ExoPlayer2 and provides better Android Auto integration, modern APIs, and ongoing support. ## Which issue is fixed? This addresses the need to migrate from ExoPlayer2, which is now in maintenance mode. Media3 is the supported path forward for Android media playback. ## Pull Request Type **Platform:** Android only **Component:** Backend audio playback system **Type:** Feature enhancement (fully backward compatible - ExoPlayer2 remains default) ## In-depth Description ### Why Media3? ExoPlayer2 is now in maintenance mode with minimal future development. Media3 is Google's recommended replacement, offering: - Active maintenance and ongoing feature development - Better Android Auto integration (browse tree, search, improved navigation) - Modern casting APIs - Built-in telemetry for performance monitoring - Cleaner architecture aligned with current Android media best practices ### Implementation Approach I've implemented this as **two build flavors** rather than replacing ExoPlayer2 outright: - **exov2** (default) - ExoPlayer2 implementation with refactored shared components - **media3** - New Media3 implementation with feature parity Both flavors can be installed simultaneously with different application IDs, making it easy to compare behavior and performance. ### Key Architectural Difference The main difference between the two flavors is service management: **ExoPlayer2 (exov2):** - MainActivity binds to PlayerNotificationService on startup - Traditional foreground service model - Plugin waits for service ready callback **Media3 (media3):** - No service binding in MainActivity - MediaLibraryService is system-managed (per Media3 design) - Plugin initializes PlaybackController directly when needed The Capacitor plugin interface is **completely unchanged**, so no web or iOS changes are required. ### What's New with Media3 **Enhanced Android Auto:** - Full browse tree implementation for library navigation - Search support for finding content - Improved metadata display - Pull-based architecture (Media3 queries on demand) vs push-based (ExoPlayer2 pre-loads) **Improved Features:** - Native Media3 cast components (cleaner casting implementation) - Rewritten progress syncing to handle Media3's async nature (UnifiedMediaProgressSyncer) - Centralized sleep timer with shake detection support - Built-in telemetry (PlaybackMetricsRecorder) for performance comparison **Code Quality Improvements:** - Interface-based design for dual-stack support (PlaybackTelemetryHost, SleepTimerHost, etc.) - Cleaner separation of concerns with dedicated coordinators - Shared components work with both backends (sleep timer, widget, progress sync) ### Media3 API Stability Media3 APIs are currently marked `@UnstableApi` as they haven't reached 1.0 stable yet. I've added the required `@OptIn(UnstableApi::class)` annotations throughout and structured the code to handle future API changes. This is expected for Media3 at this stage. ### Major Components Added **Core Playback (new files):** - Media3PlaybackService - Entry point for Media3 playback - PlaybackController - Main controller coordinating Media3 components - PlaybackPipeline - State management and playback flow - SessionController - Media session lifecycle management - Media3SessionManager - Session configuration and setup **Android Auto Support (new files):** - Media3BrowseTree - Browse tree structure - Media3BrowseItemBuilder - Constructs browsable media items - Media3BrowseDataLoader - Loads library data for browsing - Media3AutoLibraryCoordinator - Coordinates Auto integration **Shared Infrastructure (new files):** - UnifiedMediaProgressSyncer - Rewritten progress sync to handle Media3's async APIs - SleepTimerCoordinator - Centralized sleep timer logic (used by both stacks) - PlaybackTelemetryHost - Interface enabling telemetry across both backends - NetworkMonitor - Network state monitoring - WidgetPlaybackSnapshot - Immutable widget state **Refactored Components:** - Data classes now include `getMediaItem()` methods for Media3 browsing - Managers refactored with interfaces to support both backends - MainActivity conditionally binds services based on build flavor - Sleep timer extracted into shared coordinator - Progress syncing abstracted for dual-stack support ### Dependencies Added All Media3 libraries are version 1.9.0 (Apache 2.0): - androidx.media3:media3-exoplayer - androidx.media3:media3-session - androidx.media3:media3-ui - androidx.media3:media3-cast - androidx.media3:media3-exoplayer-hls ### Build Configuration ```bash # Build ExoPlayer2 (default) ./gradlew assembleExov2Debug # Build Media3 ./gradlew assembleMedia3Debug # Install both simultaneously for comparison ./gradlew installExov2Debug installMedia3Debug # List installed variants adb shell pm list packages | grep audiobookshelf ``` ### Migration Notes - ExoPlayer2 remains the **default flavor** for stability - Android 13+ now requires POST_NOTIFICATIONS permission - Both flavors achieve full feature parity for audiobook playback ## How have you tested this? I've been using the Media3 flavor as my daily driver during development to validate real-world stability and catch edge cases. **Specific testing performed:** 1. **Local Playback:** - Verified playback start, pause, resume - Tested seeking (forward/backward) - Confirmed playback speed changes - Validated chapter navigation 2. **Android Auto:** - Used Desktop Head Unit (DHU) to test browsing - Verified search functionality - Tested library navigation (continue listening, books, podcasts, series) - Confirmed playback controls from Auto interface 3. **Casting:** - Tested cast device discovery - Verified playback transfer to cast device - Confirmed cast controls (play/pause/seek) - Validated resume on disconnect 4. **Sleep Timer:** - Tested countdown timer - Verified shake to reset functionality - Confirmed end-of-chapter mode - Validated notification updates 5. **Progress Syncing:** - Verified sync across multiple devices - Tested local progress updates - Confirmed server sync behavior 6. **Build Verification:** - Both flavors compile cleanly - Both install and run simultaneously - Correct service binding per flavor - No crashes or ANRs during daily use 7. **Notification Behavior:** - Tested on Android 13+ with notification permissions - Verified playback controls from notification - Confirmed notification updates during playback ## Screenshots <!-- No UI changes - backend audio system only --> --- <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:34 +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#1681