在 Chromecast 上使用 IMA DAI SDK

播放已向 Google Cloud Video Stitcher API 注册的 VOD 流

本指南演示了如何使用 IMA DAI SDK for CAF Web Receivers 请求和播放 Google Cloud VOD 流会话

本指南在全服务 DAI 的基本示例的基础上进行了扩展,增加了对通过 Google Cloud Video Stitcher API 注册的视频流的支持。

请确保您的流式传输格式受 CAF 网络接收器支持,然后再继续。

如需了解如何与其他平台集成或使用 IMA 客户端 SDK,请参阅互动式媒体广告 SDK

背景

在使用本指南之前,请先熟悉 Chromecast 应用框架的 Web 接收器协议。

本指南假设您对 CAF 接收器概念(例如消息拦截器MediaInformation 对象)以及使用 Cast Command and Control 工具来模拟 CAF 发送器有基本的了解。

应用组件和架构

使用 IMA CAF DAI SDK 通过 Google Cloud Video Stitcher API 实现 VOD 流播放涉及两个主要组件,如本指南所示:

  • VideoStitcherVodStreamRequest:用于定义向 Google 服务器发出的流式请求的对象。
  • StreamManager:用于处理视频流与 IMA DAI SDK 之间通信的对象,例如触发跟踪 ping 和将流事件转发给发布商。

设置 Google Cloud 项目

输入以下变量以在 IMA SDK 中使用:

  • 位置 - 创建 VOD 配置的 Google Cloud 区域。

    LOCATION

  • 项目编号:使用 Video Stitcher API 的 Google Cloud 项目编号。

    PROJECT_NUMBER

  • OAuth 令牌:具有 Video Stitcher 用户角色的服务账号的短期有效的 OAuth 令牌。详细了解如何为服务账号创建短期凭据

    OAUTH_TOKEN

  • 广告资源网代码:用于请求广告的 Google Ad Manager 广告资源网代码。

    NETWORK_CODE

  • 视频点播配置 ID - 视频点播视频流的视频点播配置 ID。

    VOD_CONFIG_ID

    如需详细了解如何创建视频点播配置 ID,请参阅 Cloud Stitching 创建视频点播配置指南

    VOD_URI

设置自定义投屏接收器

如需开发自定义 Cast 接收器,您需要具备以下条件:

准备发送方以将流数据传递给接收方

首先,配置发送方应用,使其向 Web 接收方发出包含平台 MediaInformation 对象中以下字段的加载请求。

字段 目录
contentId 相应媒体项的唯一标识符,如 Cast 参考文档中所述。此 ID 不应在同一媒体队列中重复用于多个项。

CONTENT_ID

contentUrl 如果 DAI 视频流无法加载,则播放的备用推流网址(可选)。

BACKUP_STREAM_URL

contentType 如果 DAI 流无法加载,则播放的备用推流网址的可选 MIME 类型。

BACKUP_STREAM_MIMETYPE

streamType 用于此值的字符串字面量或常量因发送方平台而异。

VOD

customData

customData 字段包含其他必需字段的键值对存储区。在这种情况下,customData 包含您收集的 DAI 流数据。

字段 目录
region LOCATION
projectNumber PROJECT_NUMBER
oAuthToken OAUTH_TOKEN
networkCode NETWORK_CODE
vodConfigId VOD_CONFIG_ID

以下是一些代码示例,可帮助您快速入门:

Web

如需在 Cast Web 发送器中配置这些值,请先使用所需数据创建 MediaInfo 对象,然后向 Web 接收器发出 load 请求

// Create mediaInfo object
const mediaInfo = new chrome.cast.media.MediaInfo("CONTENT_ID");
mediaInfo.contentUrl = "BACKUP_STREAM_URL";
mediaInfo.contentType = "BACKUP_STREAM_MIMETYPE";
mediaInfo.streamType = chrome.cast.media.StreamType.VOD;
mediaInfo.customData = {
  region: "LOCATION",
  projectNumber: "PROJECT_NUMBER",
  oAuthToken: "OAUTH_TOKEN",
  networkCode: "NETWORK_CODE",
  vodConfigId: "VOD_CONFIG_ID"
};

// Make load request to cast web receiver
const castSession = cast.framework.CastContext.getInstance().getCurrentSession();
const request = new chrome.cast.media.LoadRequest(mediaInfo);
castSession.loadMedia(request).then(
  () => { console.log('Load succeed'); },
  (errorCode) => { console.log('Error code: ' + errorCode); });

Android

如需在 Cast Web 发送器中配置这些值,请先创建一个包含所需数据的 MediaInfo 对象,然后向 Web 接收器发出 load 请求

JSONObject customData = new JSONObject()
  .put("region", "LOCATION")
  .put("projectNumber", "PROJECT_NUMBER")
  .put("oAuthToken", "OAUTH_TOKEN")
  .put("networkCode", "NETWORK_CODE")
  .put("vodConfigId", "VOD_CONFIG_ID");

MediaInfo mediaInfo = MediaInfo.Builder("CONTENT_ID")
  .setContentUrl("BACKUP_STREAM_URL")
  .setContentType("BACKUP_STREAM_MIMETYPE")
  .setStreamType(MediaInfo.STREAM_TYPE_VOD)
  .setCustomData(customData)
  .build();

RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

iOS (Obj-C)

如需在 Cast Web 发送器中配置这些值,请先使用所需数据创建 GCKMediaInformation 对象,然后向 Web 接收器发出 load 请求

NSURL url = [NSURL URLWithString:@"BACKUP_STREAM_URL"];
NSDictionary *customData = @{
  @"region": @"LOCATION",
  @"projectNumber": @"PROJECT_NUMBER",
  @"oAuthToken": @"OAUTH_TOKEN",
  @"networkCode": @"NETWORK_CODE",
  @"vodConfigId": @"VOD_CONFIG_ID"
};

GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentID: @"CONTENT_ID"];
mediaInfoBuilder.contentURL = url;
mediaInfoBuilder.contentType = @"BACKUP_STREAM_MIMETYPE";
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.customData = customData;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

iOS (Swift)

如需在 Cast Web 发送器中配置这些值,请先使用所需数据创建 GCKMediaInformation 对象,然后向 Web 接收器发出 load 请求

let url = URL.init(string: "BACKUP_STREAM_URL")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let customData = [
  "region": "LOCATION",
  "projectNumber": "PROJECT_NUMBER",
  "oAuthToken": "OAUTH_TOKEN",
  "networkCode": "NETWORK_CODE",
  "vodConfigId": "VOD_CONFIG_ID"
]

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentId: "CONTENT_ID")
mediaInfoBuilder.contentURL = mediaUrl
mediaInfoBuilder.contentType = "BACKUP_STREAM_MIMETYPE"
mediaInfoBuilder.streamType = GCKMediaStreamType.none
mediaInfoBuilder.customData = customData
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}

CAC 工具

如需在 Cast Command and Control 工具中配置这些值,请点击“Load Media”标签页,并将自定义加载请求类型设置为 LOAD。然后,将文本区域中的 JSON 数据替换为以下 JSON:

{
  "media": {
    "contentId": "CONTENT_ID",
    "contentUrl": "BACKUP_STREAM_URL",
    "contentType": "BACKUP_STREAM_MIMETYPE",
    "streamType": "VOD",
    "customData": {
      "region": "LOCATION",
      "projectNumber": "PROJECT_NUMBER",
      "oAuthToken": "OAUTH_TOKEN",
      "networkCode": "NETWORK_CODE",
      "vodConfigId": "VOD_CONFIG_ID"
    }
  }
}

此自定义负载请求可以发送到接收器,以测试其余步骤。

创建自定义 CAF Web 接收器

创建自定义 Web 接收器,如 CAF SDK 自定义 Web 接收器指南中所述。

接收器的代码应如下所示:

<html>
<head>
  <script
      src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js">
  </script>
</head>
<body>
  <cast-media-player></cast-media-player>
  <script>
    const castContext = cast.framework.CastReceiverContext.getInstance()
    castContext.start();
  </script>
</body>
</html>

导入 IMA DAI SDK 并获取播放器管理器

在脚本加载 CAF 后,立即添加一个脚本标记,以将 IMA DAI SDK for CAF 导入到您的 Web 接收器。然后在后面的脚本标记中,将接收器上下文和播放器管理器存储为常量,然后再启动接收器。

<html>
<head>
  <script
      src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
  <script src="//imasdk.googleapis.com/js/sdkloader/cast_dai.js"></script>
</head>
<body>
  <cast-media-player></cast-media-player>
  <script>
    const castContext = cast.framework.CastReceiverContext.getInstance();
    const playerManager = castContext.getPlayerManager();

    castContext.start();
  </script>
</body>
</html>

初始化 IMA Stream Manager

初始化 IMA StreamManager。

<html>
<head>
  <script type="text/javascript"
      src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
  <script src="//imasdk.googleapis.com/js/sdkloader/cast_dai.js"></script>
</head>
<body>
  <cast-media-player></cast-media-player>
  <script>
    const castContext = cast.framework.CastReceiverContext.getInstance();
    const playerManager = castContext.getPlayerManager();
    const streamManager = new google.ima.cast.dai.api.StreamManager();

    castContext.start();
  </script>
</body>
</html>

创建 Stream Manager Load Interceptor

在将媒体项传递给 CAF 之前,请在 LOAD 消息拦截器中创建您的流请求。

    const castContext = cast.framework.CastReceiverContext.getInstance();
    const playerManager = castContext.getPlayerManager();
    const streamManager = new google.ima.cast.dai.api.StreamManager();

    /**
     * Creates a VOD stream request object for the Video Stitcher API.
     * @param {!LoadRequestData} castRequest The request object from the cast sender
     * @return {StreamRequest} an IMA stream request
     */
    const createStreamRequest = (castRequest) => { /* ... */};

    /**
     * Initates a DAI stream request for the final stream manifest.
     * @param {!LoadRequestData} castRequest The request object from the cast sender
     * @return {Promise<LoadRequestData>} a promise that resolves to an updated castRequest, containing the DAI stream manifest
     */
    const createDAICastRequest = (castRequest) => {
        return streamManager.requestStream(castRequest, createStreamRequest(castRequest))
          .then((castRequestWithStreamData) => {
            console.log('Successfully made DAI stream request.');
            return castRequestWithStreamData;
          })
          .catch((error) => {
            console.log('Failed to make DAI stream request.');
            // CAF will automatically fallback to the content URL
            // that it can read from the castRequest object.
            return castRequest;
          });
    };

    playerManager.setMessageInterceptor(
        cast.framework.messages.MessageType.LOAD, createDAICastRequest);

    castContext.start();

创建流式请求

完成 createStreamRequest 函数,以根据 CAF 加载请求创建 Video Stitcher API VOD 流请求。

    /**
     * Creates a VOD stream request object for the Video Stitcher API.
     * @param {!LoadRequestData} castRequest The request object from the cast sender
     * @return {StreamRequest} an IMA stream request
     */
    const createStreamRequest = (castRequest) => {
      const streamRequest = new google.ima.cast.dai.api.VideoStitcherVodStreamRequest();
      const customData = castRequest.media.customData;

      streamRequest.region = customData.region;
      streamRequest.projectNumber = customData.projectNumber;
      streamRequest.oAuthToken = customData.oAuthToken;
      streamRequest.networkCode = customData.networkCode;
      streamRequest.vodConfigId = customData.vodConfigId;
      streamRequest.videoStitcherSessionOptions = {};

      return streamRequest;
    };

(可选)添加直播会话选项

通过添加会话选项来自定义您的直播请求,以使用 VideoStitcherVodStreamRequest.videoStitcherSessionOptions 替换默认的 Cloud Video Stitcher API 配置。 如果您提供的选项无法识别,Cloud Video Stitcher API 将以 HTTP 400 错误作为响应。如需帮助,请参阅问题排查指南

例如,您可以使用以下代码段替换清单选项,该代码段会请求两个流清单,其中视频片段按从最低比特率到最高比特率的顺序排列。

...

// The following session options are examples. Use session options
// that are compatible with your video stream.
streamRequest.videoStitcherSessionOptions = {
  "manifestOptions": {
    "bitrateOrder": "ascending"
  }
};

streamManager.requestStream(streamRequest);