Auras Auras - 4 months ago 26
Android Question

Android: Get current playing media on Chromecast?

I'm working on an app that controls the Chromecast and whatever's playing on it.

I don't want a sender app, I don't want to register anything to get an api key, none of that stuff.

I'm using so far MediaRouter to control the volume and to see if it's connected to anything.

But I want something like the Google Cast app:

Google Cast android

Which knows what's playing and (or at least) the playback state.

Ideally I wouldn't want to use google play services, but if it's the only way, c'est la vie.

Answer

I finally figured it out. I had to use the google play services and the google cast sdk v2 but without registering the application.

Libraries included in the project:

compile 'com.android.support:mediarouter-v7:24.0.0' 
compile 'com.google.android.gms:play-services-cast-framework:9.2.0' 

Beware that in the code below onCreate() and onDestroy() aren't methods in an Activity, Fragment or Service, so don't copy/paste the code and expect it works. The code in those methods must pe copy/pasted in your own methods.

Here are the steps of what's happening:

  1. You select a route either via the cast button either by calling getActiveMediaRoute() which checks for which Chromecast is active (it won't work if nobody is connected to the Chromecast). Override the method or getActiveChromecastRoute() to select based on your preferences
  2. When onRouteSelected() is called a new Cast GoogleApiClient is instanced with options for the selected chromecast
  3. When onApplicationMetadataChanged() is called the code will connect to the current application running on the Chromecast
  4. After the application is successfully connected a new RemoteMediaPlayer is instanced and the MediaStatus is requested
  5. You should get a callback in onStatusUpdated() and after that you can call mRemoteMediaPlayer.getMediaStatus() and it will contain the data about what's being played on the Chromecast.

public static final String CHROMECAST_SIGNATURE = "cast.media.CastMediaRouteProviderService";

private final MediaRouteSelector mSelector;
private final MediaRouter mMediaRouter;
private CastDevice mSelectedDevice;
private Cast.Listener mCastClientListener;
private RemoteMediaPlayer mRemoteMediaPlayer;

@Override
public void onCreate() {
    mMediaRouter = MediaRouter.getInstance(context);

    mSelector = new MediaRouteSelector.Builder()
            // These are the framework-supported intents
            .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
            .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
            .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            .build();

    mMediaRouter.addCallback(mSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
}

@Override
public void onDestroy() {
    mMediaRouter.removeCallback(mMediaRouterCallback);
}

@UiThread
private boolean isChromecastActive() {
    return getActiveChromecastRoute() != null;
}

@UiThread
private Boolean isChromecastPlaying() {
    if (mRemoteMediaPlayer == null || mRemoteMediaPlayer.getMediaStatus() == null) {
        return null;
    }

    // Here you can get the playback status and the metadata for what's playing
    // But only after the onStatusUpdated() method is called in the mRemoteMediaPlayer callback
    int state = mRemoteMediaPlayer.getMediaStatus().getPlayerState();
    return (state == MediaStatus.PLAYER_STATE_BUFFERING || state == MediaStatus.PLAYER_STATE_PLAYING);
}

@UiThread
private MediaRouter.RouteInfo getActiveChromecastRoute() {
    for (MediaRouter.RouteInfo route : mMediaRouter.getRoutes()) {
        if (isCastDevice(route)) {
            if (route.getConnectionState() == MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED) {
                return route;
            }
        }
    }

    return null;
}

private int getMediaRouteVolume(@NonNull MediaRouter.RouteInfo route) {
    return route.getVolume();
}

private void setMediaRouteVolume(@NonNull MediaRouter.RouteInfo route, int volume) {
    route.requestSetVolume(volume);
}

private int getMediaRouteMaxVolume(@NonNull MediaRouter.RouteInfo route) {
    return route.getVolumeMax();
}

@UiThread
private MediaRouter.RouteInfo getActiveMediaRoute() {
    if (isChromecastActive()) {
        MediaRouter.RouteInfo route = getActiveChromecastRoute();

        if (route != null) {
            if (!route.isSelected()) {
                mMediaRouter.selectRoute(route);
            }
        }
        else if (mSelectedDevice != null) {
            mSelectedDevice = null;
        }

        return route;
    }

    return null;
}

private boolean isCastDevice(MediaRouter.RouteInfo routeInfo) {
    return routeInfo.getId().contains(CHROMECAST_SIGNATURE);
}

private MediaRouter.Callback mMediaRouterCallback = new MediaRouter.Callback() {
    @Override
    public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
        if (isCastDevice(route)) {
            Log.i("MediaRouter", "Chromecast found: " + route);
        }
    }

    @Override
    public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
        if (isCastDevice(route)) {
            Log.i("MediaRouter", "Chromecast changed: " + route);
        }
    }

    @Override
    public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
        if (mSelectedDevice == null && isCastDevice(route)) {
            Log.i("MediaRouter", "Chromecast selected: " + route);

            mSelectedDevice = CastDevice.getFromBundle(route.getExtras());
            mCastClientListener = new Cast.Listener() {
                @Override
                public void onApplicationStatusChanged() {
                    Log.i("MediaRouter", "Cast.Listener.onApplicationStatusChanged()");
                }

                @Override
                public void onApplicationMetadataChanged(ApplicationMetadata applicationMetadata) {
                    Log.i("MediaRouter", "Cast.Listener.onApplicationMetadataChanged(" + applicationMetadata + ")");

                    if (applicationMetadata != null) {
                        LaunchOptions launchOptions = new LaunchOptions.Builder().setRelaunchIfRunning(false).build();
                        Cast.CastApi.launchApplication(mApiClient, applicationMetadata.getApplicationId(), launchOptions).setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() {
                            @Override
                            public void onResult(@NonNull Cast.ApplicationConnectionResult applicationConnectionResult) {
                                Log.i("MediaRouter", "Cast.CastApi.joinApplication.onResult() " + applicationConnectionResult.getSessionId());

                                mRemoteMediaPlayer = new RemoteMediaPlayer();
                                mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
                                    @Override
                                    public void onStatusUpdated() {
                                        MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
                                        Log.i("MediaRouter", "Remote media player status " + mediaStatus.getPlayerState());
                                        // TODO: you can call isChromecastPlaying() now
                                    }
                                });

                                try {
                                    Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
                                } catch(IOException e) {
                                    Log.e("MediaRouter", "Exception while creating media channel ", e );
                                } catch(NullPointerException e) {
                                    Log.e("MediaRouter", "Something wasn't reinitialized for reconnectChannels", e);
                                }


                                mRemoteMediaPlayer.requestStatus(mApiClient).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                                    @Override
                                    public void onResult(@NonNull RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
                                        Log.i("MediaRouter", "requestStatus() " + mediaChannelResult);
                                    }
                                });

                                try {
                                    Cast.CastApi.requestStatus(mApiClient);
                                } catch (IOException e) {
                                    Log.e("MediaRouter", "Couldn't request status", e);
                                }
                            }
                        });
                    }
                }

                @Override
                public void onApplicationDisconnected(int i) {
                    Log.i("MediaRouter", "Cast.Listener.onApplicationDisconnected(" + i + ")");
                }

                @Override
                public void onActiveInputStateChanged(int i) {
                    Log.i("MediaRouter", "Cast.Listener.onActiveInputStateChanged(" + i + ")");
                }

                @Override
                public void onStandbyStateChanged(int i) {
                    Log.i("MediaRouter", "Cast.Listener.onStandbyStateChanged(" + i + ")");
                }

                @Override
                public void onVolumeChanged() {
                    Log.i("MediaRouter", "Cast.Listener.onVolumeChanged()");
                }
            };

            Cast.CastOptions.Builder apiOptionsBuilder = new Cast.CastOptions.Builder(mSelectedDevice, mCastClientListener);

            mApiClient = new GoogleApiClient.Builder(getContext())
                    .addApi( Cast.API, apiOptionsBuilder.build() )
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(@Nullable Bundle bundle) {
                            Log.i("MediaRouter", "GoogleApiClient.onConnected()");
                            Log.i("MediaRouter", "Bundle " + bundle);
                        }

                        @Override
                        public void onConnectionSuspended(int i) {
                            Log.i("MediaRouter", "GoogleApiClient.onConnectionSuspended(" + i + ")");
                        }
                    })
                    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                        @Override
                        public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
                            Log.i("MediaRouter", "GoogleApiClient.onConnectionFailed()");
                        }
                    })
                    .build();

            mApiClient.connect();
        }
        else {
            mSelectedDevice = null;
            mRemoteMediaPlayer = null;
        }
    }

    @Override
    public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo route) {
        if (isCastDevice(route)) {
            if (mSelectedDevice != null && mSelectedDevice.isSameDevice(CastDevice.getFromBundle(route.getExtras()))) {
                mSelectedDevice = null;
            }
            Log.i("MediaRouter", "Chromecast lost: " + route);
        }
    }
};