让Google Cast v3在不受支持的设备上运行

时间:2017-08-20 09:42:40

标签: android chromecast

Cast V3框架具有一些功能,可以在没有Google Play Services所需的设备的情况下在设备上运行,但在测试时我遇到了一些问题。

  1. 在Kindle上,Google API返回SERVICE_INVALID,其中isUserResolvable()为true。
  2. 在升级后返回ConnectionResult.SUCCESS的onActivityResult的设备上,CastContext.getSharedInstance()可以抛出RuntimeError
  3. 作为2)的副作用,包含MiniControllerFragment的项目的XML膨胀将失败。
  4. 我发现的一些错误是

    java.lang.RuntimeException: Unable to start activity ComponentInfo{##########.MainActivity}: android.view.InflateException: Binary XML file line #42: Error inflating class fragment
    
    Caused by: java.lang.RuntimeException: 
      com.google.android.gms.dynamite.DynamiteModule$zzc: Remote load failed. No local fallback found.
        at com.google.android.gms.internal.zzauj.zzan(Unknown Source)
        at com.google.android.gms.internal.zzauj.zza(Unknown Source)
        at com.google.android.gms.cast.framework.CastContext.<init>(Unknown Source)
        at com.google.android.gms.cast.framework.CastContext.getSharedInstance(Unknown Source)
        at com.google.android.gms.cast.framework.media.uicontroller.UIMediaController.<init>(Unknown Source)
        at com.google.android.gms.cast.framework.media.widget.MiniControllerFragment.onCreateView(Unknown Source)
    

    这是由未安装CastController代码的设备上的MiniControllerFragment的膨胀引起的。这类似于SO : Cast v3 is crashing on devices below 5.0提出的问题。 KamilŚlesiński提供的答案有助于我的调查。

    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=123, result=0, data=null} to activity #####
    

    当我实现我的ViewStub时,我仍然在预发布测试机器中崩溃,因为他们返回SUCCESS,但没有CastContext可用。为了解决这个问题,我需要另一个测试来检查CastContext是否可以创建。

1 个答案:

答案 0 :(得分:1)

你需要在应用程序中使用单例/代码,如下所示......

boolean gCastable = false;
boolean gCastTested = false;
public boolean isCastAvailable(Activity act, int resultCode ){
    if( gCastTested == true ){
        return gCastable;
    }

    GoogleApiAvailability castApi = GoogleApiAvailability.getInstance();
    int castResult = castApi.isGooglePlayServicesAvailable(act);
    switch( castResult ) {
        case ConnectionResult.SUCCESS:
            gCastable = true;
            gCastTested = true;
            return true;
     /*  This code is needed, so that the user doesn't get a 
      *
      *  your device is incompatible "OK" 
      *
      * message, it isn't really "user actionable"
      */
        case ConnectionResult.SERVICE_INVALID: // Result from Amazon kindle - perhaps check if kindle first??
            gCastable = false;
            gCastTested = true;
            return false;
      ////////////////////////////////////////////////////////////////
        default:
            if (castApi.isUserResolvableError(castResult)) {
                castApi.getErrorDialog(act, castResult, resultCode, new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        gCastable = false;
                        gCastTested = false;
                        return;
                    }
                }).show();
            } else {
                gCastTested = true;
                gCastable = false;
                return false;
            }
    }
    return gCastable;
}

public void setCastOK(Activity mainActivity, boolean result ) {
    gCastTested = true;
    gCastable = result;
}

和一个辅助函数来检查我们是否知道演员表的状态。

public boolean isCastAvailableKnown() {
    return gCastable;
}

然而,为了应对返回SUCCESS的设备,我还需要App / singleton中的以下代码。

当活动收到演员表时,我们会创建一个CastContext。 &#34;希望&#34;是,如果应用程序可以创建CastContext,那么框架将以相同的方式成功(崩溃的原因)。

public boolean onCastResultReceived( Activity act, int result ) {
    boolean wasOk = false;
    if( result == ConnectionResult.SUCCESS ){
        try {
            CastContext ctx = CastContext.getSharedInstance(act );
            wasOk = true;
        } catch ( RuntimeException e ){
            wasOk = false;
        }
    }
    if( wasOk ) {
        setCastOK(act, true);
        return true;
    }else {
        setCastOK(act, false );
        return false;
    }
}

使用ViewStub和片段......

禁用迷你控制器的充气

Fragment mini_controller_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<fragment
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cast_mini_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    app:castShowImageThumbnail="true"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

使用类似的东西......

    <ViewStub
        android:id="@+id/cast_mini_controller"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout="@layout/mini_controller_fragment"
        />

活动

活动与cast组件的互动看起来像这样......

/* called when we have found out that cast is compatible. */
private void onCastAvailable() {
    ViewStub miniControllerStub = (ViewStub) findViewById(R.id.cast_mini_controller);
    miniControllerStub.inflate();    // only inflated if Cast is compatible.
    mCastStateListener = new CastStateListener() {
        @Override
        public void onCastStateChanged(int newState) {
            if (newState != CastState.NO_DEVICES_AVAILABLE) {
                showIntroductoryOverlay();
            }
            if (mQueueMenuItem != null) {
                mQueueMenuItem.setVisible(
                        (mCastSession != null) && mCastSession.isConnected());
            }
        }
    };
    mCastContext = CastContext.getSharedInstance(this);
    if (mCastSession == null) {
        mCastSession = mCastContext.getSessionManager()
                .getCurrentCastSession();
    }
    if (mQueueMenuItem != null) {
        mQueueMenuItem.setVisible(
                (mCastSession != null) && mCastSession.isConnected());
    }
}


private void showIntroductoryOverlay() {
    if (mOverlay != null) {
        mOverlay.remove();
    }
    if ((mediaRouteMenuItem != null) && mediaRouteMenuItem.isVisible()) {
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                mOverlay = new IntroductoryOverlay.Builder(
                        MainActivity.this, mediaRouteMenuItem)
                        .setTitleText(getString(R.string.introducing_cast))
                        .setOverlayColor(R.color.primary)
                        .setSingleTime()
                        .setOnOverlayDismissedListener(
                                new IntroductoryOverlay.OnOverlayDismissedListener() {
                                    @Override
                                    public void onOverlayDismissed() {
                                        mOverlay = null;
                                    }
                                })
                        .build();
                mOverlay.show();
            }
        });
    }

}

onCreate修改如下......

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    mApp = (MyApplication)getApplication();
    if( mApp.isCastAvailable( (Activity)this, GPS_RESULT )) {
        onCastAvailable();
    }

    ...
}

onActivityResult需要应对Google Play服务升级的结果...

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == GPS_RESULT ) {
        if(mApp.onCastResultReceived( this, resultCode ) ){
            onCastAvailable();
        }

onResume

protected void onResume() {
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.addCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().addSessionManagerListener(
                mSessionManagerListener, CastSession.class);
        if (mCastSession == null) {
            mCastSession = CastContext.getSharedInstance(this).getSessionManager()
                    .getCurrentCastSession();
        }
        if (mQueueMenuItem != null) {
            mQueueMenuItem.setVisible(
                    (mCastSession != null) && mCastSession.isConnected());
        }
    }
    super.onResume();
}

的onPause

protected void onPause() {
    super.onPause();
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.removeCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().removeSessionManagerListener(
                mSessionManagerListener, CastSession.class);
    }
}

班级中的会话管理器监听器......

private final SessionManagerListener<CastSession> mSessionManagerListener =
        new MySessionManagerListener();
private class MySessionManagerListener implements SessionManagerListener<CastSession> {

    @Override
    public void onSessionEnded(CastSession session, int error) {
        if (session == mCastSession) {
            mCastSession = null;
        }
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionResumed(CastSession session, boolean wasSuspended) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarted(CastSession session, String sessionId) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarting(CastSession session) {
    }

    @Override
    public void onSessionStartFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionEnding(CastSession session) {
    }

    @Override
    public void onSessionResuming(CastSession session, String sessionId) {
    }

    @Override
    public void onSessionResumeFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionSuspended(CastSession session, int reason) {
    }
}

UI互动

最后,我可以通过调用&#34;已知&#34;功能在我的应用程序中...

    int visibility = View.GONE;
    if( mApplication.isCastAvailableKnown( ) ) {
        CastSession castSession = CastContext.getSharedInstance(mApplication).getSessionManager()
                .getCurrentCastSession();
        if( castSession != null && castSession.isConnected() ){
            visibility = View.VISIBLE;
        }
    }
    viewHolder.mMenu.setVisibility( visibility);