[TV] Make the app a first-class media-session citizen - #5740
Conversation
|
Claude finished @sztomek's task in 5m 41s —— View job Code review: TV media-session wiringRead the diff against
Overall: the approach is right — reusing the shared services through the runtime toggle instead of adding TV-specific plumbing, and the manifest block is a faithful copy of 1.
|
|
Tested this branch (4bbda40) on real hardware — a Philips 4K A1 (Android 11),installDebugProd . Results for the items listed as not-yet-verified: Startup and component state ✅ Media session ✅ Background continuation ✅ Remote media-key control ✅ Screen-off — behaves differently to a phone, and I don't think it's a defect Not tested: media resumption after reboot, Assistant voice control, and the release/legacy path — this was a debug build, so per the review comment MEDIA3_SESSION resolved to its isDebugOrPrototypeBuild default and only the media3 service was exercised. Happy to run a prototype build against LegacyPlaybackService if that's useful. One incidental note: the app installed under user User 10 on this device rather than the owner profile. Didn't seem to affect anything, just flagging it. |
|
While testing this branch I noticed there's still no way to control playback without leaving whatever you're browsing. My closed #5737 had a mini-player drawer on the remote's Options/MENU key — transport controls slide in over the current screen, Menu or Back dismisses. I've since rebased it onto current main and cut it down to reuse TvSeekBar and the existing tv_nothing_playing_* strings rather than the hand-rolled controls it originally had. It's one new file plus a small hook in TvScaffold. Is that something you'd want? Happy to open it as its own PR, or drop it if you have designs for this already. |
# Conflicts: # tv/src/main/java/au/com/shiftyjelly/pocketcasts/TvApplication.kt
|
Sorry for the long wait, i was AFK last week.
I believe this piece would require input from our design team, but I'll ask them to investigate how a miniplayer would work best - if they approve the idea and draft a deisng for it, a follow-up PR will be quite likely. |
|
Claude finished @sztomek's task in 5m 25s —— View job Code review: TV media-session wiring (round 2)Re-reviewed after
Round-1 blocker is resolved. 1.
|
| } | ||
|
|
||
| private fun setupFeatureFlags() { | ||
| val providers = if (BuildConfig.DEBUG || BuildConfig.IS_PROTOTYPE) { |
There was a problem hiding this comment.
Two notes on this block — the ordering fix itself is right (flags before ensureCorrectServiceEnabled, matching wear), neither of these is blocking.
1. BuildConfig.IS_PROTOTYPE is always false for :tv. tv/build.gradle.kts:42-46 disables the variant outright:
androidComponents {
beforeVariants { builder ->
builder.enable = builder.buildType != "prototype"
}
}so the || BuildConfig.IS_PROTOTYPE arm is dead on TV — harmless as copy-paste parity with app/automotive, but it means TV has exactly two flag paths, not three:
debug/debugProd→PreferencesFeatureProvider(what the emulator + @nolengreenspan's device run exercised),release→FirebaseRemoteFeatureProvider+DefaultReleaseFeatureProvider.
Practical consequence: the earlier suggestion to "verify a prototype build" isn't available here. A release smoke test is the only way to exercise the legacy path, since Feature.MEDIA3_SESSION.defaultValue is false in release and FirebaseConfig.defaults seeds media3_session from that same default — so a fresh release install deterministically enables LegacyPlaybackService until a Firebase fetch flips it, and the flip only takes effect on the next launch (the fetch in FirebaseRemoteFeatureProvider.init is async and completes well after onCreate). LegacyPlaybackService.onCreate's mediaSession == null → stopSelf() guard covers the in-between launch. All of that is identical to app, so nothing is introduced here — just worth knowing that release TV ships MediaSessionCompat.
2. Third verbatim copy of this function. Identical to AppLifecycleObserver.kt:106-117 and AutomotiveApplication.kt:157-168. Since TV deliberately doesn't use AppLifecycleObserver, a small shared helper in :modules:services:utils (an injectable FeatureFlagInitializer, or FeatureFlag.initializeDefaults(...)) would stop the next surface from having to re-derive the provider list and its ordering constraint. Fine to defer, but it's now a pattern rather than a one-off.
Description
The Android TV app already plays audio (through the in-process ExoPlayer) and ships a full Now Playing screen, but it was not a first-class media-session citizen: the TV manifest never declared
PlaybackService/LegacyPlaybackService, soMediaSessionManager.startServiceIfNeeded()could never resolve a media-browser service (resolveMediaBrowserServiceComponent()returned null and logged "No enabled media browser service found in manifest"). The consequence was no systemMediaSession, no foreground service, and therefore:MediaBrowsercontent tree for the system/Assistant.This PR wires TV up the same way the
wearmodule does — reusing the shared services via the runtime toggle rather than adding anything TV-specific:tv/src/main/AndroidManifest.xml): declaresPlaybackService(media3) andLegacyPlaybackService, bothenabled="false"withforegroundServiceType="mediaPlayback"and the media-browser / media3 intent-filters, plus theWAKE_LOCK,POST_NOTIFICATIONS,FOREGROUND_SERVICEandFOREGROUND_SERVICE_MEDIA_PLAYBACKpermissions they need.TvApplication.onCreate(): sets up the notification channels (the foreground service posts on the "Playback" channel — without itstartForegroundthrows on Android 8+) and callsPlaybackServiceToggle.ensureCorrectServiceEnabled(), which enables the correct service based on theMEDIA3_SESSIONfeature flag, before the existingplaybackManager.setup().Deliberately out of scope
MediaButtonReceiver— only forwards external broadcastACTION_MEDIA_BUTTONevents (Tasker/Automate); TV remote keys reach the session directly.wearomits it too, so we match wear.FOREGROUND_SERVICE_DATA_SYNC—wear/appdeclare it for WorkManager foreground workers. It's unrelated to media sessions and a pre-existing gap (TV had no foreground-service permissions at all before), so it's left for a separate change.Fixes PCDROID-727 https://linear.app/a8c/issue/PCDROID-727/mediasession-support
Testing Instructions
./gradlew :tv:installDebug).enabledComponents: …PlaybackService,disabledComponents: …LegacyPlaybackService.Smoke-tested on a Google TV emulator (
sdk_google_atv64_arm64, leanback)Startup wiring only — the behavioral outcomes below still need verifying on a real device:
FATAL EXCEPTIONfrom the newonCreate()calls.enabledComponents: PlaybackService,disabledComponents: LegacyPlaybackService—PlaybackServiceToggleran and enabled the media3 service.Not yet verified (steps 4–5 above): actual background / screen-off continuation, remote media-key / Assistant control, and media resumption on a real TV device.
Screenshots or Screencast
No UI change — the Now Playing screen already exists; this PR only wires up the media-session/foreground-service infrastructure. The verification artifact is the runtime component-enabled state above.
Checklist
./gradlew spotlessApplyto automatically apply formatting/linting)modules/services/localization/src/main/res/values/strings.xmlI have tested any UI changes...