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)
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.
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/advplyr/audiobookshelf-app/pull/1747
Author: @hypnotoad08
Created: 12/9/2025
Status: 🔄 Open
Base:
master← Head:feature/Media3-Conversion📝 Commits (10+)
a04e424feat(media3): scaffold PlayerWrapper, ExoPlayerWrapper and migrate service to wrapper (migration seam)3ce0ab9chore(media3): encapsulate ExoPlayer internals and migrate MediaSessionCallback to playerWrappera65e573fix(data): restore ExoPlayer imports for PlaybackSession media helpersba6b107fix(player): convert PlayerMediaItem -> Exo MediaItem for cast and media source creation46cc503chore(player): centralize DTO->native conversions in wrappers; CastPlayer accepts DTOsbdf30b1chore(player): wire factory to construct Media3Wrapper when feature flag enableda05dfb6Media3: wrapper-managed wiring + distinct label; trim debug logs826c1baMedia3 (short-term): basic rollout metrics + lower-risk resource tweaks; doc plan aligned with Patreon6fe6f3eFix: encapsulate playback metrics via service helpers; update listener to use helpers (no private field access)b29df25Refactor: 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:
Implementation Approach
I've implemented this as two build flavors rather than replacing ExoPlayer2 outright:
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):
Media3 (media3):
The Capacitor plugin interface is completely unchanged, so no web or iOS changes are required.
What's New with Media3
Enhanced Android Auto:
Improved Features:
Code Quality Improvements:
Media3 API Stability
Media3 APIs are currently marked
@UnstableApias 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):
Android Auto Support (new files):
Shared Infrastructure (new files):
Refactored Components:
getMediaItem()methods for Media3 browsingDependencies Added
All Media3 libraries are version 1.9.0 (Apache 2.0):
Build Configuration
Migration Notes
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:
Local Playback:
Android Auto:
Casting:
Sleep Timer:
Progress Syncing:
Build Verification:
Notification Behavior:
Screenshots
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.