Skip to content

Cast: fix device connection lifecycle and implement joinApplication#3351

Open
FloodExLLC wants to merge 4 commits into
microg:masterfrom
FloodExLLC:fix/cast-connect-and-route-controller
Open

Cast: fix device connection lifecycle and implement joinApplication#3351
FloodExLLC wants to merge 4 commits into
microg:masterfrom
FloodExLLC:fix/cast-connect-and-route-controller

Conversation

@FloodExLLC
Copy link
Copy Markdown

Fixes the two root causes that prevent Cast from working in practice.

Problem

The ChromeCast object (from the chromecast-java-api-v2 library) requires
an explicit connect() call to open a TLS socket before any operation can
succeed. The existing code created the ChromeCast instance but never called
connect(), so launchApp(), sendRawRequest(), stopSession() etc. all
threw IOException immediately, making Cast non-functional for all apps.

A second issue was that joinApplication() always launched a new instance
instead of joining an existing session, breaking multi-sender scenarios
and session resume.

Changes

CastDeviceControllerImpl

  • Add ensureConnected() helper that calls chromecast.connect() if not
    already connected; invoke it at the start of every outgoing operation
  • Fix launchApplication() to handle a null app response gracefully
    rather than NPE-ing on app.sessionId
  • Implement joinApplication() correctly: query device status, join
    the already-running matching session (wasLaunched = false), fall back
    to launching only when the app is not present

CastMediaRouteController

  • Implement onSelect(): open TLS connection when user selects route
  • Implement onUnselect() / onUnselect(int): close connection on deselect
  • Implement onRelease(): close connection when controller is destroyed

Closes #580

The ChromeCast object was created but connect() was never called
before launchApp(), sendRawRequest() etc., causing all outgoing
operations to fail with IOException because no socket existed.

- Add ensureConnected() helper; call it at the top of every
  outgoing operation (launchApplication, joinApplication,
  sendMessage, stopApplication)
- Fix launchApplication() to guard against a null Application
  response instead of NPE-ing on app.sessionId
- Implement joinApplication() properly: query device status,
  join an already-running matching session (wasLaunched=false),
  and only fall back to launching when the app is absent
onSelect() and onUnselect() were stubs. The Cast device connection
lifecycle must mirror the route selection lifecycle so the socket
is open before the Cast session begins and closed when the user
switches away.

- onSelect(): open TCP/TLS connection to the Cast device
- onUnselect() / onUnselect(int): close the connection
- onRelease(): close the connection when the controller is destroyed
@Excellencedev
Copy link
Copy Markdown

Does not even build which makes it clear you didn't test this

@Tthecreator
Copy link
Copy Markdown

Tthecreator commented Mar 22, 2026

Hey! Thank you for this PR. Seems on the surface you have done some great work.
I have seen the claim you put out on my bounty for this on BountyHub. But I also see it is not compiling yet, so please implement it properly.
I am a bit loaded at the moment, but I would like to properly test that it works first. For that I will make a test build and then I would like to see it working on my TV with Youtube native, Youtube Revanced, Youtube Music, Crunchyroll, General screencast and either one of Netflix/Hulu/Prime.

FloodExLLC added 2 commits March 22, 2026 09:04
chromecast.connect() throws both IOException and GeneralSecurityException
(a checked exception). The ensureConnected() helper only declared
throws IOException, causing a compile error.

Wrap the connect() call to catch GeneralSecurityException and rethrow
as IOException so all callers remain unchanged.

Fixes: microg#3351
….onSelect()

Same fix as CastDeviceControllerImpl: chromecast.connect() throws both
IOException and GeneralSecurityException. Update the catch clause to
handle both using a multi-catch.
@aquette
Copy link
Copy Markdown

aquette commented Mar 28, 2026

Does not even build which makes it clear you didn't test this

Why do you say so?
Builds are green, it just probably requires approvals to be mergeable.

@Excellencedev
Copy link
Copy Markdown

Does not even build which makes it clear you didn't test this

Why do you say so? Builds are green, it just probably requires approvals to be mergeable.

it was not building as at the time of that comment

@ocouch
Copy link
Copy Markdown

ocouch commented Mar 30, 2026

Just chiming in to say I have been hanging out for this and fingers crossed it all works and is pulled!

@aquette
Copy link
Copy Markdown

aquette commented Mar 30, 2026

Just chiming in to say I have been hanging out for this and fingers crossed it all works and is pulled!

IMO, we're quite a pack of fans awaiting that Christmas gift for years 💪😍👍

We all hope that it's not dust in the wind 🤞

@aquette
Copy link
Copy Markdown

aquette commented Apr 11, 2026

Any update on this @FloodExLLC ?
We're you able to test @Tthecreator ?

@aquette aquette mentioned this pull request Apr 12, 2026
@Tthecreator
Copy link
Copy Markdown

Hi, I will at last spend some time today setting up my test environment. Sorry for the long wait, I really have to make time for this.

@Tthecreator
Copy link
Copy Markdown

I just did a basic test:

Device: Oneplus5, running LineageOS 21-20240930 (Yes I know old version, because this is my old phone)
Youtube: 21.16.245
Chromecast: ccaj16lp0670t7

My test is simple: Have GMS installed with all self-checks passing (except for work-profile but that should be irrelevant). I am not logged into a Google account. Then I look up a Youtube Video using search. Then I use the cast button to cast to my chromecast. Then I see if the video starts playing.

Test #1: On my phone running real Google Play Services: Video starts playing.
Test #2: Using latest official MicroG build: I can see my casting device, but playback never starts.
Test #3: Using MicroG self-built from default branch (fresh clone): I can see my casting device, but playback never starts.
Test #4: Using MicroG with branch from the PR: I can see my casting device, but a background crash occurs and is caught by LogFox.

Details of the crash:

FATAL EXCEPTION: main
Process: com.google.android.gms, PID: 23517
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1688)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:389)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
at java.net.Socket.connect(Socket.java:646)
at com.android.org.conscrypt.AbstractConscryptSocket.connect(AbstractConscryptSocket.java:156)
at com.android.org.conscrypt.AbstractConscryptSocket.connect(AbstractConscryptSocket.java:140)
at su.litvak.chromecast.api.v2.Channel.connect(Channel.java:312)
at su.litvak.chromecast.api.v2.Channel.open(Channel.java:300)
at su.litvak.chromecast.api.v2.ChromeCast.connect(ChromeCast.java:163)
at org.microg.gms.cast.CastMediaRouteController.onSelect(CastMediaRouteController.java:69)
at androidx.mediarouter.media.MediaRouteProviderService$MediaRouteProviderServiceImplBase.onSelectRoute(MediaRouteProviderService.java:715)
at androidx.mediarouter.media.MediaRouteProviderService$ReceiveHandler.processMessage(MediaRouteProviderService.java:443)
at androidx.mediarouter.media.MediaRouteProviderService$ReceiveHandler.handleMessage(MediaRouteProviderService.java:364)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loopOnce(Looper.java:232)
at android.os.Looper.loop(Looper.java:317)
at android.app.ActivityThread.main(ActivityThread.java:8592)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)

@FloodExLLC Did you test this and run into this problem?

@emulatronicGIT
Copy link
Copy Markdown

emulatronicGIT commented May 13, 2026

I took a look at the crash reported in #3351 (comment).

The NetworkOnMainThreadException is caused by CastMediaRouteController.onSelect() calling ChromeCast.connect() directly from the route-provider handler/main path. I opened a small follow-up PR on top of this work that moves route-controller connect/disconnect/release work onto a single background executor, so selecting a route should no longer open the Cast socket on the main thread.

What changed in the follow-up:

  • onSelect() schedules chromecast.connect() on a single-thread executor.
  • onUnselect() / onRelease() schedule disconnect on the same executor.
  • late operations after release are ignored instead of crashing.

Local verification:

  • :play-services-cast-core:compileDebugJavaWithJavac passes on the rebased branch.
  • full assembleDebug passed earlier locally with JDK 17, Android SDK 35, and build-tools 34.0.0.

I cannot verify actual Chromecast playback from this machine. @Tthecreator, if you can test a build with this small patch, the first thing to check is whether the LogFox NetworkOnMainThreadException is gone and whether YouTube progresses past route selection.

Follow-up PR for testing: #3470

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.

What's the status on Google Cast implementation?

6 participants