Skip to content

Wire Media3 session into MediaSessionManager and PlaybackService#5148

Merged
sztomek merged 7 commits intomainfrom
media3/06b-session-wiring
Apr 8, 2026
Merged

Wire Media3 session into MediaSessionManager and PlaybackService#5148
sztomek merged 7 commits intomainfrom
media3/06b-session-wiring

Conversation

@sztomek
Copy link
Copy Markdown
Contributor

@sztomek sztomek commented Mar 19, 2026

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

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics.

@sztomek sztomek added this to the 8.10 milestone Mar 19, 2026
@sztomek sztomek requested a review from a team as a code owner March 19, 2026 14:22
@sztomek sztomek requested review from MiSikora and removed request for a team March 19, 2026 14:22
Copilot AI review requested due to automatic review settings March 19, 2026 14:22
@sztomek sztomek added [Type] Enhancement Improve an existing feature. [Area] Playback Episode playback issue labels Mar 19, 2026
@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Mar 19, 2026

1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-based PlaybackService with a MediaLibraryService that delegates session lifecycle + notification behavior to MediaSessionManager.
  • Extends MediaSessionManager/PlaybackManager to 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.

@sztomek sztomek modified the milestones: 8.10, 8.9 Mar 20, 2026
@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from 1777fc2 to c2754bf Compare March 27, 2026 16:09
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from aba1e9a to e1fc3e9 Compare March 27, 2026 16:14
@wpmobilebot wpmobilebot modified the milestones: 8.9, 8.10 Mar 30, 2026
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Version 8.9 has now entered code-freeze, so the milestone of this PR has been updated to 8.10.

@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from c2754bf to 830ac23 Compare April 1, 2026 17:58
Copilot AI review requested due to automatic review settings April 1, 2026 18:10
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from e1fc3e9 to e250248 Compare April 1, 2026 18:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into PlaybackService behind MEDIA3_SESSION.
  • Install the lazily-created ExoPlayer into the Media3 session after SimplePlayer.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 remove MediaMetadataCompatExt.

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.

@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from 830ac23 to c99a9d6 Compare April 1, 2026 20:31
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from e250248 to bf2eed9 Compare April 1, 2026 20:34
@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from c99a9d6 to c227601 Compare April 2, 2026 10:56
Copilot AI review requested due to automatic review settings April 2, 2026 10:57
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from bf2eed9 to 225fa59 Compare April 2, 2026 10:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from c227601 to 938eaf4 Compare April 2, 2026 11:29
Copilot AI review requested due to automatic review settings April 2, 2026 11:30
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from 289dd2a to 3abc684 Compare April 2, 2026 11:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

@wpmobilebot
Copy link
Copy Markdown
Collaborator

wpmobilebot commented Apr 8, 2026

Project manifest changes for app

The following changes in the app's merged AndroidManifest.xml file were detected (build variant: release):

--- ./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 Artifacts tab and audit the files.

</intent-filter>
</service>

<service
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exported service permission is addressed in the manifest declarations in PR #5151 (automotive-wear integration).

@sztomek sztomek force-pushed the media3/06a-shared-refactoring branch from 938eaf4 to 52001a3 Compare April 8, 2026 08:38
Copilot AI review requested due to automatic review settings April 8, 2026 08:48
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from d76ec99 to 3168499 Compare April 8, 2026 08:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Base automatically changed from media3/06a-shared-refactoring to media3/05-legacy-service April 8, 2026 09:14
@sztomek sztomek force-pushed the media3/05-legacy-service branch from 97409db to 2182d09 Compare April 8, 2026 09:31
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from 3168499 to 3c0eeb4 Compare April 8, 2026 09:35
Base automatically changed from media3/05-legacy-service to main April 8, 2026 10:08
sztomek and others added 7 commits April 8, 2026 12:14
- Filter high-frequency position updates in observeForMedia3Updates()
- Add @OptIn(UnstableApi::class) to MediaSessionManager.actions property
- Add @OptIn(UnstableApi::class) to PlaybackService.onStartCommand and onTaskRemoved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
@sztomek sztomek force-pushed the media3/06b-session-wiring branch from 3c0eeb4 to 9717b57 Compare April 8, 2026 10:16
@sztomek sztomek merged commit d753497 into main Apr 8, 2026
18 checks passed
@sztomek sztomek deleted the media3/06b-session-wiring branch April 8, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Area] Playback Episode playback issue [Type] Enhancement Improve an existing feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants