开发者控制台

Fire TV上的活动集成


Fire TV上的活动集成

Fire TV活动为独立活动,既可以实时观看(直播活动),也可以日后重播。体育赛事和音乐会直播均为典型的直播活动。

本文档旨在帮助您吸引客户观看Fire TV活动,以及介绍如何在Fire TV UI中播放或重播此类内容。

有关线性电视、视频点播和直播活动的更多信息,请参阅重要定义

Fire TV上的功能

完成Fire TV活动集成后,只要客户安装并登录您的应用,便会看到合作伙伴品牌专用的视频内容行(可以实时更新)。当客户将焦点变为该行中的任意活动时,屏幕右上角将显示丰富的元数据并播放该活动。

当前正在播放的直播活动的焦点状态。
当前正在播放的直播活动的焦点状态。
短视频剪辑内容的焦点状态。
短视频剪辑内容的焦点状态。

用户选择活动卡片后,会在应用中启动全屏播放:

在应用中全屏播放。
在应用中全屏播放。

支持的内容

以下是我们支持的视频内容:

  1. 即时直播活动 - 直播信息提要,如Twitch信息提要。
  2. 预定直播和未来活动 - 如付费观看活动或奥运会。
  3. 线性频道 - 按照节目表播放的节目,如传统有线电视。
  4. 点播和重播(VOD) - 可随时按需观看。
  5. 精彩亮点、赛事集锦、访谈(短篇VOD)- 与第4类类似,但时长更短,例如赛事亮点。

认证核对清单

亚马逊会使用以下核对清单,对应用中的活动集成进行认证。应用的实施会直接影响下面的预期行为列表。认证方面的任何例外情况都将根据具体情况处理。

授权状态更改

  • 授权结果应为单击时进行全屏播放。在大多数情况下,客户安装、打开并登录您的应用后,客户的已授权活动会填充到Fire TV UI中。
  • 客户失去授权后,必须从Fire TV UI中移除相关内容。客户失去授权的情况包括会员过期、注销应用或卸载应用。

浏览和播放体验

  • 为确保流畅的用户体验,必须提供活动名称、描述、开始/结束时间(如果适用)和内容转发图像等活动元数据。这些元数据必须作为数据库信息的一部分推送至设备。
  • 客户将焦点置于直播内容时,视频信息提要(直播预览)将替换背景图像。
  • 客户将焦点置于活动磁贴,然后按下环境菜单按钮后,可以选择“Watch with <App Name>(使用‘应用名称’观看)”或“Launch <App Name>(启动‘应用名称’)”。“Watch with <App Name>”将深层链接至应用中的全屏播放,而“Launch <App Name>”将深层链接至应用的主UI。应用必须提供深层链接,将视频信息推送至设备。
  • 如果应用启用了家长监护,则在播放前会显示PIN提示。如果启用了家长监护,则必须禁用直播频道预览。
  • 通过选择按钮或“Watch with”环境菜单项访问活动,将深层链接至应用内的全屏播放,如果活动正在直播,便会直接播放相关内容。如果所选活动将在未来播放,或者是直播活动的回放,则入口将指向活动详细信息页面或应用中的全屏播放。按后退按钮一到两次将返回Fire TV UI,但不会返回菜单中更高的层级。

开发、演示模拟和部署

  • 亚马逊必须能够将特定账户列入允许列表,以便在投入生产之前对活动体验进行审核和认证。
  • 活动功能仅可在与亚马逊联系人达成的相关协议所允许的市场启用。后续在新市场的发布需要遵循新的认证流程。

Fire TV上的推荐频道

Fire TV上的活动集成基于主屏幕推荐频道中的Android文档。此API最初在Android 8.0(API级别26)中推出,不过Fire OS已经更新,可在所有Fire TV设备上(包括早于API级别26的设备)支持此API。

将您的应用程序包列入允许列表

进行开发时,您需要先将自己列入允许列表,然后才能在直播活动行中显示内容。与亚马逊开发者共享以下信息:

  1. 您的应用程序包名称。
  2. 您想要在行中显示的名称(选择适当的长度,确保在不同UI中获得良好的显示效果)。
  3. 测试设备序列号(DSN)列表。您的设备可以借助DSN查看电子节目指南(EPG)中的内容,您可以借助客户ID (CID)查看“Live(直播)”选项卡中的内容。这将确保在启动前,您能够在设备上验证上述功能。

提供方属性

向您的亚马逊开发者提供一个代表应用品牌的单色标志。当用户浏览直播活动行时,该标志将叠加在显示的内容预览图像上。此图像文件高度必须为34个像素,横向宽度不得超过叠加图像的25%。

功能兼容性

您的应用将在各种Fire TV设备上以统一方式实施。亚马逊已将该功能(最初通过Android O(API级别26)推出)反向移植到早期版本的Fire TV设备(Fire OS 5和6)上。为确保应用能够正常运行,请在与数据库交互的环境中检查功能兼容性。(请参阅下文代码示例部分中的“检查功能兼容性”。)

推荐频道

您的应用应进行更新,以便将一个(且只有一个)推荐频道推送至本地频道数据库。这个推荐频道中将包含作为类型的PREVIEW和一个空的inputId字段。您的应用应创建一个(且只有一个)推荐频道,将类型定义为PREVIEW,并将APP_LINK_INTENT_URL设置为指向应用的主 Activity。提供与应用名称紧密匹配的displayName,不要设置inputId。在此项集成中,如果您插入多个推荐频道,则无法将数据库中的其他推荐频道及其相关预览节目识别为有效内容。

活动授权和订购

创建频道后,应更新应用,将已授权的内容推送至Fire TV设备的本地数据库。应用在后台运行时,也可以添加、更新或删除内容。如果内容出现在此数据库中,则意味着客户有权观看这些内容。所有向用户显示的内容都将以此数据库作为权威参考。此数据库中的内容可以随时添加或删除,且更改将在5分钟内反映在UI中。

应用中的事件排序则基于节目权重,并可回退到节目插入顺序。Fire TV建议您将最热门的内容置于前排,并将当前播放的节目放在这一行的前面加以突出,使其最为显眼。

编排元数据属性

Fire TV会管理UI刷新,但您的应用需要负责提供所有内容。您的应用会将TYPE_EVENTTYPE_CHANNELTYPE_CLIP中的节目插入Android数据库。建议您尽可能在TvContractCompat.PreviewPrograms中多填入一些内容,而下表列出了为确保我们的平台能够提供最佳内容转发体验,所需的最低属性以及推荐使用的属性。以下元数据大多会作为“精简详情”中的一部分显示,当焦点置于相应的内容上时,精简详情会显示在左上角。

节目元数据显示。
节目元数据显示。

下表列出了可能使用的元数据。上图中的标注对应下表中的“参考”一列。

可用的TIF字段和预览节目列 目的 参考 是否必需?
channel_id
COLUMN_CHANNEL_ID
将直播活动映射至预览频道。
type
COLUMN_TYPE
必须设置为PreviewPrograms.TYPE_EVENTPreviewPrograms.TYPE_CHANNELPreviewPrograms.TYPE_CLIP。不支持其他类型。
title
COLUMN_WEIGHT
定位行中的磁贴。若未设置COLUMN_WEIGHT,则回退即为节目插入顺序。
title
COLUMN_TITLE
活动名称,显示在内容磁贴和精简详情中。 A
short_description
COLUMN_SHORT_DESCRIPTION
活动描述,显示在精简详情中。 C
poster_art_uri
COLUMN_POSTER_ART_URI
当直播预览不可用时,在内容磁贴和背景图像中使用的16:9图像。 F、H
intent_uri
COLUMN_INTENT_URI
用户选择内容时采取的操作。此操作应使活动全屏播放。
start_time_utc_millis
COLUMN_START_TIME_UTC_MILLIS
活动的日期和开始时间,显示在精简详情中。也用于确定“LIVE”(直播)徽章。 B、G
end_time_utc_millis
COLUMN_END_TIME_UTC_MILLIS
活动的结束时间,显示在精简详情中。也用于确定“LIVE”徽章。 B、G
live
COLUMN_LIVE
无论该内容为当前直播(true)还是以往直播活动的重播(false),都将确定是否应显示“LIVE”徽章。 E
preview_video_uri
COLUMN_PREVIEW_VIDEO_URI
当焦点置于内容磁贴时,视频将呈现在屏幕右上角。指“直播预览”。 H
logo_uri
COLUMN_LOGO_URI
应用标志 - 目前不支持TIF,但支持上文“提供方属性”部分列出的格式。 H
content_rating
COLUMN_CONTENT_RATING
家长监护评级(仅用于显示)。 B
release_date
COLUMN_RELEASE_DATE
仅限非直播内容。此字段中的内容将显示为缩略图徽章,并显示在精简详情中。 B
interaction_count
COLUMN_INTERACTION_COUNT
仅限PreviewPrograms.TYPE_CLIP。观看者与此内容的交互次数。将显示在精简详情中。 B 否^
internal_provider_data
COLUMN_INTERNAL_PROVIDER_DATA
JSON文本包含两个可选字段:up_nextoriginal_air_time.up_next
仅限与PreviewPrograms.TYPE_CHANNEL一同使用。字符串,包含当前节目后待播内容的标题,显示在精简详情中。

original_air_time.与重播内容一同使用。字符串,包含当前播放内容的首播时间,显示在精简详情中。
C 否^

^现在已可用于开发,即将推出演示支持。

您可以利用PreviewProgram.Builder在Fire OS 7设备上创建PreviewProgram对象。对于早期版本的Fire OS,您必须手动创建ContentValues。请参考下面的“代码示例”部分。

内容支持: 活动

对于具有已知时间表的活动(如体育赛事),请使用PreviewProgram.TYPE_EVENT类型,同时将直播标记设置为true,并填充开始和结束时间。如果活动节目时间表已知(如体育赛事),建议将开始和结束时间同时作为元数据进行推送,以充分利用Fire TV支持的高级功能。请注意以下事项:

  1. Fire TV将显示计划在7天内播放的活动,并会在其结束时间过后自动删除活动。因此,您可以推送计划播出日期与当前日期相距远超7天的活动,无需担心活动过早显示或未能按时删除。
  2. 当前播放的活动将在磁贴上显示“LIVE”徽章,告知用户活动处于正在播放的状态。未进行播放时,则会隐藏“LIVE”徽章。
  3. 如果提供了有效的开始时间和结束时间,则活动磁贴底部还会出现红色进度条,告知用户活动的进度。

对于实时活动(如直播视频博客),请使用PreviewProgram.TYPE_EVENT类型,同时将直播标记设置为true,并在Long.MIN_VALUELong.MAX_VALUE中填充相应的开始和结束时间。如果从数据库中删除此类内容,则会从UI中删除相应的内容。

内容支持: 重播

对于直播内容重播(例如以往体育赛事的重播),请使用PreviewProgram.TYPE_EVENT类型,同时将直播标记设置为false,并在Long.MIN_VALUELong.MAX_VALUE中填充相应的开始和结束时间。重播将在磁贴上显示首播日期徽章,其中显示release_date字段内容。如果从数据库中删除此类内容,则会从UI中删除相应的内容。如果客户退出再通过Fire TV UI重新进入,则最佳实践是从客户上次观看的位置开始播放。

内容支持: 短视频剪辑

对于短视频剪辑(如体育赛事精彩亮点),请使用PreviewProgram.TYPE_CLIP类型,同时将直播标记设置为false,并在Long.MIN_VALUELong.MAX_VALUE中填充相应的开始和结束时间。可以在精简详情中设置interaction_count字段,使其显示观看者与此内容的交互次数。如果从数据库中删除此类内容,则会从UI中删除相应的内容。

最佳实践

以下产品和实施指南可在Fire TV上为客户提供最佳直播活动体验。

一般原则

  • 适时提供顺畅无阻的注册流程,以鼓励试用。例如减少应用上的注册表单或使用手机进行注册。
  • 下载页面和发布说明应提及此项集成,以便客户了解Fire TV上这项新功能的可用性。
  • 在与数据库交互之前,请务必检查设备的功能兼容性。这样可确保所有Fire TV设备上都能正常显示您的活动内容。
  • 优化深层链接流程,在2.5秒内开始全屏播放。

插入内容

  • 将直播活动的ContentValues插入数据库时,建议您手动进行构建,而不是使用支持库的辅助函数(例如PreviewProgram.Builder)。
  • 插入至少5个磁贴,填满旋转显示区域。如果可用的PreviewPrograms数量少于4个,则Fire TV将隐藏您的行。为了始终保持最低数量,请考虑添加重播、短视频剪辑或线性频道。
  • 为直播活动节目提供所有必要的元数据。有关节目类型、开始时间、结束时间和“LIVE”徽章的详细信息,请参阅“编排元数据属性”。
  • COLUMN_TITLE列提供可显示的活动名称。Fire TV可在节目磁贴中显示最多16个字母数字字符或8-10个全角字符。
  • 支持在浏览中进行直播活动预览以提升参与度。

刷新内容

  • 使用JobSchedulerWorkManager定期检查活动是否准确,确保未来7天内能够显示数量足够的活动。这将确保浏览体验中始终有丰富的活动内容,即便应用未在前台活跃,这些内容也会与实际授权的活动内容保持同步。
  • 提前推送未来预定的播放活动,尽可能减少数据库操作频率。为实现最佳性能,Fire TV每个活动行中最多可容纳15个磁贴,并且会在结束时间过后5分钟内从旋转显示区域中删除已结束的活动。您的应用只需在下一次定期同步中删除过期的直播活动内容。

代码示例

以下是一些常见的直播活动实现方式。

Android清单

<!-- Required.This service is for using JobScheduler to sync
     Live Event data periodically -->
<service
    android:name=".SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />


<!-- 必需。此接收器用于在重新启动后触发同步作业时间表。-->
<receiver android:name=".rich.RichBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- This service is for playing preview.Only needed when supporting
     preview playback in FireTV UI through TvView-->
<service android:name=".PreviewInputService"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/previewinputservice" />
</service>

检查功能兼容性

// Amazon marker to identify given device has feature backported
private final static String AMAZON_SYS_FEATURE_KEY =
  "com.amazon.feature.fos7_tv_provider";

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  // 自Android O(API 26)版本开始,对该功能提供支持
  .....
} else if (isPreviewProgramBackportedOnFOS(context))(
  // This feature is supported by Amazon on early FOS devices through backport
  .....
}

public static boolean isPreviewProgramBackportedOnFOS(Context context) {
  PackageManager pm = context.getPackageManager();
  return pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY) &&
    pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY, 1);
}

插入频道预览

public void buildPreviewChannel() {
    Channel.Builder builder = new Channel.Builder();
    builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
            .setDisplayName("SampleProviderChannel")
            .setAppLinkIntentUri(Uri.EMPTY);/* should be the intent to
                                               launch 3P's main screen */
    Channel newChannel = builder.build();
    ContentValues cv = newChannel.toContentValues();
    Uri channelUri = resolver.insert(TvContractCompat.Channels.CONTENT_URI, cv);
}

创建直播活动

public static ContentValues createLiveEventContentValues(Context context,
  long channelId) {
  Uri previewVideoUri = TvContractCompat.buildPreviewProgramUri(1)
    .buildUpon()
    .appendQueryParameter("input", "<full path to PreviewInputService.java>")
    .build();

  TvContentRating[] ratings = new TvContentRating[1];
  ratings[0] = TvContentRating.createRating("com.android.tv.dummy",
    "US_TV_DUMMY",
    "US_TV_PG_DUMMY",
    "US_TV_D_DUMMY");

  /* Note: Make sure you test before publishing.Example:
       adb shell
       am start -W -a android.intent.action.VIEW -d "example://test/blablabla?e=1"
  */
  Intent deeplinkIntent = new Intent("android.intent.action.VIEW",
    Uri.parse("example://test/blablabla?e=1"));

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Log.d(Utils.DEBUG_TAG, ">=26 Android build.Support live event!");
    PreviewProgram.Builder liveEventBuilder = new PreviewProgram.Builder();
    liveEventBuilder.setChannelId(channelId)
      .setLive(true)
      .setType(TvContractCompat.PreviewPrograms.TYPE_EVENT)
      .setTitle("Dummy Event Title")
      .setDescription("Dummy Event description")
      .setStartTimeUtcMillis(10000000)
      .setEndTimeUtcMillis(20000000)
      .setPosterArtUri(Uri.parse("http://placekitten.com/200/300"))
      .setLogoUri(Uri.parse("http://provider.logo.uri"))
      .setIntent(deeplinkIntent)
      .setPreviewVideoUri(previewVideoUri)
      .setContentRatings(ratings);
    return liveEventBuilder.build().toContentValues();
  } else if (Utils.isPreviewProgramBackportedOnFOS(context)) {
    Log.d(Utils.DEBUG_TAG, "检测到了向后移植的功能! 支持直播活动!");

    // 与PreviewProgram.Builder()中setIntent()的内在逻辑一致
    Uri deeplinkIntentUri = Uri.parse(deeplinkIntent.toUri(URI_INTENT_SCHEME));

    /* 如果构建版本低于26,则手动创建ContentValues。如果构建版本低于26,则无法使用
       PreviewProgramBuilder,因为toContentValues()方法
       会在内部移除关键密钥,
       这会导致崩溃 */
    ContentValues cv = new ContentValues();
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_LIVE, true);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_TYPE,
      TvContractCompat.PreviewPrograms.TYPE_EVENT);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_TITLE, "Dummy Event Title");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_SHORT_DESCRIPTION,
      "Dummy Event description");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_START_TIME_UTC_MILLIS,
      10000000);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_END_TIME_UTC_MILLIS,
      20000000);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_POSTER_ART_URI,
      "http://placekitten.com/200/300");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_LOGO_URI,
      "http://provider.logo.uri");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_INTENT_URI,
      (deeplinkIntentUri.toString()));
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI,
      previewVideoUri.toString());
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_CONTENT_RATING,
      contentRatingsToString(ratings));
    return cv;
  } else {
    Log.d(Utils.DEBUG_TAG, "系统构建版本不支持直播活动");
    return null;
  }
}

插入内容

ContentValues[] liveEventsArray = < Array of ContentValues of PreviewProgram >
  resolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, liveEventsArray);

更新内容

ContentValues updateValues = < values of a PreviewProgram to update >
  resolver.update(TvContractCompat.buildPreviewProgramUri( < Id of the preview program to update > ), updateValues, null, null);