将 CCL 的“发件人”应用迁移到 Cast 应用框架 (CAF)

通过以下步骤,您可以将 Android 发送器应用从 Cast SDK v2 与 CCL 转换为 CAF。CCL 的所有功能都已在 CAF 中实现,因此迁移后,您不再需要使用 CCL。

Cast CAF Sender SDK 使用 CastContext 代表您管理 GoogleAPIClient。CastContext 可为您管理生命周期、错误和回调,从而大大简化 Cast 应用的开发工作。

简介

  • 由于 CAF 发送者设计受到 Cast Companion 库的影响,因此从 CCL 到 CAF 发送者的迁移主要涉及类及其方法的一对一映射。
  • 而 CAF 发件人仍会使用 Android SDK 管理器作为 Google Play 服务的一部分进行分发。
  • 新添加到 CAF 发送者软件包 (com.google.android.gms.cast.framework.*) 并具有与 CCL 类似的功能,可负责遵守 Google Cast 设计核对清单
  • CAF Sender 提供符合 Cast 用户体验要求的 widget;这些 widget 与 CCL 提供的 widget 类似。
  • CAF Sender 提供类似于 CCL 的异步回调,用于跟踪状态和获取数据。与 CCL 不同,CAF Sender 不提供各种接口方法的任何空操作实现。

在以下部分中,我们将主要重点介绍基于 CCL 的 VideoCastManager 的视频中心应用,但在很多情况下,同样的概念也适用于 DataCastManager。

依赖项

CCL 和 CAF 依赖于 AppCompat 支持库、MediaRouter v7 支持库和 Google Play 服务。不过,区别在于 CAF 依赖于 Google Play 服务 9.2.0 或更高版本中提供的新 Cast 框架。

在 build.gradle 文件中,移除 com.google.android.gms:play-services-castcom.google.android.libraries.cast.companionlibrary:ccl 的依赖项,然后添加新的 Cast 框架:

dependencies {
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:mediarouter-v7:23.4.0'
    compile 'com.google.android.gms:play-services-cast-framework:9.4.0'
}

您还可以移除 Google Play 服务元数据:

<meta‐data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>

属于 CAF 的所有服务、activity 和资源都会自动与您的应用清单和资源合并。

CAF 支持的最低 Android SDK 版本为 9 (Gingerbread);CCL 的最低 Android SDK 版本为 10。

CCL 提供了一种便捷方法 BaseCastManager.checkGooglePlayServices(activity),用于验证设备上是否有兼容的 Google Play 服务版本。CAF 不会在 Cast SDK 中提供此信息。按照确保设备已安装 Google Play 服务 APK 中的说明确保用户设备上安装的是正确的 Google Play 服务 APK,因为更新可能不会立即覆盖所有用户。

您仍然需要为应用的主题使用 Theme.AppCompat 的变体。

初始化

对于 CCL,需要在应用实例的 onCreate() 方法中调用 VideoCastManager.initialize()。此逻辑应该从 Application 类代码中移除。

在 CAF 中,投放框架还需要明确的初始化步骤。这涉及初始化 CastContext 单例,使用适当的 OptionsProvider 指定接收器应用 ID 和任何其他全局选项。CastContext 通过提供客户端与之互动的单例来与 CCL 的 VideoCastManager 发挥类似的作用。OptionsProvider 与 CCL 的 CastConfiguration 类似,可让您配置 Cast 框架功能。

如果您当前的 CCL CastConfiguration.Builder 如下所示:

VideoCastManager.initialize(
   getApplicationContext(),
   new CastConfiguration.Builder(context.getString(R.string.app_id))
       .enableWifiReconnection()
       .enableAutoReconnect()
       .build());

然后,在 CAF 中,使用 CastOptions.Builder 的以下 CastOptionsProvider 类似:

public class CastOptionsProvider implements OptionsProvider {

    @Override
    public CastOptions getCastOptions(Context context) {
        return new CastOptions.Builder()
                .setReceiverApplicationId(context.getString(R.string.app_id))
                .build();
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(
            Context context) {
        return null;
    }
}

查看我们的示例应用,了解 OptionsProvider 的完整实现。

在 AndroidManifest.xml 文件的“application”元素中声明 OptionsProvider:

<application>
...
    <meta-data
        android:name=
          "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.google.sample.cast.refplayer.CastOptionsProvider"    
    />
</application>

在每个 ActivityonCreate 方法(而不是 Application 实例)中延迟初始化 CastContext

private CastContext mCastContext;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.video_browser);
    setupActionBar();

    mCastContext = CastContext.getSharedInstance(this);
}

如需访问 CastContext 单例,请使用以下命令:

mCastContext = CastContext.getSharedInstance(this);

设备发现

CCL 的 VideoCastManager incrementUiCounterdecrementUiCounter 应该从 ActivitiesonResumeonPause 方法中移除。

在 CAF 中,当应用进入前台并转到后台时,框架会自动启动和停止发现过程。

“投放”按钮和“投放”对话框

与 CCL 一样,这些组件由 MediaRouter v7 支持库提供。

“投放”按钮仍由 MediaRouteButton 实现,并可以作为菜单项添加到菜单项中(使用 ActionBarToolbar)。

菜单 xml 中 MediaRouteActionProvider 的声明与 CCL 相同:

<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
    app:showAsAction="always"/>

与 CCL 类似,替换每个 activity 的 onCreateOptionMenu() 方法,但不使用 CastManager.addMediaRouterButton,而是使用 CAF 的 CastButtonFactory 将 MediaRouteButton 连接到 Cast 框架:

public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.browse, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                                menu,
                                                R.id.media_route_menu_item);
    return true;
}

设备控制

与 CCL 类似,在 CAF 中,设备控制主要由框架处理。 发送者应用无需处理(也不应尝试)连接到设备并使用 GoogleApiClient 启动接收者应用。

发送者和接收者之间的互动现在以“会话”的形式表示。SessionManager 类会处理会话生命周期,并自动启动和停止会话以响应用户手势:用户在 Cast 对话框中选择 Cast 设备时,会话即开始;当用户在 Cast 对话框中点按“Stop Casting”按钮时,或发送者应用本身终止时,会话便会结束。

在 CCL 中,您必须扩展 VideoCastConsumerImpl 类才能跟踪投放会话状态:

private final VideoCastConsumer mCastConsumer = new VideoCastConsumerImpl() {
  public void onApplicationConnected(ApplicationMetadata appMetadata, 
                                     String sessionId,
                                     boolean wasLaunched) {}
  public void onDisconnectionReason(int reason) {}
  public void onDisconnected() {}
}

在 CAF 中,可以通过向 SessionManager 注册 SessionManagerListener 来向发送者应用通知会话生命周期事件。SessionManagerListener 回调为所有会话生命周期事件定义回调方法。

以下 SessionManagerListener 方法是从 CCL 的 VideoCastConsumer 接口映射的:

  • VideoCastConsumer.onApplicationConnected -> SessionManagerListener.onSessionStarted
  • VideoCastConsumer.onDisconnected -> SessionManagerListener.onSessionEnded

声明一个实现 SessionManagerListener 接口的类,并将 VideoCastConsumerImpl 逻辑移至匹配方法中:

private class CastSessionManagerListener implements SessionManagerListener<CastSession> {
  public void onSessionEnded(CastSession session, int error) {}
  public void onSessionStarted(CastSession session, String sessionId) {}
  public void onSessionEnding(CastSession session) {}
  ...
}

CastSession 类表示与投放设备的会话。该类具有用于控制设备音量和静音状态的方法,CCL 在 BaseCastManager 中执行此操作。

如需添加使用方,而不是使用 CCL VideoCastManager,请执行以下操作:

VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

现在注册 SessionManagerListener

mCastSessionManager = 
    CastContext.getSharedInstance(this).getSessionManager();
mCastSessionManagerListener = new CastSessionManagerListener();
mCastSessionManager.addSessionManagerListener(mCastSessionManagerListener,
                  CastSession.class);

如需停止监听 CCL 中的事件,请执行以下操作:

VideoCastManager.getInstance().removeVideoCastConsumer(mCastConsumer);

现在,使用 SessionManager 停止监听会话事件:

mCastSessionManager.removeSessionManagerListener(mCastSessionManagerListener,
                    CastSession.class);

如需明确与投放设备断开连接,可使用 CCL:

VideoCastManager.disconnectDevice(boolean stopAppOnExit, 
            boolean clearPersistedConnectionData,
            boolean setDefaultRoute)

对于 CAF,请使用 SessionManager

CastContext.getSharedInstance(this).getSessionManager()
                                   .endCurrentSession(true);

为了确定发送者是否连接到接收者,CCL 提供 VideoCastManager.getInstance().isConnected(),但在 CAF 中使用 SessionManager

public boolean isConnected() {
    CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                  .getSessionManager()
                                  .getCurrentCastSession();
    return (castSession != null && castSession.isConnected());
}

在 CAF 中,音量/静音状态更改通知仍会通过 Cast.Listener 中的回调方法传递;这些监听器通过 CastSession 注册。所有剩余的设备状态通知均通过 CastStateListener 回调传递;这些监听器通过 CastSession 注册。确保在当关联的 fragment、activity 或应用进入后台时仍取消注册监听器。

重新连接逻辑

CAF 尝试重建因暂时 Wi-Fi 信号丢失或其他网络连接错误而丢失的网络连接。此操作现在在会话级别完成;会话可能会在连接中断时进入“挂起”状态,并在连接恢复时回到“已连接”状态。在此过程中,框架会负责重新连接到接收器应用以及重新连接任何投放渠道。

CAF 提供自己的重新连接服务,因此您可以从清单中移除 CCL ReconnectionService

<service android:name="com.google.android.libraries.cast.companionlibrary.cast.reconnection.ReconnectionService"/>

此外,您无需在清单中为重新连接逻辑提供以下权限:

<uses‐permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses‐permission android:name="android.permission.ACCESS_WIFI_STATE"/>

CAF 重新连接服务默认处于启用状态,但可以使用 CastOptions 停用。

此外,CAF 还会默认启用自动恢复会话功能(可通过 CastOptions 停用)。如果在发送会话过程中发送应用被发送到后台或因其滑动而被终止,则框架会在发送器应用返回前台或重新启动时尝试恢复该会话;由 SessionManager 中的相应实例自动处理此问题。

自定义频道注册

CCL 提供两种向接收器创建自定义消息通道的方法:

  • CastConfiguration 可让您指定多个命名空间,然后 CCL 将为您创建渠道。
  • DataCastManager 与 VideoCastManager 类似,但侧重于非媒体用例。

CAF 不支持这两种创建自定义渠道的方式 - 您必须按照针对发送者应用添加自定义渠道的步骤操作。

与 CCL 类似,对于媒体应用,无需显式注册媒体控制通道。

媒体控件

在 CAF 中,RemoteMediaClient 类等效于 VideoCastManager 媒体方法。RemoteMediaClient.Listener 等效于 VideoCastConsumer 方法。特别是,VideoCastConsumeronRemoteMediaPlayerMetadataUpdatedonRemoteMediaPlayerStatusUpdated 方法分别映射到 RemoteMediaClient.ListeneronMetadataUpdatedonStatusUpdated 方法:

private class CastMediaClientListener implements RemoteMediaClient.Listener {

    @Override
    public void onMetadataUpdated() {
        setMetadataFromRemote();
    }

    @Override
    public void onStatusUpdated() {
        updatePlaybackState();
    }

    @Override
    public void onSendingRemoteMediaRequest() {
    }

    @Override
    public void onQueueStatusUpdated() {
    }

    @Override
    public void onPreloadStatusUpdated() {
    }
}

无需显式初始化或注册 RemoteMediaClient 对象;如果连接的接收器应用连接到媒体命名空间,则框架会在会话启动时自动实例化该对象并注册底层媒体通道。

您可以将 RemoteMediaClient 作为 CastSession 对象的 getRemoteMediaClient 方法进行访问。

CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                     .getSessionManager()
                                     .getCurrentCastSession();
mRemoteMediaClient = castSession.getRemoteMediaClient();
mRemoteMediaClientListener = new CastMediaClientListener();

而非 CCL:

VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

现在使用 CAF:

mRemoteMediaClient.addListener(mRemoteMediaClientListener);

可以向 RemoteMediaClient 注册任意数量的监听器,这样多个发送者组件就可以共享与会话关联的单个 RemoteMediaClient 实例。

CCL 的 VideoCastManager 提供了处理媒体播放的方法:

VideoCastManager manager = VideoCastManager.getInstance();
if (manager.isRemoteMediaLoaded()) {
    manager.pause();
    mCurrentPosition = (int) manager.getCurrentMediaPosition();
}

这些现在由 RemoteMediaClient 在 CAF 中实现:

if (mRemoteMediaClient.hasMediaSession()) {
    mRemoteMediaClient.pause();
    mCurrentPosition = 
        (int)mRemoteMediaClient.getApproximateStreamPosition();
}

在 CAF 中,在 RemoteMediaClient 上发出的所有媒体请求都会通过 PendingResult 回调返回 RemoteMediaClient.MediaChannelResult,该回调可用于跟踪请求的进度和最终结果。

CCL 和 CAF 都使用 MediaInfoMediaMetadata 类来表示媒体项和加载媒体。

如需在 CCL 中加载媒体,请使用 VideoCastManager

VideoCastManager.getInstance().loadMedia(media, autoPlay, mCurrentPosition, customData);

在 CAF 中,RemoteMediaClient 用于加载媒体:

mRemoteMediaClient.load(media, autoPlay, mCurrentPosition, customData);

为了在接收器上获取当前媒体会话的 Media 信息和状态,CCL 使用 VideoCastManager

MediaInfo mediaInfo = VideoCastManager.getInstance()
                                      .getRemoteMediaInformation();
int status = VideoCastManager.getInstance().getPlaybackStatus();
int idleReason = VideoCastManager.getInstance().getIdleReason();

在 CAF 中,请使用 RemoteMediaClient 获取相同的信息:

MediaInfo mediaInfo = mRemoteMediaClient.getMediaInfo();
int status = mRemoteMediaClient.getPlayerState();
int idleReason = mRemoteMediaClient.getIdleReason();

介绍性叠加层

与 CCL 类似,CAF 提供了一个自定义视图 IntroductoryOverlay,以便在首次向用户显示“投放”按钮时将其突出显示。

与其使用 CCL 的 VideoCastConsumer onCastAvailabilityChanged 方法确定何时显示叠加层,不如声明 CastStateListener,以确定 MediaRouter 在本地网络上发现 Cast 设备后,何时会显示“投放”按钮:

private IntroductoryOverlay mIntroductoryOverlay;
private MenuItem mMediaRouteMenuItem;

protected void onCreate(Bundle savedInstanceState) {
    ...
    mCastStateListener = new CastStateListener() {
        @Override
        public void onCastStateChanged(int newState) {
            if (newState != CastState.NO_DEVICES_AVAILABLE) {
                showIntroductoryOverlay();
            }
        }
    };
    mCastContext = CastContext.getSharedInstance(this);
    mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this, 
        savedInstanceState);
}

protected void onResume() {
    mCastContext.addCastStateListener(mCastStateListener);
    ...
}

protected void onPause() {
    mCastContext.removeCastStateListener(mCastStateListener);
    ...
}

跟踪 MediaRouteMenuItem 实例:

public boolean onCreateOptionsMenu(Menu menu) {
   super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.browse, menu);
    mMediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(
            getApplicationContext(), menu,
            R.id.media_route_menu_item);
    showIntroductoryOverlay();
    return true;
}

检查 MediaRouteButton 是否可见,以便可以显示介绍性叠加层:

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

查看我们的示例应用,获取用于显示介绍性叠加层的完整工作代码。

如需自定义介绍性叠加层的样式,请按照自定义简介叠加层中的步骤操作。

迷你控制器

对于要在其中显示迷你控制器的 activity,请在应用布局文件中使用 CAF 的 MiniControllerFragment,而不是 CCL 的 MiniController

<fragment
        android:id="@+id/cast_mini_controller"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:castShowImageThumbnail="true"
        android:visibility="gone"
        class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

CAF 不支持 CCL 的 MiniController 支持的手动配置,也不支持 Autoplay 功能。

如需自定义迷你控制器的样式和按钮,请按照自定义迷你控制器中的步骤操作。

通知和锁定屏幕

与 CCL 的 VideoCastNotificationService 类似,CAF 提供了一个 MediaNotificationService,用于管理在投放时媒体通知的显示。

您需要从清单中移除以下内容:

  • VideoIntentReceiver
  • VideoCastNotificationService

CCL 支持通过 CastConfiguration.Builder 提供自定义通知服务;CAF 不支持该服务。

考虑使用 CCL 进行以下 CastManager 初始化:

VideoCastManager.initialize(
   getApplicationContext(),
   new CastConfiguration.Builder(
           context.getString(R.string.app_id))
       .addNotificationAction(
           CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE,true)
       .addNotificationAction(
           CastConfiguration.NOTIFICATION_ACTION_DISCONNECT,true)
       .build());

对于 CAF 中的等效配置,SDK 提供了一个 NotificationsOptions.Builder,以帮助您将通知和锁定屏幕的媒体控件内置到发送者应用中。在初始化 CastContext 时,可以使用 CastOptions 启用通知和锁定屏幕控件。

public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = 
        new NotificationOptions.Builder()
            .setActions(Arrays.asList(
                MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
                MediaIntentReceiver.ACTION_STOP_CASTING), new int[]{0, 1})
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
             .setNotificationOptions(notificationOptions)
             .build();
    return new CastOptions.Builder()
             .setReceiverApplicationId(context.getString(R.string.app_id))
             .setCastMediaOptions(mediaOptions)
             .build();
}

在 CAF 中,通知和锁定屏幕控件始终处于启用状态。另请注意,系统默认提供播放/暂停和停止投射按钮。CAF 将自动跟踪 activity 的可见性,以确定何时显示媒体通知(Gingerbread 除外)。(有关 Gingerbread,请参阅之前的说明,了解如何使用 registerLifecycleCallbacksBeforeIceCreamSandwich();CCL 的 VideoCastManager incrementUiCounterdecrementUiCounter 调用应该被移除。)

如需自定义通知中显示的按钮,请按照将媒体控件添加到通知和锁定屏幕中的步骤操作。

展开的控制器

CCL 提供 VideoCastControllerActivityVideoCastControllerFragment,以便在投放媒体时显示展开的控制器。

您可以在清单中移除 VideoCastControllerActivity 声明。

在 CAF 中,您必须扩展 ExpandedControllerActivity 并添加“投放”按钮

如需自定义展开的控制器中显示的样式和按钮,请按照自定义展开的控制器一文中的步骤操作。

音频焦点

与 CCL 一样,音频焦点会自动管理。

音量控制

对于 Gingerbread,需要像 CCL 一样使用 dispatchKeyEvent。在 ICS 及更高版本中,CCL 和 CAF 音量控制会自动处理。

通过 CAF,您可以在应用 activity 内使用手机上的硬音量按钮控制投射音量,并在支持的版本上进行投放时显示视觉音量条。即使应用不在前方、处于锁定状态或屏幕处于关闭状态时,CAF 也可通过硬音量来处理音量变化。

字幕

在 Android KitKat 及更高版本中,可以通过“设置”>“无障碍”下的“字幕设置”自定义字幕。不过,较低版本的 Android 不支持此功能。CCL 通过为早期版本提供自定义设置并委托给 KitKat 及更高版本上的系统设置来处理此问题。

CAF 不提供更改字幕偏好设置的自定义设置。您应在清单和偏好设置 XML 中移除 CaptionsPreferenceActivity 引用。

因为不再需要更改字幕轨道,所以 CCL 的 TracksChooserDialog 由扩展控制器界面处理。

CAF 中的字幕 API 与 v2 类似。

调试日志记录

CAF 不提供调试日志记录设置。

其他

CAF 不支持以下 CCL 功能:

  • 在播放前通过提供 MediaAuthService 获取授权
  • 可配置的界面消息

示例应用

查看用于将 Universal Music Player for Android (uamp) 示例应用从 CCL 迁移到 CAF 的差异

我们还提供使用 CAF 的 Codelab 教程示例应用