用于视频推荐的 Engage SDK

本指南包含面向开发者的说明,介绍了如何使用 Engage SDK 集成推荐的视频内容,以便在 Google 途径(例如电视、手机和平板电脑)中填充推荐体验。

推荐功能利用推荐集群在一个界面分组中显示来自多个应用的电影和电视节目。每个开发者合作伙伴可以在每个推荐集群中广播最多 25 个实体,并且每个请求最多可以包含 7 个推荐集群。

准备工作

在开始之前,请完成以下步骤。 1. 验证您的应用是否以 API 级别 19 或更高版本为目标平台,以便进行此集成。

  1. com.google.android.engage 库添加到您的应用中。

    在集成过程中,您需要使用不同的 SDK:一个用于移动应用,另一个用于电视应用。

    移动设备版

    
      dependencies {
        implementation 'com.google.android.engage:engage-core:1.5.9
      }
    

    适用于电视

    
      dependencies {
        implementation 'com.google.android.engage:engage-tv:1.0.5
      }
    
  2. AndroidManifest.xml 文件中将 Engage 服务环境设置为生产环境。

    对于移动版 APK

    
    <meta-data
          android:name="com.google.android.engage.service.ENV"
          android:value="PRODUCTION">
    </meta-data>
    

    适用于电视的 APK

    
    <meta-data
        android:name="com.google.android.engage.service.ENV"
        android:value="PRODUCTION">
    </meta-data>
    
  3. 在前台服务上执行发布。

  4. 每天最多发布一次“建议”数据,触发条件为以下任一情况:

    1. 用户当天的首次登录。(
    2. 当用户开始与应用互动时。

集成

AppEngagePublishClient 发布推荐集群。使用 publishRecommendationClusters 方法发布建议对象。

使用 isServiceAvailable()2 检查服务是否可用于集成。

val client = AppEngagePublishClient(context)

client.isServiceAvailable().addOnCompleteListener { task ->
  if (task.isSuccessful) {
  // Handle IPC call success
    if(task.result) {
      // Service is available on the device, proceed with content publish
      // calls.
      client.publishRecommendationClusters(recommendationRequest)
    } else {
      // Service is not available
    }
  } else {
    // The IPC call itself fails, proceed with error handling logic here,
    // such as retry.
  }
}

推荐集群和发布请求

聚类是实体的逻辑分组。以下代码示例说明了如何根据您的偏好构建集群,以及如何为所有集群创建发布请求。

// cluster for popular movies
val recommendationCluster1 = RecommendationCluster
  .Builder()
  .addEntity(movie1)
  .addEntity(movie2)
  .addEntity(movie3)
  .addEntity(movie4)
  .addEntity(tvShow)
  // This cluster is meant to be used as an individual provider row
  .setRecommendationClusterType(TYPE_PROVIDER_ROW)
  .setTitle("Popular Movies")
  .build()

// cluster for live TV programs
val recommendationCluster2 = RecommendationCluster
  .Builder()
  .addEntity(liveTvProgramEntity1)
  .addEntity(liveTvProgramEntity2)
  .addEntity(liveTvProgramEntity3)
  .addEntity(liveTvProgramEntity4)
  .addEntity(liveTvProgramEntity5)
 // This cluster is meant to be used as an individual provider row
  .setRecommendationClusterType(TYPE_PROVIDER_ROW)
  .setTitle("Popular Live TV Programs")
  .build()

// creating a publishing request
val recommendationRequest = PublishRecommendationClustersRequest
  .Builder()
  .setSyncAcrossDevices(true)
  .setAccountProfile(accountProfile)
  .addRecommendationCluster(recommendationCluster1)
  .addRecommendationCluster(recommendationCluster2)
  .build()

创建账号个人资料

如需在 Google TV 上获得个性化体验,请提供账号和个人资料信息。使用 AccountProfile 提供:

  1. 账号 ID:用于表示用户账号在应用中的唯一标识符。可以是实际账号 ID,也可以是经过适当混淆的版本。
  2. 个人资料 ID(可选):如果您的应用支持在单个账号中使用多个个人资料,请提供特定用户个人资料的唯一身份标识。
  3. 语言区域(可选):您可以选择性地提供用户的首选语言。 如果您在 RecommendationRequest 中发送 MediaActionFeedEntity,此字段会很有用。
// If app only supports account
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .build();

// If app supports both account and profile
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .setProfileId("profile_id")
  .build();

// set Locale
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .setProfileId("profile_id")
  .setLocale("en-US")
  .build();

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

  • 系统会移除开发者合作伙伴的现有 RecommendationsCluster 数据。
  • 系统会解析请求中的数据,并将其存储在经过更新的 RecommendationsCluster 中。如果发生错误,系统将拒绝整个请求,并保留现有状态。

跨设备同步

SyncAcrossDevices 标志用于控制是否与 Google TV 分享用户的推荐聚类数据,以及是否在用户的电视、手机、平板电脑等设备上提供这些数据。为了使建议生效,必须将其设置为 true。

媒体应用必须提供清晰的设置来启用或停用跨设备同步。向用户说明好处,存储用户偏好设置一次,并在 publishRecommendations 请求中相应地应用该偏好设置。如需充分利用跨设备功能,请验证应用是否已征得用户同意,并将 SyncAcrossDevices 设置为 true

删除视频发现数据

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

DeleteReason 枚举定义了数据删除的原因。以下代码会在用户退出时移除推荐内容。

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

// If the user revokes the consent to share data with Google TV,
// you must make the following call to remove recommendations data from
// all current google TV devices. Otherwise, the recommendations data persists
// until 60 days later.
client.deleteClusters(
  new DeleteClustersRequest.Builder()
    .setAccountProfile(AccountProfile())
    .setReason(DeleteReason.DELETE_REASON_LOSS_OF_CONSENT)
    .build()
)

创建实体

SDK 定义了不同的实体来代表每种内容类型。“推荐”聚类支持以下实体:

  1. MediaActionFeedEntity
  2. MovieEntity
  3. TvShowEntity
  4. LiveTvChannelEntity
  5. LiveTvProgramEntity

说明

为每个实体提供简短说明;当用户将鼠标悬停在实体上时,系统会显示此说明,从而为用户提供更多详细信息。

特定于平台的播放 URI

为每个支持的平台(Android TV、Android 或 iOS)创建播放 URI。 这样,系统就可以选择相应的 URI,以便在相应平台上播放视频。

如果所有平台的播放 URI 都相同,则为每个平台重复使用该 URI。

// Required. Set this when you want recommended 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()

// Optional. Set this when you want recommended entities to show up on
// Google TV Android app
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 recommended 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)

// Provide appropriate rating for the system.
val contentRating = new RatingSystem
  .Builder()
  .setAgencyName("MPAA")
  .setRating("PG-13")
  .build()

海报图片

海报图片需要 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)

建议原因

(可选)提供推荐理由,Google TV 可使用该理由来构建向用户推荐特定电影或电视节目的原因。

//Allows us to construct reason: "Because it is top 10 on your Channel"
val topOnPartner = RecommendationReasonTopOnPartner
  .Builder()
  .setNum(10) //any valid integer value
  .build()

//Allows us to construct reason: "Because it is popular on your Channel"
val popularOnPartner = RecommendationReasonPopularOnPartner
  .Builder()
  .build()

//Allows us to construct reason: "New to your channel, or Just added"
val newOnPartner = RecommendationReasonNewOnPartner
  .Builder()
  .build()

//Allows us to construct reason: "Because you watched Star Wars"
val watchedSimilarTitles = RecommendationReasonWatchedSimilarTitles
  .addSimilarWatchedTitleName("Movie or TV Show Title")
  .addSimilarWatchedTitleName("Movie or TV Show Title")
  .Builder()
  .build()

//Allows us to construct reason: "Recommended for you by ChannelName"
val recommendedForUser = RecommendationReasonRecommendedForUser
  .Builder()
  .build()

val watchAgain = RecommendationReasonWatchAgain
  .Builder()
  .build()

val fromUserWatchList = RecommendationReasonFromUserWatchlist
  .Builder()
  .build()

val userLikedOnPartner = RecommendationReasonUserLikedOnPartner
  .Builder()
  .setTitleName("Movie or TV Show Title")
  .build()

val generic = RecommendationReasonGeneric.Builder().build()

显示时间窗口

如果实体仅在有限的时间内可用,请设置自定义到期时间。如果没有明确的过期时间,实体将在 60 天后自动过期并被清除。因此,只有在需要提前使实体过期时才设置过期时间。指定多个此类空闲时段。

val window1 = DisplayTimeWindow
  .Builder()
  .setStartTimeStampMillis(now()+ 1.days.toMillis())
  .setEndTimeStampMillis(now()+ 30.days.toMillis())

val window2 = DisplayTimeWindow
  .Builder()
  .setEndTimeStampMillis(now()+ 30.days.toMillis())

val availabilityTimeWindows: List<DisplayTimeWindow> = listof(window1,window2)

DataFeedElementId

如果您已将媒体目录或媒体操作 Feed 与 Google TV 集成,则无需为电影或电视节目创建单独的实体,而是可以创建一个 MediaActionFeed 实体,其中包含必需的字段 DataFeedElementId。此 ID 必须是唯一的,并且必须与媒体操作 Feed 中的 ID 一致,因为它可以帮助识别已提取的 Feed 内容并执行媒体内容查找。

val id = "dataFeedEleemntId"

MovieEntity

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


val movieEntity = MovieEntity.Builder()
  .setName("Movie name")
  .setDescription("A sentence describing movie.")
  .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
  .addPosterImages(images)
  // Suppose the duration is 2 hours, it is 72000000 in milliseconds
  .setDurationMills(72000000)
  .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);
//Suppose release date is 11-02-2025
val releaseDate  = 1739233800000L
val movieEntity = MovieEntity.Builder()
  ...
  .addGenres(genres)
  .setReleaseDateEpochMillis(releaseDate)
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addAllAvailabilityTimeWindows(availabilityTimeWindows)
  .build()

TvShowEntity

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

val tvShowEntity = TvShowEntity.Builder()
  .setName("Show title")
  .setDescription("A sentence describing TV Show.")
  .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
  .addPosterImages(images)
  .build();

您可以选择性地提供其他数据,例如流派、内容分级、推荐理由、优惠价格、季数或播放时间窗口,Google TV 可能会使用这些数据来增强显示效果或用于过滤。

val genres = Arrays.asList("Action", "Science fiction");
val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build();
val price = Price.Builder()
  .setCurrentPrice("$14.99")
  .setStrikethroughPrice("$16.99")
  .build();
val contentRatings = Arrays.asList(rating1);
val seasonCount = 5;
val tvShowEntity = TvShowEntity.Builder()
  ...
  .addGenres(genres)
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addAllAvailabilityTimeWindows(availabilityTimeWindows)
  .setSeasonCount(seasonCount)
  .setPrice(price)
  .build()

MediaActionFeedEntity

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


val mediaActionFeedEntity = MediaActionFeedEntity.Builder()
  .setDataFeedElementId(id)
  .build()

您可以选择性地提供其他数据,例如说明、推荐理由和展示时间窗口,Google TV 可能会使用这些数据来增强展示效果或进行过滤。

val mediaActionFeedEntity = MediaActionFeedEntity.Builder()
  .setName("Movie name or TV Show name")
  .setDescription("A sentence describing an entity")
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addPosterImages(images)
  .build()

LiveTvChannelEntity

表示直播电视频道。以下示例展示了如何创建包含所有必需字段的 LiveTvChannelEntity

val liveTvChannelEntity = LiveTvChannelEntity.Builder()
  .setName("Channel Name")
  // ID of the live TV channel
  .setEntityId("https://www.example.com/channel/12345")
  .setDescription("A sentence describing this live TV channel.")
  // channel playback uri must contain at least PlatformType.TYPE_ANDROID_TV
  .addPlatformSpecificPlaybackUri(channelPlaybackUris)
  .addLogoImage(logoImage)
  .build()

可以选择性地提供其他数据,例如内容分级或推荐原因。

val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build()
val contentRatings = Arrays.asList(rating1)

val liveTvChannelEntity = LiveTvChannelEntity.Builder()
  ...
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner)
  .build()

LiveTvProgramEntity

表示正在直播或计划在直播电视频道上播出的直播电视节目卡片。以下示例展示了如何创建包含所有必需字段的 LiveTvProgramEntity

val liveTvProgramEntity = LiveTvProgramEntity.Builder()
  // First set the channel information
  .setChannelName("Channel Name")
  .setChanelId("https://www.example.com/channel/12345")
  // channel playback uri must contain at least PlatformType.TYPE_ANDROID_TV
  .addPlatformSpecificPlaybackUri(channelPlaybackUris)
  .setChannelLogoImage(channelLogoImage)
  // Then set the program or card specific information.
  .setName("Program Name")
  .setEntityId("https://www.example.com/schedule/123")
  .setDescription("Program Desccription")
  .addAvailabilityTimeWindow(
      DisplayTimeWindow.Builder()
        .setStartTimestampMillis(1756713600000L)// 2025-09-01T07:30:00+0000
        .setEndTimestampMillis(1756715400000L))// 2025-09-01T08:00:00+0000
  .addPosterImage(programImage)
  .build()

还可以选择提供其他数据,例如内容分级、流派或推荐原因。

val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build()
val contentRatings = Arrays.asList(rating1)
val genres = Arrays.asList("Action", "Science fiction")

val liveTvProgramEntity = LiveTvProgramEntity.Builder()
  ...
  .addContentRatings(contentRatings)
  .addGenres(genres)
  .setRecommendationReason(topOnPartner)
  .build()

通过实施这些步骤,开发者可以成功将视频内容推荐功能集成到 Google TV 中,从而提高用户发现度和互动度,并为用户在所有设备上提供一致且个性化的观看体验。