Search code examples
androidgoogle-apichromecastgoogle-api-java-client

Issue while connecting to Google API client


I'm building a chromecast application. I'm able to launch the receiver with static text. However, I'm unable to interact with it(play media) and am getting

10-27 10:01:20.192 2292-6161/? E/MDM: [217] b.run: Couldn't connect to Google API client: ConnectionResult{statusCode=API_UNAVAILABLE, resolution=null, message=null}

in which I think could be the problem why I'm getting a timeout(Status code 15) when launching the application. I also notice that the Receiver screen stops casting after a while and my sender device doesn't detect this and still thinks it's an ongoing session. Does anyone see the problem?

See the code below for reference.

public class MediaRouterButtonActivity extends FragmentActivity {

private static final String TAG = MediaRouterButtonActivity.class
        .getSimpleName();

private MediaRouter mMediaRouter;
private MediaRouteSelector mMediaRouteSelector;
private MediaRouter.Callback mMediaRouterCallback;
private MediaRouteButton mMediaRouteButton;
private int mRouteCount = 0;

private MediaMetadata mMediaMetadata;
private CastDevice mSelectedDevice;
private GoogleApiClient mApiClient;
private Cast.Listener mCastListener;
private ConnectionCallbacks mConnectionCallbacks;
private RemoteMediaPlayer mRemoteMediaPlayer;
private boolean mWaitingForReconnect;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.media_router_button);

    mMediaRouter = MediaRouter.getInstance(getApplicationContext());
    mMediaRouteSelector = new MediaRouteSelector.Builder()
            .addControlCategory(
                    CastMediaControlIntent.categoryForCast(getResources().getString(R.string.app_id))).build();

    // Set the MediaRouteButton selector for device discovery.
    mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
    mMediaRouteButton.setRouteSelector(mMediaRouteSelector);

    mMediaRouterCallback = new MediaRouterCallback();

    mCastListener = new CastListener();
    mConnectionCallbacks = new ConnectionCallbacks();
    mConnectionFailedListener = new ConnectionFailedListener();
    mWaitingForReconnect = false;

    mRemoteMediaPlayer = new RemoteMediaPlayer();
}

@Override
protected void onStart() {
    super.onStart();
    mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
            MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}

@Override
protected void onResume() {
    super.onResume();

    // Add the callback to start device discovery
    mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
            MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}

@Override
protected void onPause() {
    mMediaRouter.removeCallback(mMediaRouterCallback);
    super.onPause();
}

@Override
protected void onStop() {
    setSelectedDevice(null);
    mMediaRouter.removeCallback(mMediaRouterCallback);
    super.onStop();
}

private class MediaRouterCallback extends MediaRouter.Callback {
    @Override
    public void onRouteAdded(MediaRouter router, RouteInfo route) {
        Log.d(TAG, "onRouteAdded");
        if (++mRouteCount >= 1) {
            // Show the button when a device is discovered.
            mMediaRouteButton.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void onRouteRemoved(MediaRouter router, RouteInfo route) {
        Log.d(TAG, "onRouteRemoved");
        if (--mRouteCount == 0) {
            // Hide the button if there are no devices discovered.
            mMediaRouteButton.setVisibility(View.GONE);
        }
    }

    @Override
    public void onRouteSelected(MediaRouter router, RouteInfo info) {
        Log.d(TAG, "onRouteSelected");
        // Handle route selection
        mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
        setSelectedDevice(mSelectedDevice);
    }

    @Override
    public void onRouteUnselected(MediaRouter router, RouteInfo info) {
        Log.d(TAG, "onRouteUnselected: info=" + info);
        setSelectedDevice(null);
    }
}

private void setSelectedDevice(CastDevice device) {
    Log.d(TAG, "setSelectedDevice: " + device);
    mSelectedDevice = device;

    if (mSelectedDevice != null) {
        try {
            disconnectApiClient();
            connectApiClient();
        } catch (IllegalStateException e) {
            Log.w(TAG, "Exception while connecting API client", e);
            disconnectApiClient();
        }
    } else {
        if (mApiClient != null) {
            disconnectApiClient();
        }
        mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
    }
}

private void connectApiClient() {
    Cast.CastOptions apiOptions = Cast.CastOptions.builder(mSelectedDevice, mCastListener)
            .build();
    mApiClient = new GoogleApiClient.Builder(getApplicationContext())
            .addApi(Cast.API, apiOptions)
            .addConnectionCallbacks(mConnectionCallbacks)
            .addOnConnectionFailedListener(mConnectionFailedListener)
            .build();
    Log.d(TAG, "connectApiClient");
    mApiClient.connect();
}

private void disconnectApiClient() {
    if (mApiClient != null) {
        if (mApiClient.isConnected() || mApiClient.isConnecting()) {
            mApiClient.disconnect();
            Log.d(TAG, "disconnectApiClient");
        }
        mApiClient = null;
    }
}

private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks {
    @Override
    public void onConnectionSuspended(int cause) {
        mWaitingForReconnect = true;
        Log.d(TAG, "ConnectionCallbacks.onConnectionSuspended");
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Log.d(TAG, "ConnectionCallbacks.onConnected");
        if (mWaitingForReconnect) {
            mWaitingForReconnect = false;
        } else {
            Cast.CastApi.launchApplication(mApiClient, getResources().getString(R.string.app_id))
                    .setResultCallback(new ConnectionResultCallback());
        }
    }
}


private final class ConnectionResultCallback implements
        ResultCallback<ApplicationConnectionResult> {
    @Override
    public void onResult(ApplicationConnectionResult result) {
        Status status = result.getStatus();
        Log.d(TAG, "onResultOnConnected" + status.isSuccess());
        if (status.isSuccess()) {
            try {
                Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
                        mRemoteMediaPlayer.getNamespace(),
                        mRemoteMediaPlayer);
                JSONObject customData = new JSONObject();
                String accessToken = "asdkfalksdjfkaljsdfla";
                try {
                    customData.put("X-At", accessToken);
                } catch (JSONException e) {
                    Log.e(TAG, "Empty Access Token", e);
                }
                Log.d(TAG, "mediaMetaDataDebug");
                mMediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
                mMediaMetadata.putString(MediaMetadata.KEY_TITLE, "Demo Video");
                MediaInfo mediaInfo = new MediaInfo.Builder(
                        "https://abcdef.com")       //link Instead
                        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                        .setCustomData(customData)
                        .build();
                try {
                    mRemoteMediaPlayer.load(mApiClient, mediaInfo, true)
                            .setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                                @Override
                                public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
                                    Status status = mediaChannelResult.getStatus();
                                    if (status.isSuccess()) {
                                        //allow users to pause video somehow;
                                    }
                                }
                            });

                } catch (Exception e) {
                    Log.e(TAG, "Problem while loading media", e);
                }
                Log.d(TAG, "wtf");
            } catch (IOException e) {
                Log.e(TAG, "Exception while creating channel", e);
            }

        } else {
            Log.d(TAG, "ConnectionResultCallback. Unable to launch the application. statusCode: "
                    + status.getStatusCode());
        }
    }
}


//google play services crap, fix later
private class CastListener extends Cast.Listener {
    @Override
    public void onApplicationDisconnected(int statusCode) {
        Log.d(TAG, "onApplicationDisconnected");
        setSelectedDevice(null);
        try {
            Cast.CastApi.removeMessageReceivedCallbacks(mApiClient,
                    mRemoteMediaPlayer.getNamespace());
        } catch (IOException e) {
            Log.w(TAG, "Exception while launching application", e);
        }
    }
}

}

This is the relevant log I'm getting

10-27 10:01:20.192 2292-6161/? E/MDM: [217] b.run: Couldn't connect to Google API client: ConnectionResult{statusCode=API_UNAVAILABLE, resolution=null, message=null}
10-27 10:59:54.962 2292-19493/? D/DeviceConnectionService: client connected with version: 8115000
10-27 11:01:12.922 27024-27024/? D/MediaRouterButtonActivity: ConnectionCallbacks.onConnected
10-27 11:01:13.002 2292-6727/? D/DeviceConnectionService: client connected with version: 8115000
10-27 11:01:33.952 27024-27024/? D/MediaRouterButtonActivity: ConnectionResultCallback. Unable to launch the application. statusCode: 15

Solution

  • The following changes made it work in my environment:

    1. Changed the app id to something that I had access to; you can use the default app id, or 4F8B3483 that the CastVideos app uses or create your own styled receiver.
    2. MediaInfo needs to have a content type, so added, say, the following to where you define your MediaInfo: '.setContentType("video/mp4")'
    3. Updated the url for the content to something that really exists and is accessible (an mp4 media on a publicly available server)

    Then I was able to launch the app and see the media playing by clicking on "Media Route Button"