1. 概览
在此 Codelab 中,您将学习如何修改现有 Web 视频应用,使其可在支持 Google Cast 的设备上投放内容。
什么是 Google Cast?
Google Cast 可让用户将移动设备上的内容投射到电视上。然后,用户可以将其移动设备用作遥控器,来控制电视上的媒体播放。
借助 Google Cast SDK,您可以扩展应用以控制电视或音响系统。使用 Cast SDK,您可以基于 Google Cast 设计核对清单来添加必需的界面组件。
Google Cast 设计核对清单用于在所有支持的平台上实现简单、可预测的 Cast 用户体验。
构建目标
完成此 Codelab 后,您将拥有一个能够将视频投射到 Google Cast 设备上的 Chrome Web 视频应用。
学习内容
- 如何将 Google Cast SDK 添加到示例视频应用中。
- 如何添加“投射”按钮以选择 Google Cast 设备。
- 如何连接到 Cast 设备并启动媒体接收器。
- 如何投射视频。
- 如何集成 Cast Connect
所需条件
- 最新版本的 Google Chrome 浏览器。
- HTTPS 托管服务,例如 Firebase Hosting 或 ngrok。
- 一台可连接到互联网的 Google Cast 设备,例如 Chromecast 或 Android TV。
- 一台带 HDMI 输入端口的电视或显示器。
- 如需测试 Cast Connect 集成,必须使用 Chromecast(支持 Google TV),但对于 Codelab 的其余部分,它是可选的。如果您还没有设置,可在本教程结束时跳过添加 Cast Connect 支持步骤。
体验
- 您需要具备 Web 开发经验。
- 您还需要有观看电视的经验 :)
您打算如何使用本教程?
您如何评价自己在构建 Web 应用方面的经验水平?
您如何评价自己在观看电视方面的经验水平?
2. 获取示例代码
您可以将所有示例代码下载到您的计算机…
然后解压下载的 zip 文件。
3. 运行示例应用
首先,我们来看看完成后的示例应用的外观。该应用是一个基础视频播放器。用户可以从列表中选择一个视频,然后在设备上本地播放该视频,或者将该视频投射到 Google Cast 设备上。
要使用已完成的构建,需要对其进行托管。
如果您没有可用的服务器,可以使用 Firebase Hosting 或 ngrok。
运行服务器
设置好所需的服务后,前往 app-done
并启动服务器。
在浏览器中,访问您托管的示例的 https 网址。
- 您应该会看到视频应用。
- 点击“投屏”按钮,然后选择您的 Google Cast 设备。
- 选择一个视频,点击播放按钮。
- 该视频便会开始在您的 Google Cast 设备上播放。
点击视频元素中的“暂停”按钮可在接收设备上暂停该视频。点击视频元素中的“播放”按钮可继续播放该视频。
点击“投射”按钮可停止投射到 Google Cast 设备。
在继续操作之前,请先停止服务器。
4. 准备起始项目
我们需要在您下载的入门级应用中添加 Google Cast 支持。以下是我们将在此 Codelab 中使用的一些 Google Cast 术语:
- 发送设备应用是指在移动设备或笔记本电脑上运行的应用;
- 接收设备应用是指在 Google Cast 设备上运行的应用。
现在,您可以使用自己喜爱的文本编辑器基于起始项目构建应用了:
- 从下载的示例代码中选择
app-start
目录。 - 使用您的服务器运行该应用并浏览界面。
请注意,在处理此 Codelab 时,您需要根据服务在服务器上重新托管示例。
应用设计
该应用从远程网络服务器中提取视频列表,并提供列表供用户浏览。用户可以选择视频查看相关详情,也可以在移动设备上本地播放视频。
该应用包含一个主视图(在 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
是设置所有投屏功能的方法。CastPlayer.prototype.switchPlayer
用于在本地播放器和远程播放器之间切换状态。CastPlayer.prototype.setupLocalPlayer
和 CastPlayer.prototype.setupRemotePlayer
会初始化本地和远程播放器。
PlayerHandler
是负责管理媒体播放的类。还有许多其他方法负责管理媒体和播放的细节。
常见问题解答
5. 添加“投放”按钮
支持 Cast 的应用会在视频元素中显示“投放”按钮。点击“投射”按钮会显示用户可以选择的 Cast 设备列表。如果用户正在发送设备上本地播放内容,则选择 Cast 设备即会在相应 Cast 设备上开始播放或继续播放。在 Cast 会话期间,用户随时可以点击“投射”按钮,停止将应用投射到 Cast 设备。在应用的任何屏幕中,用户都必须能够连接到 Cast 设备或断开与 Cast 设备的连接,如 Google Cast 设计核对清单中所述。
配置
起始项目需要的依赖项和设置与完成后的示例应用相同,但这次需要托管 app-start
的内容。
在浏览器中,访问您托管的示例的 https
网址。
请注意,进行更改后,您需要根据服务在服务器上重新托管示例。
初始化
Cast 框架有一个全局单例对象 CastContext
,用于协调框架的所有 activity。此对象必须在应用生命周期的早期初始化,通常从分配给 window['__onGCastApiAvailable']
的回调进行调用,该回调在 Cast SDK 加载完毕且可供使用后调用。在本例中,CastContext
是在 CastPlayer.prototype.initializeCastPlayer
中调用,后者是从上述回调调用。
在初始化 CastContext
时必须提供 options
JSON 对象。此类包含会影响该框架行为的选项。其中最重要的是接收设备应用 ID,此 ID 用于过滤可用 Cast 设备的列表,让系统仅显示能够运行指定应用的设备,并在投射会话开始后启动接收设备应用。
您在开发自己的支持 Cast 的应用时,必须注册为 Cast 开发者,然后为您的应用获取应用 ID。在此 Codelab 中,我们将使用一个示例应用 ID。
在 index.html
中 body
部分的末尾添加以下代码:
<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 上设置选项,并初始化新的 RemotePlayer
和 RemotePlayerControllers
:
/**
* 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)
);
};
最后,我们需要为 RemotePlayer
和 RemotePlayerController
创建变量:
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>
将以下代码添加到 index.html
中的 media_control
部分:
<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 浏览器中刷新页面。您应该会在视频元素中看到一个“投屏”按钮,当您点击该按钮时,它会列出您本地网络中的 Cast 设备。设备发现由 Chrome 浏览器自动管理。选择您的 Cast 设备,然后示例接收设备应用便会在 Cast 设备上加载。
我们尚未添加任何媒体播放支持,因此您还没法在 Cast 设备上播放视频。点击“投放”按钮即可停止投放。
6. 投放视频内容
我们将扩展示例应用,以便还可以在 Cast 设备上远程播放视频。为此,我们需要监听 Cast 框架生成的各种事件。
投射媒体
大体而言,如果您想在 Cast 设备上播放媒体内容,需要满足以下条件:
- 从 Cast SDK 创建一个用于为媒体内容建模的
MediaInfo
JSON
对象。 - 用户连接到 Cast 设备以启动您的接收器应用。
- 将
MediaInfo
对象加载到接收设备中,然后播放内容。 - 跟踪媒体状态。
- 根据用户互动情况向接收设备发送播放命令。
第 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 中,RemotePlayer
和 RemotePlayerController
提供了一组便利 API,用于在接收设备上管理远程媒体播放。对于支持媒体播放的 CastSession
,SDK 会自动创建 RemotePlayer
和 RemotePlayerController
的实例。您可以分别创建 cast.framework.RemotePlayer
和 cast.framework.RemotePlayerController
的实例来访问它们,如本 Codelab 前面所示。
接下来,我们需要构建一个 MediaInfo 对象,以便 SDK 进行处理并传入请求,从而在接收器上加载当前所选的视频。为此,请将以下代码添加到 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 : '');
}
};
现在,运行应用。连接到投放设备,然后开始播放视频。您应该会看到视频在接收设备上播放。
7. 添加了 Cast Connect 支持
通过使用 Cast Connect 库,现有的发送器应用可以通过 Cast 协议与 Android TV 应用通信。Cast Connect 在此基础架构之上构建,并以 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);
...
在加载请求中设置凭据
如果您的 Web 接收器应用和 Android TV 应用处理 credentials
的方式不同,您可能需要为每个应用定义单独的凭据。为此,请在 CastVideos.js
的 setupRemotePlayer
函数中的 playerTarget.load
下方添加以下代码:
...
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 的步骤:
- 找到 Android TV 设备的 IP 地址。通常,您可以在设置 > 网络和互联网 >(设备连接到的网络名称)下找到该设置。右侧会显示详细信息以及设备在网络上的 IP 地址。
- 使用设备的 IP 地址通过终端通过 ADB 连接到设备:
$ adb connect <device_ip_address>:5555
- 在终端窗口中,进入您在本 Codelab 开始时下载的 Codelab 示例的顶级文件夹。例如:
$ cd Desktop/chrome_codelab_src
- 运行以下命令,将此文件夹中的 .apk 文件安装到 Android TV:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- 现在,您应该能在 Android TV 设备上的您的应用菜单中看到一个名称为“投射视频”的应用。
- 运行更新后的网络发送器代码,然后使用投放图标或从 Chrome 浏览器的下拉菜单中选择
Cast..
,与 Android TV 设备建立投放会话。现在,Android 接收器上应该会启动 Android TV 应用,您可以使用 Android TV 遥控器控制播放。
8. 恭喜
现在,您已经知道如何在 Chrome Web 应用上使用 Cast SDK 微件让视频应用支持 Cast。
如需了解详情,请参阅网络发件人开发者指南。