支持 Web 应用的投射

1. 概览

Google Cast 徽标

此 Codelab 会教您如何修改现有网络视频应用,以便在支持 Google Cast 的设备上投射内容。

什么是 Google Cast?

Google Cast 可让用户将移动设备上的内容投射到电视上。然后,用户可以将其移动设备用作遥控器,来控制电视上的媒体播放。

借助 Google Cast SDK,您可以扩展应用以控制电视或音响系统。使用 Cast SDK,您可以基于 Google Cast 设计核对清单来添加必需的界面组件。

Google Cast 设计核对清单用于在所有支持的平台上实现简单、可预测的 Cast 用户体验。

构建目标

完成此 Codelab 后,您将拥有一个能够将视频投射到 Google Cast 设备上的 Chrome 网络视频应用。

学习内容

  • 如何将 Google Cast SDK 添加到示例视频应用中。
  • 如何添加“投射”按钮以选择 Google Cast 设备。
  • 如何连接到 Cast 设备并启动媒体接收器。
  • 如何投射视频。
  • 如何集成 Cast Connect

所需条件

  • 最新的 Google Chrome 浏览器。
  • npm。
  • 一部具有互联网访问权限的 Google Cast 设备,例如 ChromecastAndroid TV
  • 一台带 HDMI 输入端口的电视或显示器。
  • 若要测试 Cast Connect 集成,需要使用支持 Google TV 的 Chromecast;但对于此 Codelab 的其余部分,可视需要选择是否设置 Chromecast。如果没有,您可以跳过本教程末尾的添加 Cast Connect 支持步骤。

体验

  • 您需要具备之前的网络开发知识。
  • 您还需要有观看电视的经验 :)

您打算如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价自己在构建 Web 应用方面的经验水平?

新手水平 中等水平 熟练水平

您如何评价自己在观看电视方面的经验水平?

新手水平 中等水平 熟练水平

2. 获取示例代码

您可以将所有示例代码下载到您的计算机…

然后解压下载的 zip 文件。

3. 运行示例应用

Google Chrome 徽标

首先,我们来看看完成后的示例应用的外观。该应用是一个基础视频播放器。用户可以从列表中选择一个视频,然后在设备上本地播放该视频,或者将该视频投射到 Google Cast 设备上。

运行应用

如果您没有任何可用的服务器,也无需担心。您可以安装 node.js、http-server 和 ngrok 节点模块。

npm install -g http-server
npm install -g ngrok

如果您使用的是 http-server,请转到您的控制台并执行以下操作:

cd app-done
http-server

然后,您应该会看到如下所示的内容:

Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://172.19.17.192:8080
Hit CTRL-C to stop the server

请注意所使用的本地端口,并在新终端中通过 ngrok 使用 HTTPS 公开您的本地发件人:(8080 应该是 http 服务器的端口)

ngrok http 8080

这会设置一条连接到本地 HTTP 服务器的 ngrok 隧道,并为您分配一个可在下一步 (https://116ec943.eu.ngrok.io) 中使用的全局 HTTPS 安全端点:

ngrok by @inconshreveable                                                                                                                                                                                                                                     (Ctrl+C to quit)

Session Status         online
Version                2.2.4
Web Interface          http://127.0.0.1:8080
Forwarding             http://116ec943.eu.ngrok.io -> localhost:8080
Forwarding             https://116ec943.eu.ngrok.io -> localhost:8080

在此 Codelab 的学习期间,您应让 ngrokhttp-server 保持运行状态。您在本地所做的任何更改都会立即生效。

在浏览器中,访问从 ngrok 返回的 https 网址。

  1. 您应该会看到视频应用出现。
  2. 点击“投射”按钮,然后选择您的 Google Cast 设备。
  3. 选择一个视频,点击播放按钮。
  4. 该视频便会开始在您的 Google Cast 设备上播放。

在 Cast 设备上播放视频的图片

点击视频元素中的“暂停”按钮,即可暂停接收设备上的视频。点击视频元素中的播放按钮,继续播放视频。

点击“投射”按钮,即可停止投射到 Google Cast 设备。

我们需要先停止服务器,然后才能继续。转到运行 http-server 的终端,然后使用以下命令终止进程:

CTRL-C

转到运行 ngrok 的终端,然后使用以下命令终止进程:

CTRL-C

4. 准备启动项目

在 Cast 设备上播放视频的图片

我们需要在您下载的入门级应用中添加 Google Cast 支持。以下是我们将在此 Codelab 中使用的一些 Google Cast 术语:

  • 发送设备应用是指在移动设备或笔记本电脑上运行的应用;
  • 接收设备应用是指在 Google Cast 设备上运行的应用。

现在,您可以使用自己喜爱的文本编辑器,在入门级项目的基础上进行构建了:

  1. 从下载的示例代码中选择 文件夹图标app-start 目录。
  2. 使用 http-serverngrok 运行应用并探索界面。

请注意,在完成此 Codelab 的过程中,http-server 应该会获取您所做的更改。如果您发现它并未完成,请尝试终止并重启 http-server

应用设计

该应用从远程网络服务器中提取视频列表,并提供列表供用户浏览。用户可以选择视频查看相关详情,也可以在移动设备上本地播放视频。

该应用包含一个主视图(在 index.html 中定义)和主控制器 CastVideos.js.

index.html

此 HTML 文件声明了 Web 应用的几乎所有界面。

视图有几个部分,其中包含 div#main_video,其中包含视频元素。与视频 div 相关,我们有 div#media_control,用于定义视频元素的所有控件。下方是 media_info,它会显示视图中视频的详细信息。最后,carousel div 会在 div 中显示视频列表。

index.html 文件还会引导 Cast SDK,并指示 CastVideos 函数进行加载。

将填充这些元素的大多数内容都在 CastVideos.js 中定义、注入和控制。我们来看一下。

CastVideos.js

此脚本用于管理“投射视频”Web 应用的所有逻辑。在 CastVideos.js 中定义的视频列表及其关联的元数据包含在名为 mediaJSON 的对象中。

有几个主要部分共同负责在本地和远程管理和播放视频。总体而言,这是一个非常简单的 Web 应用。

CastPlayer 是管理整个应用、设置播放器、选择媒体以及将事件绑定到 PlayerHandler 以播放媒体的主类。CastPlayer.prototype.initializeCastPlayer 是设置所有 Cast 功能的方法。CastPlayer.prototype.switchPlayer 用于在本地播放器和远程播放器之间切换状态。CastPlayer.prototype.setupLocalPlayerCastPlayer.prototype.setupRemotePlayer 用于初始化本地和远程播放器。

PlayerHandler 是负责管理媒体播放的类。还有许多其他方法,负责管理媒体和播放的细节。

常见问题解答

5. 添加“投射”按钮

支持 Cast 的应用的图片

支持 Cast 的应用会在视频元素中显示“投射”按钮。点击“投射”按钮会显示用户可以选择的 Cast 设备列表。如果用户正在发送设备上本地播放内容,则选择 Cast 设备即会在相应 Cast 设备上开始播放或继续播放。在 Cast 会话期间,用户随时可以点击“投射”按钮,停止将应用投射到 Cast 设备。在应用的任何屏幕中,用户都必须能够连接到 Cast 设备或断开与 Cast 设备的连接,如 Google Cast 设计核对清单中所述。

配置

起始项目需要的依赖项和设置与完成后的示例应用相同。

如果您使用的是 http-server,请转到您的控制台并执行以下操作:

cd app-start
http-server

然后,您应该会看到如下所示的内容:

Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://172.19.17.192:8080
Hit CTRL-C to stop the server

请注意所使用的本地端口,并在新终端中通过 ngrok 使用 HTTPS 公开您的本地发件人:(8080 应该是 http 服务器的端口)

ngrok http 8080

这会设置一条连接到本地 HTTP 服务器的 ngrok 隧道,并为您分配一个可在下一步 (https://116ec943.eu.ngrok.io) 中使用的全局 HTTPS 安全端点:

ngrok by @inconshreveable                                                                                                                                                                                                                                     (Ctrl+C to quit)

Session Status         online
Version                2.2.4
Web Interface          http://127.0.0.1:8080
Forwarding             http://116ec943.eu.ngrok.io -> localhost:8080
Forwarding             https://116ec943.eu.ngrok.io -> localhost:8080

在此 Codelab 的学习期间,您应让 ngrokhttp-server 保持运行状态。您在本地所做的任何更改都会立即生效。

在浏览器中,访问从 ngrok 返回的 https 网址。

初始化

Cast 框架有一个全局单例对象 CastContext,用于协调框架的所有 activity。必须在应用的生命周期早期初始化此对象,通常从分配给 window['__onGCastApiAvailable'] 的回调调用,该回调在 Cast SDK 加载后可供使用。在这种情况下,CastContext 是在 CastPlayer.prototype.initializeCastPlayer 中调用的,后者是从上述回调中调用的。

初始化 CastContext 时必须提供 options JSON 对象。此类包含会影响框架行为的选项。其中最重要的是接收器应用 ID,它用于过滤可用 Cast 设备列表,以仅显示能够运行指定应用的设备,并在 Cast 会话启动时启动接收器应用。

您在开发自己的支持 Cast 的应用时,必须注册为 Cast 开发者,然后为您的应用获取应用 ID。在此 Codelab 中,我们将使用一个示例应用 ID。

将以下代码添加到 index.htmlbody 部分的最末尾处:

<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

将以下代码添加到 index.html 以初始化 CastVideos 应用以及初始化 CastContext

<script src="CastVideos.js"></script>
<script type="text/javascript">
var castPlayer = new CastPlayer();
window['__onGCastApiAvailable'] = function(isAvailable) {
  if (isAvailable) {
    castPlayer.initializeCastPlayer();
  }
};
</script>

现在,我们需要在 CastVideos.js 中添加一个新方法,该方法与我们刚刚在 index.html 中调用的方法相对应。我们来添加一个名为 initializeCastPlayer 的新方法,该方法会在 CastContext 上设置选项,并初始化新的 RemotePlayerRemotePlayerControllers

/**
 * This method sets up the CastContext, and a few other members
 * that are necessary to play and control videos on a Cast
 * device.
 */
CastPlayer.prototype.initializeCastPlayer = function() {

    var options = {};

    // Set the receiver application ID to your own (created in
    // the Google Cast Developer Console), or optionally
    // use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
    options.receiverApplicationId = 'C0868879';

    // Auto join policy can be one of the following three:
    // ORIGIN_SCOPED - Auto connect from same appId and page origin
    // TAB_AND_ORIGIN_SCOPED - Auto connect from same appId, page origin, and tab
    // PAGE_SCOPED - No auto connect
    options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;

    cast.framework.CastContext.getInstance().setOptions(options);

    this.remotePlayer = new cast.framework.RemotePlayer();
    this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer);
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
        this.switchPlayer.bind(this)
    );
};

最后,我们需要为 RemotePlayerRemotePlayerController 创建变量:

var CastPlayer = function() {
  //...
  /* Cast player variables */
  /** @type {cast.framework.RemotePlayer} */
  this.remotePlayer = null;
  /** @type {cast.framework.RemotePlayerController} */
  this.remotePlayerController = null;
  //...
};

投放按钮

现在,CastContext 已初始化,接下来需要添加“投射”按钮,以便用户选择 Cast 设备。Cast SDK 提供了一个名为 google-cast-launcher 且 ID 为“castbutton"”的“投射”按钮组件。只需向 media_control 部分添加一个 button 即可将其添加到该应用的视频元素中。

按钮元素将如下所示:

<google-cast-launcher id="castbutton"></google-cast-launcher>

将以下代码添加到 media_control 部分中的 index.html

<div id="media_control">
  <div id="play"></div>
  <div id="pause"></div>
  <div id="progress_bg"></div>
  <div id="progress"></div>
  <div id="progress_indicator"></div>
  <div id="fullscreen_expand"></div>
  <div id="fullscreen_collapse"></div>
  <google-cast-launcher id="castbutton"></google-cast-launcher>
  <div id="audio_bg"></div>
  <div id="audio_bg_track"></div>
  <div id="audio_indicator"></div>
  <div id="audio_bg_level"></div>
  <div id="audio_on"></div>
  <div id="audio_off"></div>
  <div id="duration">00:00:00</div>
</div>

现在,在 Chrome 浏览器中刷新页面。您应该会在视频元素中看到“投射”按钮,当您点击该按钮时,它会列出本地网络中的投射设备。设备发现由 Chrome 浏览器自动管理。选择 Cast 设备,然后示例接收设备应用便会在 Cast 设备上加载。

我们尚未添加任何媒体播放支持,因此您尚无法在投射设备上播放视频。点击“投射”按钮可停止投射。

6. 投射视频内容

显示支持 Cast 的应用(包含 Cast 设备选择菜单)的图片

我们将扩展示例应用,以便还可以在 Cast 设备上远程播放视频。为此,我们需要监听 Cast 框架生成的各种事件。

投射媒体

大体而言,如果您想在 Cast 设备上播放媒体,需要执行以下操作:

  1. 从 Cast SDK 创建一个为媒体项建模的 MediaInfo JSON 对象。
  2. 用户连接到 Cast 设备以启动接收器应用。
  3. MediaInfo 对象加载到接收设备中,然后播放内容。
  4. 跟踪媒体状态。
  5. 根据用户互动情况向接收设备发送播放命令。

第 1 步就是将一个对象映射到另一个对象;MediaInfo 是 Cast SDK 能够理解的内容,mediaJSON 是应用对媒体项的封装;我们可以轻松地将 mediaJSON 映射到 MediaInfo。我们已经在上一部分中完成了第 2 步。使用 Cast SDK 可以轻松执行第 3 步。

示例应用 CastPlayer 已通过 switchPlayer 方法区分本地播放和远程播放:

if (cast && cast.framework) {
  if (this.remotePlayer.isConnected) {
    //...

在此 Codelab 中,您无需准确了解所有示例播放器逻辑的运作方式。但请务必注意,您必须修改应用的媒体播放器,才能同时感知本地播放和远程播放。

目前,本地播放器始终处于本地播放状态,因为它还不知道任何关于投射状态的信息。我们需要根据 Cast 框架中发生的状态转换来更新界面。例如,如果我们开始投射,则需要停止本地播放并停用一些控件。同样,如果我们在此视图控制器中停止投射,则需要转换为本地播放。为处理此情况,我们需要监听 Cast 框架生成的各种事件。

投射会话管理

对于 Cast 框架,Cast 会话结合了连接到设备、启动(或加入现有会话)、连接到接收器应用以及初始化媒体控制渠道(如适用)的步骤。媒体控制通道是 Cast 框架从接收器发送和接收媒体播放相关消息的方式。

当用户从“投射”按钮选择设备时,投射会话将自动启动,并会在用户断开连接时自动停止。Cast 框架还会自动处理由于网络问题而重新连接到接收设备会话的情况。

Cast 会话由 CastSession 管理,后者可通过 cast.framework.CastContext.getInstance().getCurrentSession() 进行访问。EventListener 回调可用于监控会话事件,如创建、暂停、恢复和终止。

在当前应用中,所有会话和状态管理都是在 setupRemotePlayer 方法中处理的。首先,将以下代码添加到您的 CastVideos.js 中,以开始在您的应用中配置此设置:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

我们仍需绑定回调中的所有事件,并处理所有传入事件。这是一项相当简单的操作,因此我们现在就来处理这个问题:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    // Add event listeners for player changes which may occur outside sender app
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED,
        function() {
            if (this.remotePlayer.isPaused) {
                this.playerHandler.pause();
            } else {
                this.playerHandler.play();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_MUTED_CHANGED,
        function() {
            if (this.remotePlayer.isMuted) {
                this.playerHandler.mute();
            } else {
                this.playerHandler.unMute();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.VOLUME_LEVEL_CHANGED,
        function() {
            var newVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
            var p = document.getElementById('audio_bg_level');
            p.style.height = newVolume + 'px';
            p.style.marginTop = -newVolume + 'px';
        }.bind(this)
    );

    // This object will implement PlayerHandler callbacks with
    // remotePlayerController, and makes necessary UI updates specific
    // to remote playback
    var playerTarget = {};

    playerTarget.play = function () {
        if (this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }

        var vi = document.getElementById('video_image');
        vi.style.display = 'block';
        var localPlayer = document.getElementById('video_element');
        localPlayer.style.display = 'none';
    }.bind(this);

    playerTarget.pause = function () {
        if (!this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }
    }.bind(this);

    playerTarget.stop = function () {
         this.remotePlayerController.stop();
    }.bind(this);

    playerTarget.getCurrentMediaTime = function() {
        return this.remotePlayer.currentTime;
    }.bind(this);

    playerTarget.getMediaDuration = function() {
        return this.remotePlayer.duration;
    }.bind(this);

    playerTarget.updateDisplayMessage = function () {
        document.getElementById('playerstate').style.display = 'block';
        document.getElementById('playerstatebg').style.display = 'block';
        document.getElementById('video_image_overlay').style.display = 'block';
        document.getElementById('playerstate').innerHTML =
            this.mediaContents[ this.currentMediaIndex]['title'] + ' ' +
            this.playerState + ' on ' + castSession.getCastDevice().friendlyName;
    }.bind(this);

    playerTarget.setVolume = function (volumeSliderPosition) {
        // Add resistance to avoid loud volume
        var currentVolume = this.remotePlayer.volumeLevel;
        var p = document.getElementById('audio_bg_level');
        if (volumeSliderPosition < FULL_VOLUME_HEIGHT) {
            var vScale =  this.currentVolume * FULL_VOLUME_HEIGHT;
            if (volumeSliderPosition > vScale) {
                volumeSliderPosition = vScale + (pos - vScale) / 2;
            }
            p.style.height = volumeSliderPosition + 'px';
            p.style.marginTop = -volumeSliderPosition + 'px';
            currentVolume = volumeSliderPosition / FULL_VOLUME_HEIGHT;
        } else {
            currentVolume = 1;
        }
        this.remotePlayer.volumeLevel = currentVolume;
        this.remotePlayerController.setVolumeLevel();
    }.bind(this);

    playerTarget.mute = function () {
        if (!this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.unMute = function () {
        if (this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.isMuted = function() {
        return this.remotePlayer.isMuted;
    }.bind(this);

    playerTarget.seekTo = function (time) {
        this.remotePlayer.currentTime = time;
        this.remotePlayerController.seek();
    }.bind(this);

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

加载媒体

在 Cast SDK 中,RemotePlayerRemotePlayerController 提供了一组便捷 API,用于管理接收器上的远程媒体播放。对于支持媒体播放的 CastSession,SDK 会自动创建 RemotePlayerRemotePlayerController 的实例。您可以通过分别创建 cast.framework.RemotePlayercast.framework.RemotePlayerController 实例来访问这些函数,如本 Codelab 前面部分所示。

接下来,我们需要通过在 SDK 中构建一个 MediaInfo 对象来在接收器上加载并处理请求并传入请求。为此,请将以下代码添加到 setupRemotePlayer 中:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    //...

    playerTarget.load = function (mediaIndex) {
        console.log('Loading...' + this.mediaContents[mediaIndex]['title']);
        var mediaInfo = new chrome.cast.media.MediaInfo(
            this.mediaContents[mediaIndex]['sources'][0], 'video/mp4');

        mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
        mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC;
        mediaInfo.metadata.title = this.mediaContents[mediaIndex]['title'];
        mediaInfo.metadata.images = [
            {'url': MEDIA_SOURCE_ROOT + this.mediaContents[mediaIndex]['thumb']}];

        var request = new chrome.cast.media.LoadRequest(mediaInfo);
        castSession.loadMedia(request).then(
            this.playerHandler.loaded.bind(this.playerHandler),
            function (errorCode) {
                this.playerState = PLAYER_STATE.ERROR;
                console.log('Remote media load error: ' +
                    CastPlayer.getErrorMessage(errorCode));
            }.bind(this));
    }.bind(this);

    //...
};

现在,添加一个用于在本地播放和远程播放之间切换的方法:

/**
 * This is a method for switching between the local and remote
 * players. If the local player is selected, setupLocalPlayer()
 * is run. If there is a cast device connected we run
 * setupRemotePlayer().
 */
CastPlayer.prototype.switchPlayer = function() {
    this.stopProgressTimer();
    this.resetVolumeSlider();
    this.playerHandler.stop();
    this.playerState = PLAYER_STATE.IDLE;
    if (cast && cast.framework) {
        if (this.remotePlayer.isConnected) {
            this.setupRemotePlayer();
            return;
        }
    }
    this.setupLocalPlayer();
};

最后,添加一个方法来处理任何 Cast 错误消息:

/**
 * Makes human-readable message from chrome.cast.Error
 * @param {chrome.cast.Error} error
 * @return {string} error message
 */
CastPlayer.getErrorMessage = function(error) {
  switch (error.code) {
    case chrome.cast.ErrorCode.API_NOT_INITIALIZED:
      return 'The API is not initialized.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CANCEL:
      return 'The operation was canceled by the user' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CHANNEL_ERROR:
      return 'A channel to the receiver is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.EXTENSION_MISSING:
      return 'The Cast extension is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.INVALID_PARAMETER:
      return 'The parameters to the operation were not valid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE:
      return 'No receiver was compatible with the session request.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.SESSION_ERROR:
      return 'A session could not be created, or a session was invalid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.TIMEOUT:
      return 'The operation timed out.' +
        (error.description ? ' :' + error.description : '');
  }
};

现在,运行应用。连接到 Cast 设备,然后开始播放视频。您应该会看到视频在接收设备上播放。

7. 添加 Cast Connect 支持

Cast Connect 库允许现有的发送者应用通过 Cast 协议与 Android TV 应用进行通信。Cast Connect 基于 Cast 基础架构进行构建,您的 Android TV 应用充当接收器。

依赖项

  • Chrome 浏览器 M87 或更高版本

设置 Android 接收器兼容

为了启动 Android TV 应用(也称为 Android 接收器),我们需要在 CastOptions 对象中将 androidReceiverCompatible 标志设置为 true。

initializeCastPlayer 函数的 CastVideos.js 中添加以下代码:

var options = {};
...
options.androidReceiverCompatible = true;

cast.framework.CastContext.getInstance().setOptions(options);

设置启动凭据

在发件人端,您可以指定 CredentialsData 以表示要加入会话的用户。credentials 是一个可由用户定义的字符串,只要 ATV 应用可以理解即可。CredentialsData 仅在启动或加入时传递给 Android TV 应用。如果您在联网时重新设置它,它将不会传递至您的 Android TV 应用。

要设置启动凭据,需要在设置启动选项后随时定义 CredentialsData

将以下代码添加到 CastVideos.js 类中的 initializeCastPlayer 函数下:

cast.framework.CastContext.getInstance().setOptions(options);
...
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
...

设置加载请求时的凭据

如果您的网络接收器应用和 Android TV 应用以不同方式处理 credentials,您可能需要为每个应用定义不同的凭据。为此,请在 setupRemotePlayer 函数的 playerTarget.load 下的 CastVideos.js 中添加以下代码:

...
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
request.atvCredentials = 'atv-user-credentials';
...

根据发送者投射到的接收端应用,SDK 现在会自动处理用于当前会话的凭据。

测试 Cast Connect

在 Chromecast(支持 Google TV)上安装 Android TV APK 的步骤如下:

  1. 找到 Android TV 设备的 IP 地址。通常,您可在设置 &; 互联网 > (您的设备所连接的网络名称)下找到此设置。右侧会显示该网络的详情和您的设备 IP。
  2. 使用设备的 IP 地址通过终端通过 ADB 连接到设备:
$ adb connect <device_ip_address>:5555
  1. 从终端窗口中,转到您在此 Codelab 开始时下载的 Codelab 示例的顶级文件夹。例如:
$ cd Desktop/chrome_codelab_src
  1. 运行以下命令,将此文件夹中的 .apk 文件安装到 Android TV:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. 现在,您应该可以在 Android TV 设备上的您的应用菜单中按投射视频的名称来查看应用。
  2. 运行更新后的 Web 发送者代码,并使用投射图标或在 Chrome 浏览器的下拉菜单中选择 Cast..,与您的 Android TV 设备建立投射会话。现在,这会在您的 Android 接收器上启动 Android TV 应用,并可让您使用 Android TV 遥控器控制播放。

8. 恭喜

您现在已经知道如何在 Chrome Web 应用上使用 Cast SDK 微件为视频应用启用 Cast。

有关详情,请参阅网站发件人开发者指南。