Wire Media3 session into MediaSessionManager and PlaybackService#5148
Wire Media3 session into MediaSessionManager and PlaybackService#5148
Conversation
Generated by 🚫 Danger |
There was a problem hiding this comment.
Pull request overview
Wires a Media3 MediaLibrarySession into the playback stack behind the MEDIA3_SESSION feature flag, shifting service/session responsibilities into MediaSessionManager and simplifying the playback service implementation.
Changes:
- Introduces
SeedStatePlayer(with tests) to provide an immediate “paused at last position” placeholder timeline for AAOS/controllers on cold start. - Replaces the legacy
MediaBrowserServiceCompat-basedPlaybackServicewith aMediaLibraryServicethat delegates session lifecycle + notification behavior toMediaSessionManager. - Extends
MediaSessionManager/PlaybackManagerto support Media3 session creation, player installation/swapping (local/cast), cast state forwarding, and custom command button layouts; updates legacy service code paths for nullable legacy session.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/playback/SeedStatePlayerTest.kt | Adds Robolectric coverage for the new placeholder player’s seeded/unseeded behavior. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/SeedStatePlayer.kt | Implements a SimpleBasePlayer placeholder that becomes READY with a single item after seeding. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackService.kt | Refactors service to MediaLibraryService, sets Media3 notification provider, forwards media-button intents, and delegates session lifecycle to MediaSessionManager. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackManager.kt | Installs the lazily-created ExoPlayer into the Media3 session post-play(), wires cast install + cast-state updates, and starts the service earlier for Media3 readiness. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt | Adds full Media3 session lifecycle, forwarding player wiring, cast state mirroring, and a Media3-focused observation/custom-layout pipeline (keeps legacy path too). |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/LegacyPlaybackService.kt | Adds a guard for null legacy session and makes legacy calls null-safe under flag mismatch. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/extensions/MediaMetadataCompatExt.kt | Deletes legacy MediaMetadataCompat extensions previously derived from Google sample code. |
| automotive/src/main/java/au/com/shiftyjelly/pocketcasts/AutoPlaybackService.kt | Simplifies automotive playback service subclass and removes legacy browse override (now handled by session callbacks). |
You can also share your feedback on Copilot code review. Take the survey.
1777fc2 to
c2754bf
Compare
aba1e9a to
e1fc3e9
Compare
|
Version |
c2754bf to
830ac23
Compare
e1fc3e9 to
e250248
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in (feature-flagged) Media3 MediaLibrarySession path by moving session lifecycle ownership into MediaSessionManager and rewriting PlaybackService as a MediaLibraryService, while keeping the legacy MediaSessionCompat path available when the flag is off.
Changes:
- Add a Media3 session lifecycle in
MediaSessionManager(create/install/swap/release) and wire it intoPlaybackServicebehindMEDIA3_SESSION. - Install the lazily-created
ExoPlayerinto the Media3 session afterSimplePlayer.play(), and propagate cast state into the Media3 session. - Introduce
SeedStatePlayer(+ tests) to provide an immediate “paused at last position” timeline for AAOS on cold start; update legacy service nullability guard and removeMediaMetadataCompatExt.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/playback/SeedStatePlayerTest.kt | Adds Robolectric unit coverage for placeholder player seeding and command availability. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/SeedStatePlayer.kt | Adds a SimpleBasePlayer placeholder that exposes a seeded READY timeline for Media3 controllers. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackService.kt | Replaces legacy MediaBrowserServiceCompat implementation with a Media3 MediaLibraryService delegating to MediaSessionManager. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackManager.kt | Installs local/cast players into the Media3 session and starts the service early for lazy ExoPlayer creation. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt | Adds Media3 session creation, player swapping, metadata/layout updates, and legacy session nullability handling behind a feature flag. |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/LegacyPlaybackService.kt | Guards against a null legacy session when the Media3 flag is enabled (stale component starts). |
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/extensions/MediaMetadataCompatExt.kt | Removes the MediaMetadataCompat extensions (no longer needed with the new Media3 wiring). |
| automotive/src/main/java/au/com/shiftyjelly/pocketcasts/AutoPlaybackService.kt | Simplifies Automotive service now that browse is handled via Media3 session callbacks. |
830ac23 to
c99a9d6
Compare
e250248 to
bf2eed9
Compare
c99a9d6 to
c227601
Compare
bf2eed9 to
225fa59
Compare
c227601 to
938eaf4
Compare
289dd2a to
3abc684
Compare
Project manifest changes for appThe following changes in the --- ./build/reports/diff_manifest/app/release/base_manifest.txt 2026-04-08 10:24:14.252021980 +0000
+++ ./build/reports/diff_manifest/app/release/head_manifest.txt 2026-04-08 10:24:16.892041848 +0000
@@ -717,9 +717,25 @@
android:name="androidx.mediarouter.media.MediaTransferReceiver"
android:exported="true" >
</receiver>
-
+ <!--
+ Both services start disabled; PlaybackServiceToggle.ensureCorrectServiceEnabled()
+ enables the correct one in Application.onCreate() based on the MEDIA3_SESSION feature flag.
+ This avoids the system resolving the wrong service before the toggle runs.
+ -->
<service
android:name="au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackService"
+ android:enabled="false"
+ android:exported="true"
+ android:foregroundServiceType="mediaPlayback"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ <action android:name="androidx.media3.session.MediaLibraryService" />
+ </intent-filter>
+ </service>
+ <service
+ android:name="au.com.shiftyjelly.pocketcasts.repositories.playback.LegacyPlaybackService"
+ android:enabled="false"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
android:label="@string/app_name" >
@@ -738,20 +754,7 @@
android:pathPrefix="/pocket_casts_wear_communication"
android:scheme="wear" />
</intent-filter>
- </service>
- <!--
- A receiver that will receive media buttons and send as
- intents to your MediaBrowserServiceCompat implementation.
- Required on pre-Lollipop. More information at
- http://developer.android.com/reference/android/support/v4/media/session/MediaButtonReceiver.html
- -->
- <receiver
- android:name="androidx.media.session.MediaButtonReceiver"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.MEDIA_BUTTON" />
- </intent-filter>
- </receiver> <!-- Google play services -->
+ </service> <!-- Google play services -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" /> <!-- Chromecast -->
@@ -784,10 +787,10 @@
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup" />
<meta-data
- android:name="okhttp3.internal.platform.PlatformInitializer"
+ android:name="androidx.profileinstaller.ProfileInstallerInitializer"
android:value="androidx.startup" />
<meta-data
- android:name="androidx.profileinstaller.ProfileInstallerInitializer"
+ android:name="okhttp3.internal.platform.PlatformInitializer"
android:value="androidx.startup" />
</provider> <!-- Disable Advertising ID collection -->
<meta-data
@@ -1270,10 +1273,6 @@
android:directBootAware="true"
android:exported="false" />
- <uses-library
- android:name="android.ext.adservices"
- android:required="false" />
-
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
android:directBootAware="false"
@@ -1294,6 +1293,10 @@
</intent-filter>
</receiver>
+ <uses-library
+ android:name="android.ext.adservices"
+ android:required="false" />
+
<service
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
android:exported="false" >Go to https://buildkite.com/automattic/pocket-casts-android/builds/16269/canvas?sid=019d6c98-0685-44d5-b758-e843f51faade, click on the |
| </intent-filter> | ||
| </service> | ||
|
|
||
| <service |
There was a problem hiding this comment.
The exported service permission is addressed in the manifest declarations in PR #5151 (automotive-wear integration).
938eaf4 to
52001a3
Compare
d76ec99 to
3168499
Compare
97409db to
2182d09
Compare
3168499 to
3c0eeb4
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nged - Route deferred CastStatePlayer through installCastPlayerInternal() to avoid double transport callbacks and castStatePlayer being cleared - Use Int.MAX_VALUE for notifyChildrenChanged itemCount so controllers re-fetch updated content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ice) The remaining test (testPlaybackServiceEntersAndExitsForeground) references notificationManager, notificationHelper, testPlaybackStateChange, and isForegroundService — all removed from PlaybackService during the session wiring refactoring. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract sendError call into @OptIn-annotated function so lint recognizes the UnstableApi opt-in for the SessionError usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3c0eeb4 to
9717b57
Compare
Description
Wires the Media3 MediaLibrarySession into the app behind the MEDIA3_SESSION feature flag (off by default). MediaSessionManager gains the full Media3 session lifecycle — createSession(), installPlayer(), installCastPlayer(), updateCastState(), release() — plus an RxJava observation pipeline that pushes playback metadata and artwork to the forwarding player, and a CommandButton custom layout for notification/AAOS controls. PlaybackService is rewritten from a 450-line MediaBrowserServiceCompat to a ~115-line MediaLibraryService that delegates entirely to MediaSessionManager. PlaybackManager is wired to install the ExoPlayer into the Media3 session after SimplePlayer.play() creates it, and to forward cast state changes. A new SeedStatePlayer provides a "paused at last position" placeholder so AAOS shows a Now Playing screen immediately on cold start before real playback begins. AutoPlaybackService is simplified to drop its onLoadChildren override since browse is now handled by the session callbacks. LegacyPlaybackService is updated for the now-nullable mediaSession, and MediaMetadataCompatExt is deleted. Fixes #
Testing Instructions
Just review the code please
Checklist
./gradlew spotlessApplyto automatically apply formatting/linting)modules/services/localization/src/main/res/values/strings.xml