book_path: /distribute/other-docs/_book.yaml project_path: /distribute/other-docs/_project.yaml
本指南介绍了如何使用 Engage SDK 将“继续观看”功能集成到 Android TV 应用中。
准备工作
完成“入门指南”中的准备工作说明。
集成
创建实体
SDK 定义了不同的实体来代表每种内容类型。延续集群支持以下实体:
为这些实体指定特定于平台的 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 后,请检查验证应用,确认您的数据是否正在正确发布。您的延续集群应在应用的界面中显示为单独的一行。
- 在应用中测试以下操作:
- 登录。
- 在个人资料之间切换(如适用)。
- 开始播放视频,然后暂停视频,或返回首页。
- 在视频播放期间关闭应用。
- 从“继续观看”行中移除内容(如果支持)。
- 每次操作后,请确认您的应用已调用
publishContinuationClustersAPI,并且数据已在验证应用中正确显示。 对于正确实现的相关实体,验证应用会显示一个绿色的“一切正常”对勾标记。
图 1. 验证应用成功 验证应用会标记出所有存在问题的实体。
图 2. 验证应用错误 如需排查存在错误的实体,请使用电视遥控器选择并点击验证应用中的实体。系统会显示具体问题,并以红色突出显示以供您查看(请参阅下方示例)。
图 3. 验证应用错误详情