media icon indicating copy to clipboard operation
media copied to clipboard

CastContext never restores previous CastState after app returns to foreground

Open WSteverink opened this issue 2 months ago • 2 comments

Version

Media3 main branch

More version details

Summary

CastContext stops updating its CastState after the app returns from the background. When the app is backgrounded, the state changes from DEVICE_AVAILABLE to NO_DEVICES_AVAILABLE as expected, but on resume the state never returns to DEVICE_AVAILABLE and no further state callbacks are triggered. This leaves the app stuck in an incorrect cast-state until restart.

We are updating our UI based on this CastState, which is now partially broken.

Reproduce

To reproduce this, use the latest Media3 main branch and run the demo-session variant.

Within the MainActivity, add:

    override fun onResume() {
        super.onResume()
        val snapshot = CastContext.getSharedInstance(this).castState
        Log.d("[DEBUG]", "onResume: snapshot $snapshot")
    }

And within the MainActivity.onCreate add:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        CastContext.getSharedInstance(this).addCastStateListener { state ->
            Log.d("[DEBUG]", "addCastStateListener: $state <-----")
        }

Now run the app and make sure that casting devices are available. Steps:

  1. Observe that the listener prints CastState 2 (DEVICES_AVAILABLE).
  2. Background the app.
  3. Observe that the listener prints CastState 1 (NO_DEVICES_AVAILABLE) — as expected.
  4. Return the app to the foreground.
  5. Observe that the listener does not print a new state, and the snapshot printed in onResume is still CastState 1 (NO_DEVICES_AVAILABLE).

I might be missing something, but it seems that this listener is not working correctly. Any help is highly appreciated. Keep up the good work, and thanks in advance.

Devices that reproduce the issue

Samsung a51 Android 13 Pixel 7 Android 16

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Bug Report

  • [ ] You will email the zip file produced by adb bugreport to [email protected] after filing this issue.

WSteverink avatar Dec 03 '25 12:12 WSteverink

Hi @WSteverink, thanks for reporting. Cast devices are not guaranteed to be available all the time, they are only expected to be discovered while the app is scanning, and scanning is guaranteed to be disabled when the app is in the background (for system resource saving reasons). What I'm trying to say is that this, as is, is not necessarily a bug.

One way of checking if there's actually a bug here is checking whether devices are not available as expected when you press the cast button. The rationale is that the cast button (or rather, the dialog that pops when the cast button is pressed) triggers a route scan, and therefor guarantees that cast devices will be discovered (or if they are not discovered, it's indicative of a bug).

So, I have the following questions for you:

  • Are you unable to see any routes in the device picker dialog that shows up when you press the cast button? If they show up, then there's no bug, we are just missing a scan request.
  • What are you trying to achieve by accessing the routes directly? There might be a better way of doing what you need to do. Or, at least we can trigger a scan in a way that doesn't eat up unnecessary resources because you are scanning pretty much all the time.

AquilesCanta avatar Dec 03 '25 16:12 AquilesCanta

  • Are you unable to see any routes in the device picker dialog that shows up when you press the cast button? If they show up, then there's no bug, we are just missing a scan request.

Nope, when i use the CastButton i do see my devices being discovered.

  • What are you trying to achieve by accessing the routes directly? There might be a better way of doing what you need to do. Or, at least we can trigger a scan in a way that doesn't eat up unnecessary resources because you are scanning pretty much all the time.

We have a custom design for the CastButton (at the client’s request). We use the CastState from the listener to create our own CastUiState, which we then use to update the UI, such as enabling or disabling buttons. And we use it to decide whether we want to send metadata changes to the player. This works seamlessly with ExoPlayer, but not with CastPlayer.

For now, we could check whether the CastPlayer is being used to determine if casting is active, and fall back to the default CastButton instead of the custom one, since the default button behaves correctly.

Sample code:

        val listener = CastStateListener { castState ->
                CastUiState(
                    isCasting = listOf(
                        CastState.CONNECTED,
                        CastState.CONNECTING
                    ).contains(castState),
                    hasDevicesAvailable = castState != CastState.NO_DEVICES_AVAILABLE,               
            )
        }

WSteverink avatar Dec 05 '25 14:12 WSteverink

This works seamlessly with ExoPlayer, but not with CastPlayer

It's unclear to me the relationship between ExoPlayer and the state of the Cast devices available on the network. Particularly, I'm not sure what you mean by seamlessly, since ExoPlayer doesn't know anything about Cast.

For now, we could check whether the CastPlayer is being used to determine if casting is active

If your goal is to know whether you are currently casting, you can use the Player.deviceInfo (and its corresponding listener), and check whether playback is remote or local. There's also the sessionAvailabilityListener in RemoteCastPlayer, but my advise is to stick to the DeviceInfo.

Sample code

Be mindful that the availability of CastDevices depends on the scanning state of the app, which is:

  • generally something you want to minimize in order to save resources.
  • generally non-reliable: For example, if your app goes into the background your scan requests will be ignored. But they'll be enabled again if the user opens the output switcher while your app is in the background.

So to sum up: if you need to know whether there are available cast devices in your network, you can trigger an active scan request (see CastContext#getMergedSelector and MediaRouter#addCallback), but don't use scan to determine the state or behavior of the cast button. Otherwise, you'll need to be scanning all the time, and then the system / GMS can choose to ignore your scan request as it deems you are scanning spuriously. It's ok to trigger a cast scan while there's a UI that lists Cast devices (i.e. once the user has tapped on the cast button and the media route chooser dialog has popped up).

AquilesCanta avatar Dec 15 '25 12:12 AquilesCanta