Android 发件人应用中的 CastCompanion

CastCompanion in Android Sender App

我正在关注这个 tutorial. And this is my project

这是我的主class:

package com.mac.isaac.castcompanionapp;

import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;

import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.RemoteMediaPlayer;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;

import java.io.IOException;

public class CastActivity extends AppCompatActivity {

    private Button mButton;
    private MediaRouter mMediaRouter;
    private MediaRouteSelector mMediaRouteSelector;
    private MediaRouter.Callback mMediaRouterCallback;
    private CastDevice mSelectedDevice;
    private GoogleApiClient mApiClient;
    private RemoteMediaPlayer mRemoteMediaPlayer;
    private Cast.Listener mCastClientListener;
    private boolean mWaitingForReconnect = false;
    private boolean mApplicationStarted = false;
    private boolean mVideoIsLoaded;
    private boolean mIsPlaying;

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

        Log.i("MYTAG", "onCreate()");

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mVideoIsLoaded)
                    startVideo();
                else
                    controlVideo();
            }
        });

        initMediaRouter();
    }

    private void initMediaRouter() {
        Log.i("MYTAG", "initMediaRouter()");
        // Configure Cast device discovery
        mMediaRouter = MediaRouter.getInstance(getApplicationContext());
        mMediaRouteSelector = new MediaRouteSelector.Builder()
                .addControlCategory(
                        CastMediaControlIntent.categoryForCast(getString(R.string.app_id)))
                .build();
        mMediaRouterCallback = new MediaRouterCallback();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i("MYTAG", "onResume()");
        // Start media router discovery
        mMediaRouter.addCallback( mMediaRouteSelector,
                mMediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN );
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu ) {
        Log.i("MYTAG", "onCreateOptionsMenu()");
        super.onCreateOptionsMenu( menu );
        getMenuInflater().inflate( R.menu.menu, menu );
        MenuItem mediaRouteMenuItem = menu.findItem( R.id.media_route_menu_item );
        MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider( mediaRouteMenuItem );
        mediaRouteActionProvider.setRouteSelector( mMediaRouteSelector );
        return true;
    }

    private void startVideo() {
        Log.i("MYTAG", "startVideo()");
        MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );
        mediaMetadata.putString( MediaMetadata.KEY_TITLE, getString( R.string.video_title ) );

        MediaInfo mediaInfo = new MediaInfo.Builder( getString( R.string.video_url ) )
                .setContentType( getString( R.string.content_type_mp4 ) )
                .setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )
                .setMetadata( mediaMetadata )
                .build();
        try {
            mRemoteMediaPlayer
                    .load( mApiClient, mediaInfo, true )
                    .setResultCallback( new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                        @Override
                        public void onResult( RemoteMediaPlayer.MediaChannelResult mediaChannelResult ) {
                            if( mediaChannelResult.getStatus().isSuccess() ) {
                                mVideoIsLoaded = true;
                                mButton.setText( getString( R.string.pause_video ) );
                            }
                        }
                    } );
        } catch( Exception e ) {
            Log.e("MYTAG", "Error starting video "+e.getMessage());
            e.printStackTrace();
        }
    }

    private void controlVideo() {
        Log.i("MYTAG", "controlVideo()");
        if( mRemoteMediaPlayer == null || !mVideoIsLoaded )
            return;
        if( mIsPlaying ) {
            mRemoteMediaPlayer.pause( mApiClient );
            mButton.setText( getString( R.string.resume_video ) );
        } else {
            mRemoteMediaPlayer.play( mApiClient );
            mButton.setText( getString( R.string.pause_video ) );
        }
    }

    private void reconnectChannels( Bundle hint ) {
        Log.i("MYTAG", "reconnectChannels()");
        if( ( hint != null ) && hint.getBoolean( Cast.EXTRA_APP_NO_LONGER_RUNNING ) ) {
            //Log.e( TAG, "App is no longer running" );
            teardown();
        } else {
            try {
                Cast.CastApi.setMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer );
            } catch( IOException e ) {
                Log.e( "MYTAG", "Exception while creating media channel ", e );
            } catch( NullPointerException e ) {
                Log.e( "MYTAG", "Something wasn't reinitialized for reconnectChannels" );
            }
        }
    }

    private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener {

        @Override
        public void onConnectionFailed( ConnectionResult connectionResult ) {
            Log.i("MYTAG", "onConnectionFailed()");
            teardown();
        }
    }

    @Override
    protected void onPause() {
        Log.i("MYTAG", "onPause()");
        if ( isFinishing() ) {
            // End media router discovery
            mMediaRouter.removeCallback( mMediaRouterCallback );
        }
        super.onPause();
    }

    private void teardown() {
        Log.i("MYTAG", "teardown()");
        if( mApiClient != null ) {
            if( mApplicationStarted ) {
                try {
                    Cast.CastApi.stopApplication( mApiClient );
                    if( mRemoteMediaPlayer != null ) {
                        Cast.CastApi.removeMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace() );
                        mRemoteMediaPlayer = null;
                    }
                } catch( IOException e ) {
                    Log.e( "MYTAG", "Exception while removing application " + e );
                }
                mApplicationStarted = false;
            }
            if( mApiClient.isConnected() )
                mApiClient.disconnect();
            mApiClient = null;
        }
        mSelectedDevice = null;
        mVideoIsLoaded = false;
    }

    private void initCastClientListener() {
        mCastClientListener = new Cast.Listener() {
            @Override
            public void onApplicationStatusChanged() {
            }

            @Override
            public void onVolumeChanged() {
            }

            @Override
            public void onApplicationDisconnected( int statusCode ) {
                teardown();
            }
        };
    }

    private void launchReceiver() {
        Log.i("MYTAG", "launchReceiver()");
        Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
                .builder( mSelectedDevice, mCastClientListener );

        ConnectionCallbacks mConnectionCallbacks = new ConnectionCallbacks();
        ConnectionFailedListener mConnectionFailedListener = new ConnectionFailedListener();
        mApiClient = new GoogleApiClient.Builder(CastActivity.this)
                .addApi( Cast.API, apiOptionsBuilder.build() )
                .addConnectionCallbacks( mConnectionCallbacks )
                .addOnConnectionFailedListener( mConnectionFailedListener )
                .build();

        mApiClient.connect();
    }

    private void initRemoteMediaPlayer() {
        Log.i("MYTAG", "initRemoteMediaPlayer()");
        mRemoteMediaPlayer = new RemoteMediaPlayer();
        mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
            @Override
            public void onStatusUpdated() {
                MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
                mIsPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING;
            }
        });

        mRemoteMediaPlayer.setOnMetadataUpdatedListener( new RemoteMediaPlayer.OnMetadataUpdatedListener() {
            @Override
            public void onMetadataUpdated() {
                Log.i("MYTAG", "onMetadataUpdated()");
            }
        });
    }

    private class MediaRouterCallback extends MediaRouter.Callback {

        @Override
        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
            Log.i("MYTAG", "onRouteSelected()");
            initCastClientListener();
            initRemoteMediaPlayer();

            mSelectedDevice = CastDevice.getFromBundle( info.getExtras() );

            launchReceiver();
        }

        @Override
        public void onRouteUnselected( MediaRouter router, MediaRouter.RouteInfo info ) {
            Log.i("MYTAG", "onRouteUnselected()");
            teardown();
            mSelectedDevice = null;
            mButton.setText( getString( R.string.play_video ) );
            mVideoIsLoaded = false;
        }

    }

    private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks {

        @Override
        public void onConnected( Bundle hint ) {
            Log.i("MYTAG", "onConnected()");
            if( mWaitingForReconnect ) {
                mWaitingForReconnect = false;
                reconnectChannels( hint );
            } else {
                try {
                    Cast.CastApi.launchApplication( mApiClient, getString( R.string.app_id ), false )
                            .setResultCallback(
                                    new ResultCallback<Cast.ApplicationConnectionResult>() {
                                        @Override
                                        public void onResult(
                                                Cast.ApplicationConnectionResult applicationConnectionResult) {
                                            Status status = applicationConnectionResult.getStatus();
                                            if( status.isSuccess() ) {
                                                //Values that can be useful for storing/logic
                                                ApplicationMetadata applicationMetadata =
                                                        applicationConnectionResult.getApplicationMetadata();
                                                String sessionId =
                                                        applicationConnectionResult.getSessionId();
                                                String applicationStatus =
                                                        applicationConnectionResult.getApplicationStatus();
                                                boolean wasLaunched =
                                                        applicationConnectionResult.getWasLaunched();

                                                mApplicationStarted = true;
                                                reconnectChannels( null );
                                            }
                                        }
                                    }
                            );
                } catch ( Exception e ) {
                    Log.e("MYTAG", "error launching application");
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onConnectionSuspended(int i) {
            Log.i("MYTAG", "onConnectionSuspended()");
            mWaitingForReconnect = true;
        }
    }

}

我在这里遇到错误:

mRemoteMediaPlayer.load

它说:

04-23 21:29:31.730 4524-4524/com.mac.isaac.castcompanionapp E/MYTAG: Error starting video Attempt to invoke virtual method 'com.google.android.gms.common.api.PendingResult com.google.android.gms.cast.RemoteMediaPlayer.load(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.cast.MediaInfo, boolean)' on a null object reference
04-23 21:29:31.730 4524-4524/com.mac.isaac.castcompanionapp W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.common.api.PendingResult com.google.android.gms.cast.RemoteMediaPlayer.load(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.cast.MediaInfo, boolean)' on a null object reference
04-23 21:29:31.730 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at com.mac.isaac.castcompanionapp.CastActivity.startVideo(CastActivity.java:110)
04-23 21:29:31.730 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at com.mac.isaac.castcompanionapp.CastActivity.access0(CastActivity.java:30)
04-23 21:29:31.730 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at com.mac.isaac.castcompanionapp.CastActivity.onClick(CastActivity.java:57)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.view.View.performClick(View.java:5204)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.view.View$PerformClick.run(View.java:21153)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.os.Handler.handleCallback(Handler.java:739)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.os.Looper.loop(Looper.java:148)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5417)
04-23 21:29:31.731 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
04-23 21:29:31.732 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
04-23 21:29:31.732 4524-4524/com.mac.isaac.castcompanionapp W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

正在调试我遇到了这个错误:

public static final int APPLICATION_NOT_RUNNING Status code indicating that a requested application is not currently running. Constant Value: 2005

所以我猜问题出在 chromecast 设备上,而不是我的应用程序。但是我的应用程序看起来仍然像播放歌曲而不是视频,仍然没有任何反应。

我可以在我的工具栏上看到投射按钮,我可以 select 一个设备,然后 chromecast 似乎在黑屏 3 秒后重新启动,但它没有显示我的视频. 我认为问题出在我的应用程序中,因为我无法正确发送信息。 我已经在 Cast SDK 控制台中设置了所有内容,我尝试了所有 3 个选项:

而且我仍然找不到一个可用的教程(即使 Google 的 GitHub 上的那个也不起作用,我认为它没有更新)。 有没有人用过这个并有工作样本?

已解决,我不得不选择 StyledMediaReceiver,发布应用程序并重新启动 Chromecast 设备。每次您在 Google Cast SDK 控制台中进行更改时,chromecast 设备都必须重新启动才能获取它(似乎它只在启动时获取信息)。 我希望这可以帮助其他有同样问题的人。