Skip to content

Cast: Start a session when a Cast route is selected#3567

Open
peterhel wants to merge 1 commit into
microg:masterfrom
peterhel:fix/cast-session-start
Open

Cast: Start a session when a Cast route is selected#3567
peterhel wants to merge 1 commit into
microg:masterfrom
peterhel:fix/cast-session-start

Conversation

@peterhel

Copy link
Copy Markdown

Cast: start a session when a Cast route is selected

Problem

Apps using the modern Cast SDK (play-services-cast-framework) — Prime Video, Netflix,
Disney+, YouTube, and any app built on CastContext/SessionManager — cannot start a
Cast session through microG. The Cast button discovers and lists Chromecasts (route
discovery works), but selecting a device does nothing: no session starts, no connection is
attempted, and the app's SessionManagerListener never fires. On the device this looks
like a silent failure (the symptom reported for Prime Video on /e/OS).

Root cause

CastContextImpl builds the merged MediaRouteSelector and holds the app's IMediaRouter
and its ISessionProviders, but it never registers a route-selection callback.
MediaRouterCallbackImpl — which already contains the correct "on selection, start the
session" logic — is imported by CastDynamiteModuleImpl but never instantiated or
registered with the router. As a result nothing observes route selection, so the session
lifecycle is never driven.

A second, latent bug sits in the provider lookup: defaultSessionProvider is fetched with
sessionProviders.get(categoryForCast(appId)), but the provider map is keyed by the full
control-category string, which carries extra namespace/flag suffixes
(e.g. …/CC1AD845///ALLOW_IPV6). The exact-key lookup misses, leaving
defaultSessionProvider == null, so even once selection is observed the session can't be
created (and MediaRouterCallbackImpl.onRouteSelected would NPE on it).

Fix

play-services-cast-framework only — two files, no API changes:

  1. CastContextImpl — register MediaRouterCallbackImpl with the app's
    IMediaRouter (registerMediaRouterCallbackImpl + addCallback) in the constructor, so
    route selection is observed and starts a session. This deliberately goes through the
    IMediaRouter binder rather than touching androidx MediaRouter directly: the dynamite
    runs with the app's Resources but microG's resource IDs, so a direct
    MediaRouter.getInstance(context) from the dynamite throws
    Resources$NotFoundException. The app's MediaRouterProxy already runs androidx
    MediaRouter correctly in-process.
  2. CastContextImpl — fall back to a category-prefix match when the exact
    categoryForCast(appId) key isn't present, so the default session provider resolves
    despite the flag suffixes.
  3. MediaRouterCallbackImpl.onRouteSelected — null-safety hardening (skip cleanly if
    there is no provider / the unwrapped object isn't a SessionImpl, and fall back to the
    route's own extras when getRouteInfoExtrasById returns null).

Verification

Built into a vtmDefault GmsCore APK and tested against Google's real
play-services-cast-framework (a minimal sender app) on:

  • an AOSP Android 14 emulator, and
  • a Pixel 2 on LineageOS 22.2 (Android 15).

Before: selecting the Cast route only logs unimplemented Method: onSelect; nothing else
happens.

After: selecting the route drives the full session lifecycle —

MediaRouterCallbackImpl.onRouteSelected → SessionImpl.start → SessionManager.onSessionStarting
CastSender: onSessionStarting   (app's SessionManagerListener fires)
castState: NO_DEVICES_AVAILABLE → CONNECTING

i.e. the session now starts and proceeds to the device-connection/authentication handshake.

Note: completing the connection to a live device additionally requires microG signature
spoofing to be active on the device (so the app's GMS authenticity check passes) — that is
an existing environmental requirement of microG, independent of this change. On a device
without working signature spoofing the session correctly stalls at CONNECTING with a
SERVICE_INVALID from the app's GoogleApiManager; this patch is what gets the session to
that point in the first place.


🤖 Generated with Claude Code

CastContextImpl never registered a route-selection callback, so selecting a
Chromecast via the Cast SDK (CastContext/SessionManager) did nothing: discovery
worked but no session was ever started and the app's SessionManagerListener never
fired. MediaRouterCallbackImpl already had the correct start-on-selection logic but
was never instantiated or registered with the router.

Register MediaRouterCallbackImpl via IMediaRouter (registerMediaRouterCallbackImpl +
addCallback) so route selection starts a session. This goes through the IMediaRouter
binder rather than touching androidx MediaRouter from the dynamite, which would throw
Resources$NotFoundException (the dynamite uses the app's Resources but microG's
resource IDs).

Also resolve defaultSessionProvider by category prefix: the provider map is keyed by
the full control category (with namespace/flag suffixes such as
.../CC1AD845///ALLOW_IPV6), so the exact categoryForCast(appId) lookup missed and
left defaultSessionProvider null. Harden MediaRouterCallbackImpl.onRouteSelected
against a missing provider / non-SessionImpl and fall back to the route extras.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant