“继续观看”功能利用接续集群,在一个界面分组中显示来自多个应用的未看完视频以及同一电视剧季的下一集。您可以在此接续集群中展示其实体。按照本指南了解如何使用 Engage SDK 通过“继续观看”体验提升用户互动度。
第 1 步:准备工作
在开始之前,请完成以下步骤:
确保您的应用以 API 级别 19 或更高级别为目标平台,以便进行此集成
将
com.google.android.engage
库添加到您的应用中:集成时需要使用单独的 SDK:一个适用于移动应用,另一个适用于 TV 应用。
移动设备
dependencies { implementation 'com.google.android.engage:engage-core:1.5.5 }
TV
dependencies { implementation 'com.google.android.engage:engage-tv:1.0.2 }
在
AndroidManifest.xml
文件中将 Engage 服务环境设为生产环境。移动设备
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION"> </meta-data>
TV
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION"> </meta-data>
为电视 APK 添加了
WRITE_EPG_DATA
权限<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
使用后台服务(例如
androidx.work
)进行安排,确保可靠地发布内容。为了提供流畅的观看体验,请在以下事件发生时发布继续观看数据:
- 首次登录:在用户首次登录时,发布其数据可确保其观看记录立即可用。
- 创建或切换个人资料(多资料应用):如果您的应用支持多资料,请在用户创建或切换资料时发布数据。这样可以确保每位用户都能获得个性化体验。
- 视频播放中断:为帮助用户从上次中断的位置继续播放,请在用户暂停或停止视频播放时,或在应用在播放期间退出时发布数据。
- “继续观看”抽屉更新(如果受支持):当用户从“继续观看”抽屉中移除内容时,请通过发布更新后的数据来反映该更改。这样可确保该任务栏始终提供相关且个性化的功能。
- 视频完整播放:
- 对于电影,请从“继续观看”栏中移除已看完的电影。 如果电影是系列电影的一部分,请添加下一部电影,以吸引用户保持互动。
- 对于剧集,请移除已看完的剧集,并添加该剧集的下一集(如果有),以鼓励用户继续观看。
集成
AccountProfile
如需在 Google TV 上获享个性化的“继续观看”体验,请提供账号和个人资料信息。使用 AccountProfile 提供:
账号 ID:表示用户在应用中的账号的唯一标识符。这可以是实际的账号 ID,也可以是经过适当混淆处理的版本。
个人资料 ID(可选):如果您的应用支持单个账号中的多个个人资料,请为特定用户个人资料提供唯一标识符(真实或经过混淆处理的标识符)。
// If your app only supports account
val accountProfile = AccountProfile.Builder()
.setAccountId("your_users_account_id")
.build()
// If your app supports both account and profile
val accountProfile = AccountProfile.Builder()
.setAccountId("your_users_account_id")
.setProfileId("your_users_profile_id")
.build()
创建实体
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 像素的图片可能无法显示。
Image image1 = new Image.Builder()
.setImageUri(Uri.parse("http://www.example.com/entity_image1.png");)
.setImageHeightInPixel(300)
.setImageWidthInPixel(169)
.build()
Image image2 = new Image.Builder()
.setImageUri(Uri.parse("http://www.example.com/entity_image2.png");)
.setImageHeightInPixel(640)
.setImageWidthInPixel(360)
.build()
// And other images for different form factors.
val images = Arrays.asList(image1, image2)
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 = new 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 季,请将剧季号设置为 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
负责发布接续集群。您可以使用 publishContinuationCluster()
方法发布 ContinuationCluster
对象。
首先,您应使用 isServiceAvailable() 检查服务是否可供集成。
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;它会替换现有内容。如果您需要更新 ContinuationCluster 中的特定实体,则需要重新发布所有实体。
仅应为成人账号提供 ContinuationCluster 数据。仅当 AccountProfile 属于成人时发布。
跨设备同步
SyncAcrossDevices 标志
此标志用于控制用户的 ContinuationCluster 数据是否会在其设备(电视、手机、平板电脑等)之间同步。默认为 false,这意味着跨设备同步默认处于停用状态。
值:
- true:系统会在用户的所有设备上共享 ContinuationCluster 数据,以提供流畅的观看体验。我们强烈建议您使用此选项,以获得最佳跨设备体验。
- false:ContinuationCluster 数据仅限于当前设备。
征求用户意见:
媒体应用必须提供用于启用/停用跨设备同步的明确设置。向用户说明好处,并存储用户的偏好设置一次,然后在 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 服务器中删除用户的数据,请使用 client.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(
new DeleteClustersRequest.Builder()
.setAccountProfile(AccountProfile())
.setReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
.setSyncAcrossDevices(true)
.build()
)
测试
使用验证应用确保您的 Engage SDK 集成正常运行。此 Android 应用提供了一些工具,可帮助您验证数据并确认系统是否正确处理了广播 intent。
调用发布 API 后,请检查验证应用,确认您的数据是否已正确发布。您的接续集群应显示为应用界面中的单独行。
- 确保在应用的 Android 清单文件中未将“感兴趣的服务标志”设置为正式版。
- 安装并打开 Engage Verify 应用
- 如果
isServiceAvailable
为false
,请点击“切换”按钮以启用。 - 输入应用的软件包名称,以便在开始发布后自动查看已发布的数据。
- 在您的应用中测试以下操作:
- 登录。
- 在个人资料之间切换(如果适用)。
- 开始播放视频,然后暂停播放,或返回首页。
- 在视频播放期间关闭应用。
- 从“继续观看”行中移除内容(如果支持)。
- 执行每项操作后,请确认您的应用调用了 publishContinuationClusters API,并且数据在验证应用中正确显示。
对于正确实现的实体,验证应用会显示绿色的“一切顺利”对勾标记。
图 1. 验证应用成功 验证应用会标记所有有问题的实体。
图 2. 验证应用错误 如需排查存在错误的实体,请使用电视遥控器在验证应用中选择并点击相应实体。系统会显示具体问题并以红色突出显示,以供您查看(请参阅以下示例)。
图 3. 验证应用错误详情