提升在 Android TV 上通过“接下来观看”频道观看电影/电视剧集的互动度

1. 简介

接下来观看

“接下来观看”频道行显示在 Android TV 主屏幕上,其中的视频可供用户接下来观看。“接下来观看”行可能称为“接下来播放”或“继续观看”,具体取决于相应的系统版本。

b0ca4ea7c72ba8f6.png

此频道由系统创建和维护。此频道中的每一项称为一个节目。您的应用可以向“接下来观看”频道添加/更新节目或从中移除节目,其中包括用户中途停止观看的内容,以及用户互动过的内容(例如连续剧的下一集或节目的下一季)等。

概念

“接下来观看”频道为您的应用提供了一种方式,有助于提升与用户的再互动度。

您可以将用户已经互动过的内容添加/更新至“接下来观看”频道或将其从中移除。此类内容可以是未看完的视频或推荐观看的下一集/连续剧等。

您还可以让应用在用户观看完相应剧集后移除剧集,并添加新一季连续剧的下一集。

“接下来观看”频道有以下四种使用场景:

  • 继续观看用户尚未看完的视频。
  • 推荐下一个可观看的视频。例如,如果用户看完了第 1 集,那么您可以推荐第 2 集。
  • 显示用户当前正在观看的连续剧的剧集。
  • 维护用户添加的感兴趣视频的观看列表

此 Codelab 展示了如何在用户暂停观看某个视频时将该视频添加到“接下来观看”频道中。此外,还介绍了如何在视频播放完成后将其从“接下来观看”频道中移除,以及如何添加下一集(如有)。

此 Codelab 不包括“接下来观看”频道在发布的剧集和观看列表中的使用场景。

通过“接下来观看”频道观看电影和电视剧集

“接下来观看”频道是 Android TV 主屏幕上的一项重要功能,有助于用户及时了解尚未看完的电影和电视节目。对于观看电视剧集的用户来说,这一功能尤为重要,因为一部电视连续剧通常有很多集,用户可能需要分多次观看,而借助该功能,用户每次都能从上次没看完的地方继续观看。

想象一下用户回到家,坐在电视前思索要观看什么内容。通过“接下来观看”频道,您的应用可让用户直接从主屏幕继续浏览上次没看完的电视剧集。这样不仅可以提高用户再互动度,还能让您的应用和用户都从中受益。

此 Codelab 将向您介绍电视参考应用,展示如何处理“接下来观看”频道的不同使用场景,并解读 Google 关于“接下来观看”功能的质量指南。此 Codelab 重点介绍了如何处理与电视剧集相关的事宜,但类似的规则同样也适用于电影。

适用范围

此 Codelab 中的代码适用于 Android TV 设备,包括支持 Google TV 体验的设备。

构建内容

在此 Codelab 中,您将向“接下来观看”频道添加、更新电影/电视剧集或将其从中移除。您的应用将:

  • 实现用于处理电影的不同使用场景
  • 实现用于处理电视剧集的不同使用场景

学习内容

  • Google 关于“接下来观看”的质量指南

所需条件

  • Android 应用开发基础知识
  • Android Studio 4.1+,您可以点击此处下载

2. 准备工作

克隆初始项目

您可以从 GitHub 代码库下载源代码:

git clone https://github.com/android/codelab-watchnext-for-movie-tv-episodes.git

您也可以直接通过以下链接下载。

打开 Android Studio,从菜单栏中依次点击 File > Open,或者从欢迎界面上点击 Open an Existing Android Studio Project,然后选择最近克隆的文件夹。

8271a7b581390845.png

了解初始项目

34641bc2c9f71f0f.png

该项目中有四个步骤。在每个步骤中,您都将根据适用部分中的说明添加代码。完成一个部分后,您可以将代码与 step_x_completed 中的代码进行比较。

为简单起见,我们已添加了一些关于视频列表和 ExoPlayer 的基本代码,可用于观看应用内的内容。此举将创建电视应用的基本框架,这不属于此 Codelab 的范围。

我们的目标是学会向“接下来观看”频道添加/更新或从中移除尚未观看和已观看的视频,以及提升应用的再互动度。

下面是该应用中的主要组件:

  • FileVideoRepository 是用于加载和查询视频元数据的类。
  • PlaybackFragment 是视频播放 fragment。
  • WatchNextPlaybackStateListener 是播放状态更改监听器;它会监听播放事件并触发相关操作。
  • WatchNextWorker 是负责更新“接下来观看”频道的 worker。
  • WatchNextHelper 是一个辅助程序类,可简化“接下来观看”频道的使用。

此 Codelab 使用 res/raw/api.json 中的媒体数据来填充“接下来观看”条目。

运行初始项目

运行 step_1。如果您遇到问题,请参阅关于使用入门的文档

  1. 连接 Android TV 或启动模拟器。
  1. 选择 step_1 配置,选择您的 Android 设备,然后按菜单栏中的 run 按钮。249ea91a556fbd61.png
  2. 您应该会看到一个简单的电视应用概览,其中包含四个视频集合,与以下屏幕截图类似。
  3. 浏览应用的主屏幕,熟悉电视参考应用。有以下四种视频类别:
  4. 超级剪辑
  5. 其他剪辑
  6. 《贝弗利山人》:两季中的一些剧集;
  7. 查理·卓别林系列电影。
  8. 点击《贝弗利山人》类别中的一集电视剧观看。此 Codelab 将告诉您如何为这些电视剧集添加“接下来观看”节目

d7b51de54f4a1199.png

您学到的内容

在此简介中,您学习了以下内容:

  • 此 Codelab 中使用的代码结构和主要类。
  • 如何设置和运行示例应用。

后续操作

“接下来观看”质量指南

3. 了解“接下来观看”质量指南

为了提升主屏幕体验,所有向“接下来观看”频道中添加内容的应用都需要保持一致的行为方式。

具体而言,在某些情况下,开发者需要为电视剧集提供支持。Google 总结了“接下来观看”必须遵循的一系列质量指南,以确保“接下来观看”频道的质量。

如何构建您的“接下来观看”功能以满足 Google 的质量标准

Google 在评估“接下来观看”功能时,将验证以下几点:

  1. 节目暂停/停止时,将被添加到“接下来观看”频道中
  2. 应用能够通过“接下来观看”条目继续播放
  3. 应用及时更新“接下来观看”频道中的播放位置
  4. 播放完成后,节目会从“接下来观看”频道中移除
  5. 应用会在当前剧集播放完毕时添加下一集
  6. 应用不会将用户未互动过的内容添加至“接下来观看”频道
  7. 将所有未看完的内容推送到“接下来观看”频道
  8. 应用会设置正确且完整的元数据,例如剧季/剧集编号
  9. 应用不会针对同一部电视连续剧添加多个剧集

以下是各项质量要求的说明

  1. 节目暂停/停止时,将被添加到“接下来观看”频道中。
  • 播放完毕时,应用应向“接下来观看”行添加传统电影和电视节目
  1. 应用能够通过“接下来观看”条目继续播放。
  • 添加到“接下来观看”频道的内容会从上次播放位置恢复播放;视频应该在内容加载完毕后立即开始播放
  1. 应用会及时更新“接下来观看”中的播放位置。
  • 应用需要跟踪播放进度,并在用户停止播放视频后将“接下来观看”节目更新到最新的播放位置
  1. 播放完成后,节目会从“接下来观看”频道中移除。
  • 应用应行为得当,并且必须进行自我清理。“接下来观看”行是面向所有应用的共享行,我们希望确保此行中的内容准确无误,以便赢得用户的信任
  1. 应用会在当前剧集播放完毕时添加下一集。
  • 当用户观看电视连续剧时,该应用应通过在“接下来观看”行中添加后续剧集,来方便用户继续观看
  1. 应用不会将用户未互动过的内容添加至“接下来观看”频道。
  • 根据“接下来观看”指南,应用应仅在用户“开始”观看时,才向“接下来观看”频道添加电影或电视剧集
  • 不建议向“接下来观看”频道添加预告片和短视频剪辑,因为在此类内容中,用户进行再互动的几率很低
  1. 将所有未看完的内容推送到“接下来观看”频道。
  • 提供方不应人为限制其向“接下来观看”频道推送的卡片数量。如果用户有一段未看完的内容,提供方应将该内容推送到“接下来观看”行
  1. 应用会设置正确且完整的元数据。
  • 确保与剧集关联的元数据正确无误
  • 剧集编号、剧季编号和名称必须准确无误
  • 进度条必须与实际观看数量成比例
  • 剧集或连续剧图片应显示在图块中
  1. 应用应避免为同一部电视连续剧添加多个剧集。
  • 针对每部电视剧集,应用应保留最多一个“接下来观看”条目;如果用户在观看两集不同的剧集时都只看了一半,“接下来观看”频道应仅显示最近观看的剧集

这些质量要求将有助于您的应用提供出色的“接下来观看”体验。

好的,让我们牢记这些质量指南,并开始构建“接下来观看”功能。

您学到的内容

在此部分,您学习了以下内容:

  • “接下来观看”质量要求

后续操作

向“接下来观看”频道添加未看完的剧集

4. 向“接下来观看”频道添加未看完的内容

我们将从基本功能开始:向“接下来观看”频道添加未看完的剧集。

此 Codelab 将引导您创建 WatchNextProgram,并填写剧集的正确元数据,例如剧集编号、剧季编号和视频类型等。“接下来观看”条目可更新,旨在确保它能够与用户的最新播放位置保持同步,使用户能够通过点击节目继续播放。

本部分将涵盖以下内容:

  • 节目暂停/停止时,将被添加到“接下来观看”频道中。
  • 应用能够通过“接下来观看”条目继续播放。
  • 应用会及时更新“接下来观看”中的播放位置。
  • 应用会设置正确且完整的元数据。

添加未看完的视频 - 电影和剧集之间的区别

将电影和剧集添加到“接下来观看”频道的过程非常相似。唯一的区别是电影和剧集的元数据不同。

例如,对于剧集,我们在 WatchNextProgram.Builder 中介绍了特定的元数据方法,例如 setEpisodeNumber, setSeasonNumber(), setSeasonTitle()setEpisodeTitle()

向“接下来观看”频道添加未看完的视频(电影/剧集)

如需向“接下来观看”频道添加未看完的视频,开发者可以使用 WatchNextProgram.Builder 来构建 WatchNextProgram 实例,然后调用 PreviewChannelHelper.publishWatchNextProgram 以将其发布到“接下来观看”频道。

首先,创建 WatchNextProgram 的构建器实例,并设置所有元数据来说明该视频。

step_1PlayNextHelper.kt 中搜索 setBuilderMetadata 方法,将以下代码复制并粘贴到“Step 1.1 - Set video metadata for WatchNextProgram.”注释之间。

WatchNextHelper.kt

builder.setType(type)
   .setWatchNextType(watchNextType)
   .setLastPlaybackPositionMillis(watchPosition)
   .setLastEngagementTimeUtcMillis(System.currentTimeMillis())
   .setTitle(video.name)
   .setDurationMillis(duration.toMillis().toInt())
   .setPreviewVideoUri(Uri.parse(video.videoUri))
   .setDescription(video.description)
   .setPosterArtUri(Uri.parse(video.thumbnailUri))
   // Intent uri used to deep link video when the user clicks on watch next item.
   .setIntentUri(Uri.parse(video.uri))
   .setInternalProviderId(video.id)
   // Use the contentId to recognize the same content across different channels.
   .setContentId(video.id)

if (type == TYPE_TV_EPISODE) {
   builder.setEpisodeNumber(video.episodeNumber.toInt())
       .setSeasonNumber(video.seasonNumber.toInt())
       // User TV series name and season number to generate a fake season name.
       .setSeasonTitle(context.getString(
           R.string.season, video.category, video.seasonNumber))
       // Use the name of the video as the episode name.
       .setEpisodeTitle(video.name)
       // Use TV series name as the tile, in this sample,
       // we use category as a fake TV series.
       .setTitle(video.category)
}

请仔细阅读第 1.1 步中的代码,了解我们设置这些元数据的原因。

  1. setLastPlaybackPositionMillis()setDurationMillis() 有助于显示正确的播放进度,并在用户与视频进行互动时更新播放进度。
  2. setLastEngagementTimeUtcMillis() 设置用户观看此视频时的时间戳,这有助于“接下来观看”频道对条目进行优先级排序。

向“接下来观看”频道添加未看完的电影

我们可以使用 WATCH_NEXT_TYPE_NEXT 向“接下来观看”频道添加未看完的电影。

设置电影元数据:标题和说明

对于电影,请设置标题和说明以及其他属性,如此一来,用户无需点击观看视频就知道他们观看的是正确内容。

builder.setType(type)
   .setWatchNextType(watchNextType)
   .setLastPlaybackPositionMillis(watchPosition)
   .setLastEngagementTimeUtcMillis(System.currentTimeMillis())
   .setTitle(video.name)
   .setDurationMillis(duration.toMillis().toInt())
   .setPreviewVideoUri(Uri.parse(video.videoUri))
   .setDescription(video.description)
   .setPosterArtUri(Uri.parse(video.thumbnailUri))
...

电影的屏幕截图示例。

99a21ecd22268f2d.png

向“接下来观看”频道添加未看完的剧集

您可以对 setWatchNextType() 使用四种类型,对未看完的电视剧集使用 WATCH_NEXT_TYPE_CONTINUE,对下一集使用 WATCH_NEXT_TYPE_NEXT

设置剧集元数据:剧集和剧季编号/标题

对于电视剧集,请设置剧集编号和剧季编号,如此一来,用户无需点击观看视频就知道他们观看的是正确剧集。

if (type == TYPE_TV_EPISODE) {
   Builder.setType(PreviewPrograms.TYPE_EPISODE)
       .setEpisodeNumber(video.episodeNumber.toInt())
       .setSeasonNumber(video.seasonNumber.toInt())
       // Use TV series name and season number to generate a fake season name.
       .setSeasonTitle(context.getString(
           R.string.season, video.category, video.seasonNumber))
       // Use the name of the video as the episode name.
       .setEpisodeTitle(video.name)
       // Use TV series name as the tile, in this sample,
       // we use category as a fake TV series.
       .setTitle(video.category)
}

您必须正确设置 SeasonTitle、EpisodeTitle 和 Title。每一剧集都有用于描述剧集内容的专属标题,我们可将其用于 EpisodeTitle。使用电视连续剧标题作为电视节目的 title 属性,以便用户了解剧集内容。如果您已设置剧季标题,则可将其用于 SeasonTitle,相反,您也可以使用连续剧名称和剧季编号的组合,例如 <电视连续剧名称> 剧季 <剧季编号>。

剧集的屏幕截图示例。

658c430b13bcb3a6.png

继续播放

setLastPlaybackPositionMillis(watchPosition) 用于传入用户停止观看电影/剧集的时间;该进度将显示在“接下来观看”卡片中。在电视参考应用中,代码使用 WatchProgressDatabase 来跟踪每个视频的播放进度。这使得无论用户是如何找到视频的,都能从之前的中断处继续观看。

根据观看操作播放指南,相应剧集应在视频内容加载完毕后立即开始播放。因为用户已经开始观看,所以无需再次显示电视连续剧信息。

接下来,调用 PreviewChannelHelper.publishWatchNextProgram 将其发布到“接下来观看”频道。在同一文件中搜索“Step 1.2”,然后粘贴以下代码:

WatchNextHelper.kt

try {
   programId = PreviewChannelHelper(context)
       .publishWatchNextProgram(updatedProgram)
   Timber.v("Added New program to Watch Next row: ${updatedProgram.title}")
} catch (exc: IllegalArgumentException) {
   Timber.e(
       exc, "Unable to add program to Watch Next row. ${exc.localizedMessage}"
   )
   exc.printStackTrace()
}

刷新播放进度

如果“接下来观看”频道中已存在“接下来观看”卡片,当用户观看该视频的更多内容时,应用需要不断更新此卡片,才能反映最新的观看进度。

更新 WatchNextProgram 时,请使用同一构建器类构建 WatchNextProgram,并调用 PreviewChannelHelperupdateWatchNextProgram 以更新现有条目。将以下代码粘贴到 WatchNextHelper.kt 中的“Step 1.3”。

WatchNextHelper.kt

programId = existingProgram.id
PreviewChannelHelper(context).updateWatchNextProgram(updatedProgram, programId)

检查结果

浏览代码,将您的更改与 step_1_completed 中的源代码进行比较,运行 step_1_completed 并观看某一剧集的一部分,验证是否已将其添加到“接下来观看”频道。

验证

  • ✅ 通过:节目暂停/停止时,将被添加到“接下来观看”频道中
  • ✅ 通过:应用能够通过“接下来观看”条目继续播放
  • ✅ 通过:应用会及时更新“接下来观看”中的播放位置
  • ✅ 通过:应用会设置正确且完整的元数据
  • ✅ 通过:将所有未看完的内容推送到“接下来观看”频道
  • ❗ 失败:节目将在播放完毕后从“接下来观看”频道中移除
  • ❗ 失败:应用会在当前剧集播放完毕时添加下一集
  • ❗ 失败:应用不会将用户未互动过的内容添加至“接下来观看”频道
  • ❗ 失败:应用不会针对同一部电视连续剧添加多个剧集

您学到的内容

在此部分中,您学习了如何:

  • 创建 WatchNextProgram
  • 在“接下来观看”频道中插入或更新 WatchNextProgram
  • 更新播放进度
  • 继续播放
  • 为电视剧集设置正确的元数据

后续操作

向“接下来观看”频道添加未看完的剧集

5. 播放完毕时从“接下来观看”频道中移除内容(电影/剧集)

“接下来观看”频道的卡片是按上次互动时间排序的,最新的互动视频位于频道前面位置。当用户完整观看节目后,应用应将其从“接下来观看”频道中移除,并向用户推广更多相关内容。

开发者需要监控视频的播放进度。用户看完某个视频后,应用需将其从“接下来观看”频道中移除。

注意:同样的逻辑也可用于从“接下来观看”频道中移除电影或剧集。

移除 WatchNextProgram

如需从“接下来观看”频道中删除条目,开发者需找到正确的 WatchNextProgram,并使用节目 URI 将其从内容提供程序中删除。为此,开发者需要将 WatchNextProgram 与自有数据库中的视频实体相匹配。我们可以利用 internalProviderId 字段,设置唯一的视频标识符,并将其与开发者自有数据库中的某个实体进行关联。

首先,通过查找视频 ID 找到正确的 WatchNextProgram。您可以通过 WatchNextProgram. getInternalProviderId 或是 WatchNextProgram 内容提供程序访问 internalProviderId,然后使用 URI 从“接下来观看”频道中将其移除。

搜索“Step 2.1”,复制并粘贴以下内容:

WatchNextHelper.kt

val foundProgram = getWatchNextProgramByVideoId(video.id, context)
if (foundProgram == null) {
   Timber.e(
       "Unable to delete. No program found with videoID ${video.id}"
   )
   return null
}

// Use the found program's URI to delete it from the content resolver
return foundProgram.let {
   val programUri = TvContractCompat.buildWatchNextProgramUri(it.id)
   // delete returns the number of rows deleted.
   val deleteCount = context.contentResolver.delete(
       programUri, null, null
   )

   if (deleteCount == 1) {
       Timber.v("Content successfully removed from Watch Next")
       programUri
   } else {
       Timber.e("Content failed to be removed from Watch Next, delete count $deleteCount")
       null
   }
}

如果您想一次性移除多个 WatchNextProgram,最好请求执行批量操作以优化对电视 content provider 的访问。搜索“Step 2.2”,然后复制以下代码段并将其粘贴到 WatchNextHelper.kt 中。

WatchNextHelper.kt

val foundPrograms = getWatchNextProgramByVideoIds(videos.map { it.id }, context)
val operations = foundPrograms.map {
   val programUri = TvContractCompat.buildWatchNextProgramUri(it.id)
   ContentProviderOperation.newDelete(programUri).build()
} as ArrayList<ContentProviderOperation>

val results = context.contentResolver.applyBatch(TvContractCompat.AUTHORITY, operations)

results.forEach { result ->
   if (result.count != 1) {
       Timber.e("Content failed to be removed from Watch Next: ${result.uri}")
   }
}

根据“接下来观看”指南,如果用户已看完某剧集,应将其移除。如果开始显示片尾演职员表,则视为用户已“看完”剧集。在这种情况下,不要将其添加到“接下来观看”频道(如果已添加,则将其移除)。您可以使用自动检测片尾演职员表的技术,或根据内容时长估计(例如,剧集只剩下不到 3 分钟),来确定此状态。

通过查看 WatchNextHelper.kt 中的 handleWatchNextForEpisodes() 方法,您可以找到以下代码段:

WatchNextHelper.kt

video.isAfterEndCreditsPosition(watchPosition.toLong()) -> {
   removeVideoFromWatchNext(context, video)

   ...
}

在此 Codelab 中,我们使用 VIDEO_COMPLETED_DURATION_MAX_PERCENTAGE 来模拟片尾彩蛋位置,您可以将 isAfterEndCreditsPosition() 中的代码替换为您自己的逻辑。

检查结果

浏览代码,将您的更改与 step_2_completed 中的源代码进行比较,运行 step_2_completed 并观看剧集,验证是否已在看完后将其从“接下来观看”频道中移除。

验证

  • ✅ 通过:节目暂停/停止时,将被添加到“接下来观看”频道中
  • ✅ 通过:应用能够通过“接下来观看”条目继续播放
  • ✅ 通过:应用会及时更新“接下来观看”中的播放位置
  • ✅ 通过:应用会设置正确且完整的元数据
  • ✅ 通过:将所有未看完的内容推送到“接下来观看”频道
  • ✅ 通过:播放完成后,节目会从“接下来观看”频道中移除
  • ❗ 失败:应用会在当前剧集播放完毕时添加下一集
  • ❗ 失败:应用不会将用户未互动过的内容添加至“接下来观看”频道
  • ❗ 失败:应用不会针对同一部电视连续剧添加多个剧集

您学到的内容

在此简介中,您学习了以下内容:

  • 识别电视剧集的片尾彩蛋
  • 按视频 ID 查找 WatchNextProgram
  • 删除单个 WatchNextProgram
  • 删除多个 WatchNextProgram

后续操作

向“接下来观看”频道添加下一集

6. 添加下一集

与电影不同的是,电视节目不止 1 季,且每一季都有很多集。如果用户看完了某剧集,我们建议用户向“接下来观看”频道添加下一集,而不是仅将该剧集从“接下来观看”频道中移除。下一集可以是同一季中刚刚看完的剧集的下一集,也可以是下一季的第一集(如果该集是当前剧季的最后一集)。

向“接下来观看”频道添加下一集后,将“接下来观看”类型设为 WATCH_NEXT_TYPE_NEXT,则表示此剧集不是以前观看过的节目,而是用户可以关注的全新剧集。应用可让用户从头观看下一集。搜索“TODO: Step 3.1 - Add next episode from TV series.”,复制以下代码并将其粘贴到第 3.1 步:

WatchNextHelper.kt

videoRepository.getNextEpisodeInSeries(video)?.let {
       insertOrUpdateVideoToWatchNext(
           it,
           0,
           WATCH_NEXT_TYPE_NEXT,
           context
       )
       newWatchNextVideo = it
   }

getNextEpisodeInSeries() 方法会捕获下一集。

同一季的下一集

如果当前剧季还有更多剧集,应用应选择可供观看的下一集,并将其设为下一集。

下一季的第一集

如果用户已经看完了当前剧季,但仍有较新一季可供观看,则应用应将下一季的第一集选为下一集。

已发布的新剧集

如果没有更多剧集,则应用无需添加下一集。然而,当有新剧集推出时,最好将其推送到客户端并将其添加到“接下来观看”频道。新剧集应使用 WATCH_NEXT_TYPE_NEW 作为 WatchNextProgram 类型。此解决方案需要此 Codelab 并未涵盖的服务器推送通知。

检查结果

浏览代码,将您的更改与 step_3_completed 中的源代码进行比较,运行 step_3_completed 并观看剧集,验证是否已在看完后向“接下来观看”频道添加下一集。

验证

  • ✅ 通过:节目暂停/停止时,将被添加到“接下来观看”频道中
  • ✅ 通过:应用能够通过“接下来观看”条目继续播放
  • ✅ 通过:应用会及时更新“接下来观看”中的播放位置
  • ✅ 通过:应用会设置正确且完整的元数据
  • ✅ 通过:将所有未看完的内容推送到“接下来观看”频道
  • ✅ 通过:播放完成后,节目会从“接下来观看”频道中移除
  • ✅ 通过:应用会在当前剧集播放完毕时添加下一集
  • ❗ 失败:应用不会将用户未互动过的内容添加至“接下来观看”频道
  • ❗ 失败:应用不会针对同一部电视连续剧添加多个剧集

您学到的内容

在此部分,您学习了以下内容:

  • 如何选择下一集
  • 添加电视连续剧的下一集

后续操作

了解用户的兴趣所在。

7. 了解用户的兴趣所在

为了让用户专注于“接下来观看”频道中最相关的内容,在向“接下来观看”频道添加电视剧集或从频道中移除电视剧集时,应用需要考虑一些额外的因素。

同一电视连续剧中的多个剧集

导致用户在特定时间可能有多个未看完剧集的原因有许多。例如:

  1. 该电视连续剧可通过多个应用或直播观看;
  2. 用户想加速并跳过某些内容;

“接下来观看”频道的主屏幕上的视频条目非常有限,Google 建议应用在“接下来观看”频道中最多保留每部电视连续剧的集,保留的这一集应是用户上次观看的一集。

WatchNextHelper 类中,我们在 handlePlayNextForEpisode() 中对其进行处理。搜索“Step 4.1”,复制以下代码并将其粘贴到第 4.1 步的空白区域。

WatchNextHelper.kt

newWatchNextVideo?.let { videoToKeep ->
   videoRepository.getAllVideosFromSeries(videoToKeep.seriesUri)?.let { allEpisodes ->
           filterWatchNextVideos(allEpisodes, context)
               ?.let { watchedEpisodes ->
                   removeVideosFromWatchNext(
                       context, watchedEpisodes.filter { it.id != videoToKeep.id })
               }
       }
}

在第 4.1 步中,我们会继续跟踪用户观看的最新剧集,并从同一部电视连续剧中移除所有其他剧集。由于该步骤一次性移除了多个剧集,因此我们创建了一个新方法 removeVideosFromWatchNext(),以利用 Android content provider 的批量操作。

互动度较低的内容

根据“接下来观看”指南,只有在用户已开始观看时,才应将剧集添加到“接下来观看”频道中;如果用户观看某剧集超过 2 分钟,则视为该用户已经“开始”观看该剧集。搜索“Step 4.2”,复制以下代码并将其粘贴到第 4.2 步的区域中。

WatchNextHelper.kt

val durationInMilliSeconds = duration.toMillis().toInt()
// Return true if either X minutes or Y % have passed
// Following formatting spans over multiple lines to accommodate max 100 limit
val watchNextMinStartedMillis = TimeUnit.MINUTES.toMillis(WATCH_NEXT_STARTED_MIN_MINUTES)
// Check if either X minutes or Y% has passed
val hasVideoStarted =
   (currentPosition >= (durationInMilliSeconds * WATCH_NEXT_STARTED_MIN_PERCENTAGE)) or
           (currentPosition >= watchNextMinStartedMillis)
val hasVideoStartedWithValidPosition =
   ((currentPosition <= durationInMilliSeconds) and hasVideoStarted)
Timber.v(
   "Has video started: %s, duration: %s, watchPosition: %s",
   hasVideoStartedWithValidPosition,
   duration,
   currentPosition
)
return hasVideoStartedWithValidPosition

检查结果

浏览代码,将您的更改与 step_4_completed 中的源代码进行比较,运行 step_4_completed 并观看剧集,验证是否存在以下情况:

  1. 此代码从电视连续剧中移除其他剧集
  2. 仅当用户已开始观看时,才会将其添加到“接下来观看”频道

验证

  • ✅ 通过:节目暂停/停止时,将被添加到“接下来观看”频道中
  • ✅ 通过:应用能够通过“接下来观看”条目继续播放
  • ✅ 通过:应用会及时更新“接下来观看”中的播放位置
  • ✅ 通过:应用会设置正确且完整的元数据
  • ✅ 通过:将所有未看完的内容推送到“接下来观看”频道
  • ✅ 通过:播放完成后,节目会从“接下来观看”频道中移除
  • ✅ 通过:应用会在当前剧集播放完毕时添加下一集
  • ✅ 通过:应用不会将用户未互动过的内容添加至“接下来观看”频道
  • ✅ 通过:应用不会针对同一部电视连续剧添加多个剧集

您学到的内容

在此部分,您学习了以下内容:

  • 避免添加同一电视连续剧的多个剧集
  • 避免添加互动度较低的内容

后续操作

恭喜

8. 恭喜

恭喜!您已成功为自己的电视剧集构建了“接下来观看”频道,并了解了“接下来观看”的所有质量要求。

太棒了!

深入阅读

参考文档