3

I'm working on an Android app that supports sending music to a ChromeCast. We'd like users to be able to cast entire music playlists while the app runs in the background.

When my Nexus 7 is not connected to USB power and I turn the screen inactivity timeout to 15 seconds in the settings, the app will disconnect from the ChromeCast about 90 seconds after the device powers off its screen.

I've identified that I'm getting a MediaRouter.Callback call to onRouteUnselected, and since that's the callback I get when a user disconnects from a route, I'm handling it by tearing down the ApplicationSession.

When I plug back in and check the logcat, I see this message around the same time:

I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }

Can I do anything to avoid the route being unselected when the app is in the background, or is there something else I can do to get the behavior I want?

rooster
  • 61
  • 1
  • 6
  • check if mediarouter has `setWakeMode(ctx, PowerManager.PARTIAL_WAKE_LOCK);` method. i know that mediaPlayer uses this to keep the wifi on. you'll need this permission in manifest too: `` – cucko Sep 24 '13 at 19:19
  • Thanks for suggestion, but I couldn't find any such wake mode on the Router. – rooster Sep 26 '13 at 15:22
  • Has anyone had any luck with this? I'm also facing the same issue.... – dell116 Oct 14 '13 at 19:46

3 Answers3

3

I eventually got around this by refusing to disconnect the message streams and tear down the session when the route was disconnected under these conditions, and silently re-select the route when it became available again. The route gets deselected, but it does not affect my casting session.

To do this, I check to see if the route exists when it's unselected.

public void onRouteUnselected(final MediaRouter router, final RouteInfo route) {
    if (!onUiThread()) {
        new Handler(Looper.getMainLooper()).post((new Runnable() {
            @Override
            public void run() {
                onRouteUnselected(router, route);
            }
        }));

        return;
    }

    boolean isThisRouteAvailable = doesRouterContainRoute(router, route);

    mRouteToReconnectTo = null;

    if (isThisRouteAvailable) {
        // Perform code to close the message streams and tear down the session.
    } else {
        // The route was unselected because it's no longer available from the router,
        // so try to just keep playing until the message streams get disconnected.
        mRouteToReconnectTo = route;
        // Short-circuited a disconnect.
    }
}

Later, when the route comes back, we can immediately re-select it.

@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
    super.onRouteAdded(router, route);
    // if mRouteToReconnectTo is not null, check to see if this route
    // matches it, and reconnect if it does with router.selectRoute(route)
}

@Override
public void onRouteSelected(final MediaRouter router, final RouteInfo route) {
    if (!onUiThread()) {
        new Handler(Looper.getMainLooper()).post((new Runnable() {
            @Override
            public void run() {
                onRouteSelected(router, route);
            }
        }));

        return;
    }

    if (areRoutesEqual(mRouteToReconnectTo, route)) {
        // Short-circuited a reconnect.
        mRouteToReconnectTo = null;
        return;
    }

    mRouteToReconnectTo = null;

    // Standard post-selection stuff goes here
}

There's no good way to compare two RouteInfo's, so I ended up writing a helper function that compared their description strings.

rooster
  • 61
  • 1
  • 6
  • If you're using the support v7 RouteInfo then there's a method `getId()`(http://developer.android.com/reference/android/support/v7/media/MediaRouter.RouteInfo.html#getId()) which seems perfect. It even suggests using it in cases like this. – roarster Apr 06 '14 at 14:34
3

Rooster's answer is perfectly feasible and actually provides good insight as to how to re-connect to a route once it comes back online....

but....just to give further insight on what's going on....

You're getting...

I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }

because when the device goes to sleep and is NOT plugged into a power source, the WIFI hardware is going into a low-power profile mode (and possibly shutting down entirely). This results in packet loss and subsequently causes the MedaRouter to fire the onRouteUnselected callback.

To prevent the Wifi from turning off you could set a WakeLock on the Wifi in the following manner:

WifiLock wifiLock;
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "MyWifiLock");
wifiLock.acquire();

Using the flag WifiManager.WIFI_MODE_FULL_HIGH_PERF will keep the WIFI hardware alive and active when the device goes to sleep. Caution, this flag is only available to API 12 and above.

I tried using the WifiManager.WIFI_MODE_FULL flag when creating the WifiLock, but that didn't seem to do the trick.

Obviously anyone using any type of WifiLock or WakeLock should take considerable care in making sure locks released when no longer needed. Also, beware this will cause battery drain when the device screen is off.

dell116
  • 5,835
  • 9
  • 52
  • 70
  • Thanks for the follow-up! I had tried the WiFi lock, but missed the HIGH_PERF, and that must be why it didn't work for me. – rooster Oct 20 '13 at 02:16
  • Ditto, my friend...ditto! – dell116 Oct 20 '13 at 16:46
  • I tried doing _exactly_ this in my app, and the Chromecast session still disconnects shortly after the device's screen switches off. Unless on USB power, then it works fine. Frustrating! – aroth May 20 '17 at 09:36
  • @aroth - This is really really old. I'm pretty sure if you use the V3 cast SDK you won't have to worry about this anymore. – dell116 May 26 '17 at 14:41
  • @dell116 The problem turned out to be related to Doze mode. Full details here: https://stackoverflow.com/questions/44106286 – aroth May 27 '17 at 00:55
0

If you used the sample code (Android in this case), you're probably doing this...

mSession.setStopApplicationWhenEnding(true);
mSession.endSession();

...when the route is unselected. If you instead do this...

mSession.setStopApplicationWhenEnding(false);
mSession.endSession();

...then you can clean up the session, but the Chromecast will keep the application alive. When the route becomes available again (or possibly when the user picks the device again) you can build a new session. I have yet to explore how to determine if the new session is talking to a "brand new" instance of the application or to the application left running from another session, but I'll update this answer when I do.

FinnTheHuman
  • 135
  • 2
  • 9