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

Posted

技术标签:

【中文标题】让 Google Cast v3 在不受支持的设备上运行【英文标题】:Getting Google Cast v3 to work on unsupported devices 【发布时间】:2017-08-20 09:42:40 【问题描述】:

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

    在 Kindle 上,Google API 返回 SERVICE_INVALID,isUserResolvable() 为 true。 在升级后 onActivityResult 返回 ConnectionResult.SUCCESS 的设备上,CastContext.getSharedInstance() 可以抛出 RuntimeError。 作为 2) 的副作用,包含 MiniControllerFragment 的项目的 XML 膨胀将失败。

我发现的一些错误是

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)

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

java.lang.RuntimeException: Failure delivering result ResultInfowho=null, request=123, result=0, data=null to activity #####

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

【问题讨论】:

您是如何解决问题的?通过检查 GoogleServices 版本? onCastResultReceived 在 try 块中创建 CastContext。如果失败,则不会使 Cast 可用。我已尝试编辑答案以澄清这一点。 我没有使用你的解决方案。一般来说,我问的是您提到的问题的原因是什么? 我的活动在 OnActivityResult 中获得了成功代码,但嵌入式 GPS UI 在充气时崩溃。鉴于创建 CastContext 失败,在启用膨胀之前创建一个可以让我预测结果。 就我而言,它很难重现。我也得到了成功代码。很少有设备会出现此问题。 【参考方案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 中添加以下代码。

当 Activity 收到转换结果时,我们创建一个CastContext。 “希望”是,如果应用程序可以创建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 和片段禁用迷你控制器的充气...

片段 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_
    android:layout_
    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_
        android:layout_
        android:layout_alignParentBottom="true"
        android:layout="@layout/mini_controller_fragment"
        />

活动

Activity 与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();

暂停

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 转换可用...

    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);

【讨论】:

将viewstub 膨胀放入try catch 并观察它是否失败不是更容易吗? @AndreasRudolph,如果 Cast 可用,则不太清楚,并且如果某些 UI 元素没有正确处理捕获,则可能会破坏它们。如果我希望 ViewStub 能够正常工作,我觉得我可以更好地控制结果。 你说得对,我想这是一个适当的大解决方案与一个较小但没有针对性的解决方案的问题。一个人可以选择自己 - 谢谢你的帮助!

以上是关于让 Google Cast v3 在不受支持的设备上运行的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Google Cast v3 与 ExoPlayer v2 集成?

在 Google Cast Chrome API (v3) 中使用playbackDuration/startTime 进行部分播放

发送方和接收方的 google-cast-sdk 音频和字幕手动处理

Android:让 Google Cast MediaRouter 图标显示

偷渡者不受支持,微软警告:请勿在不受支持的 PC 上安装 Win11

Cast v3 中的 ActionBar Cast Button 颜色