Fire TV 上多媒体应用的要求


Fire TV 上多媒体应用的要求

要使 Fire TV 应用与 Fire TV 上的其他应用和谐交互,所有应用都必须遵守相同的多媒体要求。这些多媒体要求包括如何正确处理活动生命周期和音频焦点事件、MediaSession、解码器实例、唤醒锁等。通过遵守此处概述的要求,当用户在 Fire TV 上的不同应用之间导航时,您的应用将为他们提供更好的体验。

Fire TV 上应用的合作

在 Fire TV 上,应用的主要目的通常是播放多媒体。因此,Fire TV 上各应用的合作是必不可少的。

在许多方面,面向电视设备的应用可能比一般的安卓应用更复杂。“主页”按钮和语音交互随时可供用户使用,这可以立即中断应用的播放。与平板电脑和手机应用不同的是,在 Fire TV 上,不能使用状态栏和通知栏与应用交互。停止或终止行为不当的应用在电视设备上也是一项更复杂的任务。如果您关闭了播放音乐的应用,只是为了发现在开始观看电影时音乐继续播放,那么您就知道多媒体交互会如何以灾难性的方式发生冲突。

由于 Fire TV 上的应用需要和谐合作,因此所有应用都必须遵循相同的多媒体实践。一般来说,请遵循安卓开发者页面上的推荐指南。但是,由于电视设备上的应用之间各种交互的复杂性,我们将安卓开发者文档中列出的许多建议视为要求而不是建议。如果 Fire TV 的要求更严格,要求详细信息中会加以注明。

背景安卓知识

本文中的概念假定您熟悉安卓开发。要更好地理解这些基本概念,请参阅安卓文档中的以下主题:

请注意,并非这里提到的所有安卓 API 都可以在 Fire OS 5(最高支持 API 级别 22)上使用。其中一些提到的 API 是特定于安卓 API 级别 25 (Nougat),Fire OS 6 支持该级别。如果要求指的是您的应用不支持的 API 级别,请遵循应用 API 级别提供的实施指南。

按应用类型和活动/服务方法列出的要求

要求因应用类型以及应用是将媒体作为活动还是服务进行播放而异。多媒体应用可分为几种类型:

  • 仅播放音频的应用(例如,音乐应用)
  • 播放音频和视频的应用(例如,电视网络电台应用)
  • 仅播放视频的应用(例如,没有音频的游戏,允许用户用他们最喜欢的音乐应用在后台播放音乐)

这些不同类别的应用通常具有不同的要求。

此外,应用可以将媒体作为活动或服务播放。视频应用必须具有可用于渲染的窗口,因此它们通常使用活动方法。在后台播放(同时被其他活动覆盖)的仅音频应用必须使用前台服务以避免中断。在这两种情况下,必须在输出任何音频之前进行音频焦点管理。以下各部分分别指定了这些应用类型的要求。

基于活动的应用要求

当您的多媒体应用作为活动(而不是服务)播放时,您必须仔细处理活动生命周期事件。您必须了解安卓的活动生命周期中描述的所有可能的过渡。处理这些过渡是您的 Fire TV 应用的要求。

要求 1.1: 处理 onPause()

当您的活动被其他活动部分覆盖时(例如,在用户长按“主页”之后,Fire TV 显示提醒通知时)调用 onPause()。即使在调用 onPause() 后,您的活动仍可继续播放,但您仍然必须执行回调以改变音频焦点。(无论生命周期事件如何,都不允许您在失去音频焦点后播放音频。)

要求 1.2: 处理 onResume()

onResume() 必须恢复原始播放状态(即恢复 onPause() 中所做的任何更改),而不调用 onCreate()。这里的“播放状态”可能有各种含义,具体取决于您的应用。通常,您应使用原始音量继续自动播放(如果您也有音频焦点),并且还原后的 UI 显示最新进度条。如果您必须释放用于播放的任何资源,则可能需要重新初始化管道并找到播放中断之前的最后已知位置。

要求 1.3: 在 onStop() 中释放资源

onStop() 被调用时,您必须释放用于播放的所有资源(AudioTrackMediaCodec 等)。当用户按“主页”后返回到您的应用时,您的应用需要重新获取所有资源。

使用单独的活动进行播放时要小心谨慎。快速返回到您的应用可能意味着生命周期事件的顺序将是 onStop()onRestart()onStart()onResume()。如果在 onStop() 中释放资源,则可以考虑通过调用 finish() 来关闭播放活动。或者,您可以沿这些生命周期事件重新获取所有播放资源(在 onRestart()onStart()onResume() 中)。

要求 1.4: 避免使用 onDestroy()

您不能依赖调用 onDestroy() 来释放资源。资源必须已在 onStop() 中释放。调用 onDestroy() 的时机(如果调用)由设备的操作系统决定。当快速切换到其他应用时,它很可能不会被调用。

要求 1.5: 音频焦点与活动生命周期无关

您不能依赖在活动生命周期中的特定状态下接收到的特定音频焦点更改事件。生命周期和音频焦点更改事件通常一起收到,例如当您的活动因语音交互而中断时。在这种情况下,您将收到一个 AUDIOFOCUS_LOSS 和一个 onPause() 活动生命周期事件,但这些事件实际上是独立的,事件的顺序可随时更改。

基于服务的应用要求

如果您的应用播放不含视频的音频,建议您使用构建音频应用中所述的前台服务来实现播放。使用这种方法,当用户浏览其他活动时,允许在后台播放音频。

要求 2.1: 启动服务

使用 startForeground() 方法将服务作为前台服务启动,以避免在内存不足的情况下被中断。

要求 2.2: 停止服务

播放完毕后,使用 stopForeground() 删除服务的前台状态。此方法表示如果需要更多内存,您的服务可能会被终止。

媒体会话要求

媒体应用必须处理传入的媒体会话事件,无论事件发生在何处。事件可能源自以下各项:

  • Fire TV 设备的遥控器

  • 带下一个/上一个按钮的蓝牙耳机

  • 带多媒体按钮的附加键盘,以及鼠标

  • 智能手表

  • 在用户手机上运行的远程应用

  • 响应语音交互的亚马逊服务(例如用户说“Alexa,暂停!”)等等。

要以通用方式处理所有这些事件,而不考虑活动生命周期状态、AudioFocus 事件等,您的应用必须遵循使用媒体会话中的准则。具体而言,请注意以下事项:

“媒体会话与它管理的播放器一起存续。您应该在拥有媒体会话及其关联播放器的活动或服务的 onCreate() 方法中创建和初始化媒体会话。

处理媒体会话事件的要求如下:

要求 3.1: 允许传输控制回调

初始化 MediaSession 时,设置标记以允许从传输控制器和媒体按钮回调。(注意:​ 从 API 级别 26 开始,所有会话都需要处理传输控制。)

要求 3.2: 发布 MediaSession

在开始播放之前,通过调用 setActive 来发布媒体会话。

要求 3.3: 更新播放状态

保持 PlaybackState 为最新:

  • 维护有效控制器操作的列表。例如,“暂停”只有正在播放时才可能;此外,有些应用可能会在广告期间限制快进。

  • 更新玩家位置,以便正确更新任何进度条。注意:​ 为了避免只是为了更新播放位置而刷新状态,您可能需要使用带 4 个参数(包括 updateTime)的 PlaybackStateCompat.Builder.setState()

  • 在元数据信息发生变化时更新元数据信息,如艺术家姓名、曲目标题、曲目持续时间等。应更新所有 UI 显示(甚至是手机上的遥控应用)。

要求 3.4: 释放 MediaSession

播放完成后,使用MediaSession.release() API 释放媒体会话。

要求 3.5: Mediasession 的持续时间

您的 MediaSession 的活动期必须与您的应用被允许并愿意播放的时间段完全匹配。例如,如果您在后台播放音乐,则必须在后台持续处理媒体按钮。如果在暂时失去音频焦点时暂停播放,则还必须停用媒体会话以放弃对媒体按钮的处理。

音频焦点事件要求

通过音频焦点处理,应用以协调的方式播放音频。正如管理音频焦点中所解释的那样:

“为了避免每个音乐应用同时播放,安卓引入了音频焦点的概念。一次只有一个应用可以持有音频焦点。”

在 Fire TV 上,播放任何类型音频的应用都必须具有音频焦点,然后才能开始播放。围绕请求音频焦点、处理音频焦点更改和放弃音频焦点有各种要求。

要求 4.1: 在播放的前一刻请求音频焦点

应用必须在播放音频的前一刻调用 AudioManager.requestAudioFocus()。(许多开发者忽略了要求中的前一刻部分。) 您的 requestAudioFocus()abandonAudioFocus() 音频焦点调用之间的时间段必须与您处理焦点更改回调的时间完全匹配。

例如,通常错误实现的场景是:

  1. 用户在列出应用内各种可用内容的屏幕上单击“播放”。
  2. 应用请求音频焦点以检查是否允许播放。
  3. 应用开始下载内容,显示“正在缓冲”屏幕几秒钟。
  4. 应用切换到活动,由实际播放处理音频焦点变化。

在实际播放开始前出现瞬态“正在缓冲”屏幕(步骤 3)期间,您的应用也可能会失去音频焦点。如果您在步骤 2 中请求了音频焦点,则必须从该点开始连续处理 AudioFocus 事件。当用户到达步骤 4(处理 AudioFocus 事件的播放活动)时,您的应用可能已经失去了播放权限(例如,如果用户在步骤 1 中单击“播放”后快速启动了语音交互)。

要求 4.2: 验证是否已授予焦点

应用必须验证 requestAudioFocus() 调用是否返回 AUDIOFOCUS_REQUEST_GRANTED。返回的任何其他值意味着不允许您的应用开始播放。注意:​ API 级别 26 引入了 AUDIOFOCUS_REQUEST_DELAYED,但目前不会在 Fire TV 设备(API 级别 25)上返回此值。

要求 4.3: 放弃音频焦点

每个播放会话都必须在完成播放后立即放弃音频焦点。如果您的应用有多个播放会话(例如,您开始在新活动上播放下一集节目),则不需要第二次请求音频焦点。如果您第二次请求音频焦点,您的第一个会话将收到一个带有参数 AUDIOFOCUS_LOSS 的回调;作为响应,您的第一个会话应该释放它的资源(那些未与第二个会话共享的资源)并放弃音频焦点。因此,您的应用将始终只出现在 AudioFocus 堆栈中一次(您可以通过在命令行中运行 adb shell dumpsys audio 手动确认这一点)。

音频焦点更改要求

如果您的应用请求音频焦点,它还必须处理实现 AudioManager.OnAudioFocusChangeListener 的焦点更改,并且必须正确处理所有回调参数。回调参数是以下项之一:

  • AUDIOFOCUS_LOSS

  • AUDIOFOCUS_LOSS_TRANSIENT

  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

  • AUDIOFOCUS_GAIN

要求 4.4: 处理 AUDIOFOCUS_LOSS

当您的应用收到 AUDIOFOCUS_LOSS 事件时,它必须完全停止音频播放,因为系统已授予另一个应用完全播放权限。您不应该期望音频焦点返回到您的应用(在此之后,您将永远不会得到 AUDIOFOCUS_GAIN)。您应该关闭您的应用,释放资源(AudioTrackMediaCodec 等等),并通过调用 AudioManager.abandonAudioFocus() 来通知系统您播放完毕。

要求 4.5: 处理 AUDIOFOCUS_LOSS_TRANSIENT

当您的应用收到 AUDIOFOCUS_LOSS_TRANSIENT 事件时,它必须“暂停”音频播放(但这里没有对视频的硬性要求)。当您的应用因短暂通知、系统声音或其他临时状态而中断时,通常会发生这种情况,并且音频焦点将很快返回到您的应用。

暂停音频意味着在这一点之后您无法输出任何音频。您的应用必须处于静音状态。您可以暂停整个播放,也可以将音频音量降低到 0 并继续播放视频。播放按需内容的视频应用应选择完全暂停视频和音频播放。您可以保留用于播放的资源。如果完全暂停将导致用户体验较差(例如,直播视频馈送缓冲较长时间从而中断),则可以在音频静音时继续播放视频。

要求 4.6: 处理 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

当您的应用收到 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 时,它必须降低音量(放弃)。预期的音量水平可能因各种用例而异,但不得干扰与音频输出平行播放的任何其他声音。(例如,您可以将临时音量级别设置为原始音量的 30-40% 左右。)

当系统想要播放短通知时,通常会收到此事件,在此期间,音频应用可以继续以较低音量播放音乐。注意:​ 这意味着您还可以将此临时音量级别设置为 0 或完全暂停播放,从而实际上像处理 AUDIOFOCUS_LOSS_TRANSIENT 一样处理此事件。

要求 4.7: 处理 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

输出编码音频内容(例如杜比直通)的应用应在收到 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 时完全暂停播放。编码的内容在解码之前没有音量的概念。对于直通情况,只在接收器 (AVR) 上进行解码,因此在 Fire TV 上设置流音量不会影响实际输出。

亚马逊的音频增强型平台或许能够对内容进行解码,将音量更新到较低级别,对内容进行重新编码,然后将其发送到 HDMI 输出,但不保证此功能。(您可以联系亚马逊了解具体信息和可用性。)

要求 4.8: 处理 AUDIOFOCUS_GAIN

当您的应用收到 AUDIOFOCUS_GAIN 时,它可以按照您的应用最初预期的那样继续播放。此事件将在短暂时间段(在此期间暂停、静音或以降低音量播放)结束后发送。如果您之前调低了音量,则必须恢复原始音量级别

当收到此 AUDIOFOCUS_GAIN 事件时,播放实时流的音频应用和视频应用通常会自动继续播放。此外,还建议播放按需视频内容的应用继续播放,或者选择保持暂停状态,向用户显示“播放”按钮以便在按下该按钮后继续播放。

要求 4.9: 放弃音频焦点

完成音频播放后,如果您的应用曾被授予音频焦点,则还必须使用与之前请求的音频焦点相同的参数调用 AudioManager.AbandonAudioFocus()。您必须释放用于播放的资源。(您可以再次请求音频焦点以进行下一次播放)。

安全解码器要求

要求 5.1: 安全解码器的限制

应用在任何给定时间只能使用一个安全的解码器管道。大多数平台只支持一个安全解码器 (MediaCodec) 实例,因此对多个安全内容进行并行解码的任何尝试都将失败。

要求 5.2: 释放资源

当调用播放活动的 onStop() 函数时,必须立即释放安全解码器。安全解码器用于视频播放,因此需要解码器的应用将始终具有一个活动和用于渲染的表面。一旦此活动停止,应释放资源,以便其他应用获取安全管道的单个实例。

唤醒锁要求

一般来说,您可以预期所有 Fire TV 设备都由墙上电源供电(而不是电池供电),因此您可以跳过应用的功耗和性能管理。例如,您不需要保持 CPU 处于打开状态、调暗屏幕、管理高效的网络使用情况或 WIFI 锁。但是,您需要遵循保持设备处于唤醒状态开发者页面中的说明来管理唤醒锁。

要求 6.1: 保持设备处于唤醒状态

在预期播放时,您必须保持设备处于唤醒状态。这通常意味着您的应用设置活动标记 FLAG_KEEP_SCREEN_ON 或通过获取 FULL_WAKE_LOCK 来实现动态唤醒锁管理。

注意:​ 使用 FLAG_KEEP_SCREEN_ON 时,您必须在暂停播放时取消设置该标记。

要求 6.2: 释放唤醒锁

如果播放因任何原因未处于活动状态,则必须释放唤醒锁。(如果影片结束、显示缓冲屏幕或失去音频焦点,则播放可能不会处于活动状态。) 您的应用预计会让设备屏幕保护程序启动(超时后),然后让设备进入睡眠状态。

输出状态更改要求

当输出状态发生变化时,应用必须遵守以下要求。

要求 7.1: HDMI 断开连接

当您收到 HDMI 断开事件时,必须暂停媒体播放。大多数应用应该不会在没有屏幕的情况下继续播放(不必要的 4K 视频播放的数据使用量可能非常大)。即使仅音频应用也应在 HDMI 接收器断开连接时暂停播放。

注意:​ 当显示器的输入从 Fire TV 更改为其他内容时,也会发送 HDMI 事件。当 HDMI 电缆重新连接到显示器时,大多数应用都应该保持暂停状态。

要求 7.2: 从蓝牙接收器切换到 HDMI 输出

耳机通常用于以较高音量使用,而电视扬声器和音响系统通常设置为低得多的级别。当用户断开正在使用的蓝牙耳机的连接时,音频流将自动重新路由到扬声器。用户通常期望音频和视频应用暂停,以避免音量级别的突然变化。为了支持这种情况,您的应用应该处理意图 ACTION_AUDIO_BECOMING_NOISY

要求 7.3: 切换到蓝牙输出

直接输出编码音频流的应用(例如,杜比直通)必须侦听音频设备的更改。编码的音频流可以发送到 HDMI 输出(电视机和 AV 接收器支持大多数杜比标准),而无需在 Fire TV 上进行解码。但是,当打开蓝牙耳机时,音频流会自动重新路由到耳机,并且蓝牙标准不允许将编码的音频流定向到这些设备。因此,应用必须为设备功能的变化做好准备,并采取适当的行动。关于如何识别这些音频设备的变化以及如何对变化作出反应,有多个选项。

要接收通知,应用可以注册 AudioDeviceCallbacks(在 API 级别 23 中引入)或注册意图 AudioManager.ACTION_HDMI_AUDIO_PLUG(后者速度较慢并占用较多资源,因此建议使用 AudioDeviceCallback)。

为了应对更改,除其他选项外,应用还可以切换到 PCM 输入流或关闭直通(通过使用 MediaCodec,编码的输入流将被解码为 PCM);蓝牙耳机将能够正确播放内容。

要求 7.4: 在不同 HDMI 输出之间切换

依赖 PCM 输出以外的输出(如杜比或 DTS 等编码)的应用必须检查在 HDMI 断开连接和重新连接事件之间接收器的功能是否发生了变化。如果功能已更改,它们必须重新创建音频对象。

用户可以从支持各种音频编码的显示器拔下 HDMI 线缆,并将其插回支持不同编码的显示器。功能的变化可能需要您的应用调整其音频输出。

其他要求

要求 8.1: 不修改全局设备状态

不要使用影响设备全局状态的 API,例如,AudioManger.setStreamVolume()。尝试更新音量的应用应使用其 AudioTrack 或 MediaPlayer 实例的 setVolume() API,而不是旨在影响设备所有应用的 AudioManager API。

要求 8.2: 测试复杂场景

考虑先前描述的要求的复杂方案。如果您正确处理 AudioFocus 事件和安卓生命周期事件,保持 MediaSession,遵循 HDMI 连接和断开连接状态等,您的应用应能够处理任意复杂的用例,如下所示:

  1. 在应用中开始播放。
  2. 启动语音搜索。
  3. 从显示器上拔下 HDMI 线缆。
  4. 按遥控器上的“后退”以从语音搜索返回到您的应用。
  5. 打开之前配对的蓝牙耳机。
  6. 将 HDMI 电缆重新插入显示器。

此时,大多数应用应等待处于暂停状态的用户交互。所有用户界面显示都应反映暂停状态。例如,遥控器应用应禁用暂停按钮。应将音频路由到蓝牙耳机,同时避免杜比直通输出。