在 Chromecast 上使用 IMA DAI SDK

播放使用 Google Cloud Video Stitcher API 注册的直播

本指南演示了如何使用 IMA DAI SDK for CAF Web Receiver 来 请求和播放使用 Google Cloud Video Stitcher API注册的活动的直播, 并在播放期间插入广告插播时间点。

本指南扩展了完整服务 DAI 中的基本示例,增加了对使用 Google Cloud Video Stitcher API 注册的内容流的支持。

在继续之前,请确保您的流式传输格式受 CAF Web Receiver 支持。

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

背景

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

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

应用组件和架构

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

  • VideoStitcherLiveStreamRequest:一个用于定义向 Google 服务器发送的内容流请求的对象。该请求指定了 Cloud Video Stitcher API 的实例、直播配置 ID 和其他可选参数。
  • StreamManager:一个用于处理视频流与 IMA DAI SDK 之间通信的对象,例如触发跟踪 ping 并将内容流事件转发给发布商。

前提条件

您需要以下 IMA SDK 变量:

对于自定义 Cast Receiver,您需要以下内容:

  • 一个 Cast 开发者控制台账号,其中包含 许可名单中的测试设备。

  • 一个托管的 Web Receiver 应用,该应用已在您的 Cast 开发者控制台中注册,并且可以进行修改以 托管本指南提供的代码。

  • 一个配置为使用您的 Web Receiver 应用的发送应用。在本示例中,本指南使用 Cast Command and Control 工具作为发送器。

准备发送器以将内容流数据传递给 Receiver

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

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

CONTENT_ID

contentUrl 如果 DAI 内容流加载失败,则播放的可选备用推流网址。

BACKUP_STREAM_URL

contentType 如果 DAI 内容流加载失败,则播放的备用推流网址的可选 MIME 类型。

BACKUP_STREAM_MIMETYPE

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

LIVE

customData

customData 字段包含其他必需字段的键值对存储。在本例中,customData 包含您收集的 DAI 内容流数据。

字段 目录
liveConfigID LIVE_CONFIG_ID
region LOCATION
projectNumber PROJECT_NUMBER
oAuthToken OAUTH_TOKEN
networkCode NETWORK_CODE
customAssetKey CUSTOM_ASSET_KEY

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

Web

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

// 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.LIVE;
mediaInfo.customData = {
liveConfigID: "LIVE_CONFIG_ID",
region: "LOCATION",
projectNumber: "PROJECT_NUMBER",
oAuthToken: "OAUTH_TOKEN",
networkCode: "NETWORK_CODE",
customAssetKey: "CUSTOM_ASSET_KEY"
};

// 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 Receiver 发出加载 请求

JSONObject customData = new JSONObject()
  .put("liveConfigID", "LIVE_CONFIG_ID")
  .put("region", "LOCATION")
  .put("projectNumber", "PROJECT_NUMBER")
  .put("oAuthToken", "OAUTH_TOKEN")
  .put("networkCode", "NETWORK_CODE")
  .put("customAssetKey", "CUSTOM_ASSET_KEY");

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

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

iOS (Obj-C)

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

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

GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentID: @"CONTENT_ID"];
mediaInfoBuilder.contentURL = url;
mediaInfoBuilder.contentType = @"BACKUP_STREAM_MIMETYPE";
mediaInfoBuilder.streamType = GCKMediaStreamTypeLive;
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 Receiver 发出加载 请求

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

let customData = [
  "liveConfigID": "LIVE_CONFIG_ID",
  "region": "LOCATION",
  "projectNumber": "PROJECT_NUMBER",
  "oAuthToken": "OAUTH_TOKEN",
  "networkCode": "NETWORK_CODE",
  "customAssetKey": "CUSTOM_ASSET_KEY"
]

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentId: "CONTENT_ID")
mediaInfoBuilder.contentURL = mediaUrl
mediaInfoBuilder.contentType = "BACKUP_STREAM_MIMETYPE"
mediaInfoBuilder.streamType = GCKMediaStreamType.Live
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。然后,将文本区域中的 JSON 数据替换为此 JSON:

{
  "media": {
    "contentId": "CONTENT_ID",
    "contentUrl": "BACKUP_STREAM_URL",
    "contentType": "BACKUP_STREAM_MIMETYPE",
    "streamType": "LIVE",
    "customData": {
      "liveConfigID": "LIVE_CONFIG_ID",
      "region": "LOCATION",
      "projectNumber": "PROJECT_NUMBER",
      "oAuthToken": "OAUTH_TOKEN",
      "networkCode": "NETWORK_CODE",
      "customAssetKey": "CUSTOM_ASSET_KEY"
    }
  }
}

此自定义加载请求可以发送给 Receiver,以测试其余步骤。

创建自定义 CAF Web Receiver

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

Receiver 的代码应如下所示:

<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 并获取 Player Manager

在加载 CAF 的脚本之后,立即添加一个脚本标记,以将 IMA DAI SDK for CAF 导入到您的 Web Receiver。然后,在后面的脚本标记中,将 Receiver 上下文和 Player Manager 存储为常量,然后再启动 Receiver。

<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 Stream Manager。

<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 加载拦截器

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

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

    /**
     * Creates a livestream 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 直播请求。

    /**
     * Creates a livestream 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.VideoStitcherLiveStreamRequest();
      const customData = castRequest.media.customData;

      streamRequest.liveStreamEventId = customData.liveConfigID;
      streamRequest.region = customData.region;
      streamRequest.projectNumber = customData.projectNumber;
      streamRequest.oAuthToken = customData.oAuthToken;
      streamRequest.networkCode = customData.networkCode;
      streamRequest.customAssetKey = customData.customAssetKey;

      return streamRequest;
    };

(可选)添加流式传输会话选项

通过添加会话选项来自定义内容流请求,以替换默认的 Cloud Video Stitcher API 配置,使用 VideoStitcherLiveStreamRequest.videoStitcherSessionOptions。 如果您提供无法识别的选项,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);