在 Android TV 上集成“继续观看”功能

book_path: /distribute/other-docs/_book.yaml project_path: /distribute/other-docs/_project.yaml

本指南介绍了如何使用 Engage SDK 将“继续观看”功能集成到 Android TV 应用中。

准备工作

完成“入门指南”中的准备工作说明。

集成

创建实体

SDK 定义了不同的实体来代表每种内容类型。延续集群支持以下实体:

  1. MovieEntity
  2. TvEpisodeEntity
  3. LiveStreamingVideoEntity
  4. VideoClipEntity

为这些实体指定特定于平台的 URI 和海报图片。

此外,如果尚未为每个平台(例如 Android TV、Android 或 iOS)创建播放 URI,请立即创建。因此,当用户在每个平台上继续观看时,应用会使用目标播放 URI 来播放视频内容。

// Required. Set this when you want continue watching entities to show up on
// Google TV
val playbackUriTv = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_ANDROID_TV)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_tv"))
    .build()

// Required. Set this when you want continue watching entities to show up on
// Google TV Android app, Entertainment Space, Playstore Widget
val playbackUriAndroid = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_ANDROID_MOBILE)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_android"))
    .build()

// Optional. Set this when you want continue watching entities to show up on
// Google TV iOS app
val playbackUriIos = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_IOS)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_ios"))
    .build()

val platformSpecificPlaybackUris =
    Arrays.asList(playbackUriTv, playbackUriAndroid, playbackUriIos)

海报图片需要 URI 和像素尺寸(高度和宽度)。通过提供多张海报图片来面向不同的设备规格,但请验证所有图片是否都保持 16:9 的宽高比和至少 200 像素的高度,以便正确显示“继续观看”实体,尤其是在 Google 的娱乐空间内。高度小于 200 像素的图片可能不会显示。

val images = Arrays.asList(
    Image.Builder()
        .setImageUri(Uri.parse("http://www.example.com/entity_image1.png"))
        .setImageHeightInPixel(300)
        .setImageWidthInPixel(169)
        .build(),
    Image.Builder()
        .setImageUri(Uri.parse("http://www.example.com/entity_image2.png"))
        .setImageHeightInPixel(640)
        .setImageWidthInPixel(360)
        .build()
    // Consider adding other images for different form factors
)

MovieEntity

此示例展示了如何创建包含所有必需字段的 MovieEntity

val movieEntity = MovieEntity.Builder()
   .setWatchNextType(WatchNextType.TYPE_CONTINUE)
   .setName("Movie name")
   .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
   .addPosterImages(images)
   // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
   .setLastEngagementTimeMillis(1701388800000)
   // Suppose the duration is 2 hours, it is 72000000 in milliseconds
   .setDurationMills(72000000)
   // Suppose last playback offset is 1 hour, 36000000 in milliseconds
   .setLastPlayBackPositionTimeMillis(36000000)
   .build()

提供类型和内容分级等详细信息,可让 Google TV 以更动态的方式展示您的内容,并将其与合适的观看者联系起来。

val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val movieEntity = MovieEntity.Builder()
    ...
    .addGenres(genres)
    .addContentRatings(contentRatings)
    .build()

除非您指定较短的过期时间,否则实体会自动保持可用状态 60 天。只有在需要在此默认期限之前移除实体时,才需要设置自定义过期时间。

// Set the expiration time to be now plus 30 days in milliseconds
val expirationTime = DisplayTimeWindow.Builder()
    .setEndTimestampMillis(now().toMillis()+2592000000).build()
val movieEntity = MovieEntity.Builder()
    ...
    .addAvailabilityTimeWindow(expirationTime)
    .build()

TvEpisodeEntity

此示例展示了如何创建包含所有必需字段的 TvEpisodeEntity

val tvEpisodeEntity = TvEpisodeEntity.Builder()
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Episode name")
    .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMills(72000000) // 2 hours in milliseconds
    // 45 minutes and 15 seconds in milliseconds is 2715000
    .setLastPlayBackPositionTimeMillis(2715000)
    .setEpisodeNumber("2")
    .setSeasonNumber("1")
    .setShowTitle("Title of the show")
    .build()

剧集编号字符串(例如 "2")和季编号字符串(例如 "1")在显示在“继续观看”卡片上之前,会扩展为适当的形式。请注意,它们应该是数字字符串,不要使用“e2”“第 2 集”“s1”或“第 1 季”。

如果某个电视节目只有一季,请将季数设置为 1。

为了最大限度地提高观看者在 Google TV 上找到您的内容的机会,请考虑提供其他数据,例如流派、内容分级和播放时间窗口,因为这些详细信息可以增强显示效果和过滤选项。

val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val tvEpisodeEntity = TvEpisodeEntity.Builder()
    ...
    .addGenres(genres)
    .addContentRatings(contentRatings)
    .setSeasonTitle("Season Title")
    .setShowTitle("Show Title")
    .build()

VideoClipEntity

以下示例展示了如何创建包含所有必需字段的 VideoClipEntity

VideoClipEntity 表示用户生成的剪辑,例如 YouTube 视频。

val videoClipEntity = VideoClipEntity.Builder()
    .setPlaybackUri(Uri.parse("https://www.example.com/uri_for_current_platform"))
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Video clip name")
    .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMills(600000) //10 minutes in milliseconds
    .setLastPlayBackPositionTimeMillis(300000) //5 minutes in milliseconds
    .addContentRating(contentRating)
    .build()

您可以选择性地设置创建者、创建者图片、创建时间(以毫秒为单位)或有效时间窗口。

LiveStreamingVideoEntity

以下示例展示了如何创建包含所有必需字段的 LiveStreamingVideoEntity

val liveStreamingVideoEntity = LiveStreamingVideoEntity.Builder()
    .setPlaybackUri(Uri.parse("https://www.example.com/uri_for_current_platform"))
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Live streaming name")
    .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMills(72000000) //2 hours in milliseconds
    .setLastPlayBackPositionTimeMillis(36000000) //1 hour in milliseconds
    .addContentRating(contentRating)
    .build()

您可以选择性地为直播实体设置开始时间、广播者、广播者图标或播放时间窗口。

如需详细了解属性和要求,请参阅 API 参考文档

提供接续集群数据

AppEngagePublishClient 负责发布接续集群。 您可以使用 publishContinuationCluste 方法发布 ContinuationCluster 对象。

请务必按照入门指南中的说明初始化客户端并检查服务可用性。

client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .addEntity(movieEntity1)
                .addEntity(movieEntity2)
                .addEntity(tvEpisodeEntity1)
                .addEntity(tvEpisodeEntity2)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

当服务收到请求时,系统会在一项事务中执行以下操作:

  • 系统会移除开发者合作伙伴的现有 ContinuationCluster 数据。
  • 系统会解析请求中的数据,并将其存储在经过更新的 ContinuationCluster 中。

如果发生错误,系统将拒绝整个请求,并保留现有状态。

发布 API 是更新/插入 API;它会替换现有内容。如果您需要更新延续集群中的特定实体,则需要重新发布所有实体。

继续观看集群数据应仅针对成人账号提供。仅当账号个人资料属于成人时发布。

跨设备同步

SyncAcrossDevices 标志用于控制用户的 ContinuationCluster 数据是否在电视、手机、平板电脑等设备之间同步。默认情况下,跨设备同步处于停用状态。

值:

  • true:续播集群数据会在用户的所有设备之间共享,以实现无缝观看体验。我们强烈建议您选择此选项,以获得最佳跨设备体验。
  • false:延续集群数据仅限于当前设备。

媒体应用必须提供清晰的设置来启用或停用跨设备同步。向用户说明好处,然后存储用户偏好设置一次,并在 publishContinuationCluster 中相应地应用该设置。

// Example to allow cross device syncing.
client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

如需充分利用我们的跨设备功能,请验证应用是否已征得用户同意,并将 SyncAcrossDevices 启用为 true。这样一来,内容便可在设备之间无缝同步,从而带来更出色的用户体验并提高用户互动度。例如,一位合作伙伴在实施此功能后,“继续观看”点击次数增加了 40%,因为其内容可在多种设备上显示。

删除视频发现数据

如需在标准 60 天保留期限之前从 Google TV 服务器手动删除用户的数据,请使用 deleteClusters 方法。收到请求后,服务将删除相应账号个人资料或整个账号的所有现有视频发现数据。

DeleteReason 枚举定义了数据删除的原因。以下代码会在用户退出登录时移除“继续观看”数据。


// If the user logs out from your media app, you must make the following call
// to remove continue watching data from the current google TV device,
// otherwise, the continue watching data will persist on the current
// google TV device until 60 days later.
client.deleteClusters(
    DeleteClustersRequest.Builder()
        .setAccountProfile(AccountProfile())
        .setReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
        .setSyncAcrossDevices(true)
        .build()
)

测试

使用验证应用验证 Engage SDK 集成是否正常运行。

调用发布 API 后,请检查验证应用,确认您的数据是否正在正确发布。您的延续集群应在应用的界面中显示为单独的一行。

  • 在应用中测试以下操作:
    • 登录。
    • 在个人资料之间切换(如适用)。
    • 开始播放视频,然后暂停视频,或返回首页。
    • 在视频播放期间关闭应用。
    • 从“继续观看”行中移除内容(如果支持)。
  • 每次操作后,请确认您的应用已调用 publishContinuationClusters API,并且数据已在验证应用中正确显示。
  • 对于正确实现的相关实体,验证应用会显示一个绿色的“一切正常”对勾标记。

    验证应用成功屏幕截图
    图 1. 验证应用成功
  • 验证应用会标记出所有存在问题的实体。

    验证应用错误屏幕截图
    图 2. 验证应用错误
  • 如需排查存在错误的实体,请使用电视遥控器选择并点击验证应用中的实体。系统会显示具体问题,并以红色突出显示以供您查看(请参阅下方示例)。

    验证应用错误详情
    图 3. 验证应用错误详情