无线电控制实现

无线电控制实现基于 MediaSessionMediaBrowse,这使得媒体应用和语音助理应用能够控制无线电。如需了解详情,请参阅 developer.android.com 上的 为车载系统构建媒体应用

car-broadcastradio-support 库的 packages/apps/Car/libs 中提供了媒体浏览树实现。此库还包含 ProgramSelector 的扩展程序,用于与 URI 之间进行转换。建议无线电实现使用此库来构建关联的浏览树。

媒体来源切换器

为了在无线电和媒体中显示的其他应用之间提供无缝过渡,car-media-common 库包含应集成到无线电应用中的类。MediaAppSelectorWidget 可以包含在无线电应用的 XML 中(参考媒体应用和无线电应用中使用的图标和下拉列表)

<com.android.car.media.common.MediaAppSelectorWidget
    android:id="@+id/app_switch_container"
    android:layout_width="@dimen/app_switch_widget_width"
    android:layout_height="wrap_content"
    android:background="@drawable/app_item_background"
    android:gravity="center" />

此微件启动 AppSelectionFragment,其中会显示可切换到的媒体来源列表。如果需要提供的 UI 以外的其他 UI,您可以创建一个自定义微件,以便在应显示切换器时启动 AppSelectionFragment

AppSelectionFragment newFragment = AppSelectionFragment.create(widget,
            packageName, fullScreen);
    newFragment.show(mActivity.getSupportFragmentManager(), null);

参考无线电应用实现(位于 packages/apps/Car/Radio 中)中提供了一个示例实现。

详细控制规范

MediaSession(通过 MediaSession.Callback)接口为当前正在播放的无线电节目提供了控制机制

  • onPlayonStop。(取消)静音无线电播放。
  • onPause。时移暂停(如果支持)。
  • onPlayFromMediaId。播放顶层文件夹中的任何内容。例如,“播放 FM”或“播放无线电”。
  • onPlayFromUri。播放特定频率。例如,“播放 88.5 FM”。
  • onSkipToNextonSkipToPrevious。调到下一个或上一个电台。
  • onSetRating。添加到或从“收藏夹”中添加或移除。

MediaBrowser 通过三种类型的顶层目录公开可调谐的 MediaItem

  • 可选节目(电台)。此模式通常由双调谐器无线电使用,以指示用户所在位置所有可用的可调谐无线电台。
  • 收藏夹。添加到“收藏夹”列表的无线电节目,有些可能不可用(超出接收范围)。
  • 频段频道。当前区域中所有物理上可能的频道(87.9、88.1、88.3、88.5、88.7、88.9、89.1 等)。每个频段都有一个单独的顶层目录。
MediaBrowserService tree structure
图 2. MediaBrowserService 树结构

每个文件夹(AM/FM/节目)中的每个元素都是一个 MediaItem,其 URI 可与 MediaSession 结合使用进行调谐。每个顶层文件夹(AM/FM/节目)都是一个 MediaItem,其 mediaId 可与 MediaSession 结合使用以触发播放,并且由 OEM 自行决定。例如,“播放 FM”、“播放 AM”和“播放无线电”都是非特定的无线电查询,它们使用 mediaId 发送到 OEM 无线电应用。无线电应用可以自行决定从通用请求和 mediaId 中播放什么内容。

MediaSession

鉴于没有暂停广播流的概念,“播放”、“暂停”和“停止”操作并非始终适用于无线电。对于无线电,“停止”操作与将流静音相关联,而“播放”操作与取消静音相关联。

某些无线电调谐器(或应用)提供通过缓存内容并在稍后播放来模拟广播流暂停的功能。在这种情况下,请使用 onPause

从 mediaId 和 URI 播放操作旨在调谐到从 MediaBrowser 接口获取的电台。mediaId 是无线电应用提供的任意字符串,用于施加唯一(因此给定的 ID 仅指向一个项目)且稳定(因此给定的项目在整个会话中具有相同的 ID)的值,以便识别给定的电台。URI 将采用明确定义的架构。简而言之,URI 化的 ProgramSelector 形式。虽然这保留了唯一性属性,但它不必是稳定的,尽管当电台移动到不同的频率时,它可以更改。

根据设计,不使用 onPlayFromSearch。客户端(配套应用)有责任从 MediaBrowser 树中选择搜索结果。将该责任转移到无线电应用会增加复杂性,需要在字符串查询应如何显示方面达成正式合同,并导致不同硬件平台上的用户体验不一致。

注意:无线电应用不包含任何额外信息,这些信息可能有助于搜索客户端未通过 MediaBrowser 接口公开的电台名称。

跳到下一个或上一个电台取决于当前上下文

  • 当应用调谐到“收藏夹”列表中的电台时,应用可以从“收藏夹”列表中移动到下一个电台。
  • 收听“节目”列表中的电台可能会导致调谐到下一个可用的电台,并按频道号排序。
  • 收听任意频道可能会导致调谐到下一个物理频道,即使没有广播信号也是如此。

无线电应用会处理这些操作。

错误处理

TransportControls 操作(“播放”、“停止”和“下一个”)不提供有关操作是否成功的反馈。指示错误的唯一方法是将 MediaSession 状态设置为 STATE_ERROR 并显示错误消息。

无线电应用必须处理这些操作,并执行它们或设置错误状态。如果执行“播放”命令不是立即的,则播放状态应更改为 STATE_CONNECTING(在直接调谐的情况下)或 STATE_SKIPPING_TO_PREVIOUS NEXT,同时正在执行命令。

客户端应观看 PlaybackState 并验证会话是否将当前节目更改为请求的节目或进入错误状态。STATE_CONNECTING 不得超过 30 秒。但是,直接调谐到给定的 AM/FM 频率应更快地执行。

添加和移除收藏夹

MediaSession 具有评分支持,可用于控制“收藏夹”。使用 RATING_HEART 类型的评分调用的 onSetRating 会将当前调谐的电台添加到“收藏夹”列表或从中移除。

与旧版预设相反,此模型假定为无序且无限制的“收藏夹”列表,其中每个保存的收藏夹都分配给一个数字槽(通常为 1 到 6)。因此,基于预设的系统与 onSetRating 操作不兼容。

MediaSession API 的限制在于,只能添加或移除当前调谐到的电台。例如,必须先选择项目,然后才能将其移除。这只是 MediaBrowser 客户端(如配套应用)的限制。无线电应用没有类似的限制。当应用不支持“收藏夹”时,此部分是可选的。

MediaBrowser

为了表达哪些频率或物理频道名称(当调谐到任意频道适用于给定的无线电技术时)对给定区域有效,列出了每个频段的所有有效频道(频率)。在美国区域,这相当于 101 个 FM 频道(范围为 87.8 到 108.0 MHz 范围,使用 0.2MHz 间距)和 117 个 AM 频道(范围为 530 到 1700 kHz,使用 10kHz 间距)。由于高清无线电使用相同的频道空间,因此不会单独呈现。

当前可用的无线电节目列表是扁平的,因为它不允许显示方案,例如按直接音频广播 (DAB) 组合分组。

“收藏夹”列表中的条目可能无法调谐。例如,如果给定节目超出范围。无线电应用可能会或可能不会预先检测到条目是否可以调谐。如果是这样,它可能不会将条目标记为可播放。

为了识别顶层文件夹,应用了与蓝牙相同的机制。也就是说, MediaDescription 对象的 Extras 软件包包含一个特定于调谐器的字段,就像蓝牙使用 EXTRA_BT_FOLDER_TYPE 一样。对于广播无线电,这导致在公共 API 中定义以下新字段

  • EXTRA_BCRADIO_FOLDER_TYPE = "android.media.extra.EXTRA_BCRADIO_FOLDER_TYPE"。以下值之一
    • BCRADIO_FOLDER_TYPE_PROGRAMS = 1。当前可用的节目。
    • BCRADIO_FOLDER_TYPE_FAVORITES = 2。“收藏夹”。
    • BCRADIO_FOLDER_TYPE_BAND = 3。给定频段的所有物理频道。

    无需定义任何特定于无线电的自定义元数据字段,因为所有相关数据都适合现有的 MediaBrowser.MediaItem 方案

    • 节目名称(RDS PS、DAB 服务名称)。 MediaDescription.getTitle
    • FM 频率。 URI(请参阅ProgramSelector)或 MediaDescription.getTitle(如果条目位于 BROADCASTRADIO_FOLDER_TYPE_BAND 文件夹中)。
    • 特定于无线电的标识符(RDS PI、DAB SId)。 MediaDescription.getMediaUri 已解析为 ProgramSelector。

    通常,无需为当前节目或“收藏夹”列表中的条目获取 FM 频率(因为客户端应在媒体 ID 上运行)。但是,如果出现此类需求(例如,为了显示目的),它会出现在 URI 中,并且可以解析为 ProgramSelector。也就是说,不建议使用 URI 来选择当前会话中的项目。有关详情,请参阅 ProgramSelector

    为了避免性能或 binder 相关的问题,MediaBrowser 服务必须支持分页

    注意:默认情况下,分页在 onLoadChildren() 变体中默认实现,无需选项处理。

    所有类型的列表(原始频道、找到的节目和收藏夹)中的相关条目可能具有不同的 mediaId(这取决于无线电应用;支持库将使它们不同)。URI(以 ProgramSelector 形式)在原始频道和找到的节目之间有所不同(大多数情况下,除非 FM 没有 RDS),但在找到的节目和收藏夹之间大多相同(例如,除非 AF 已更新)。

    为不同类型的列表中的条目设置不同的 mediaId 可以对它们执行不同的操作。您可以根据最近选择的 MediaItem 的文件夹,在 onSkipToNext 上遍历“收藏夹”列表或“所有节目”列表(请参阅 MediaSession)。

    特殊调谐操作

    “节目”列表允许用户调谐到特定电台,但不允许用户发出诸如“调谐到 FM”之类的通用请求,这可能会导致调谐到 FM 频段上最近收听的电台。

    为了支持此类操作,某些顶层目录设置了 FLAG_PLAYABLE 标志(以及用于文件夹的 FLAG_BROWSABLE)。

    操作 调谐到 如何发出
    播放无线电 任何无线电频道 startService(ACTION_PLAY_BROADCASTRADIO)



    playFromMediaId(MediaBrowser.getRoot())
    播放 FM 任何 FM 频道 从 FM 频段的 mediaId 播放。

    要调谐到哪个节目由应用决定。这通常是给定列表中最近调谐到的频道。有关 ACTION_PLAY_BROADCASTRADIO 的详情,请参阅通用播放 Intent

    发现和服务连接

    PackageManager 可以直接找到为广播无线电树提供服务的 MediaBrowserService。为此,请使用 ACTION_PLAY_BROADCASTRADIO Intent(请参阅通用播放 Intent)和 MATCH_SYSTEM_ONLY 标志调用 resolveService。要查找为无线电服务的所有服务(可能不止一个;例如,单独的 AM/FM 和卫星),请使用 queryIntentServices

    已解析的服务也处理 android.media.browse.MediaBrowserService 绑定 Intent。这已通过 GTS 验证。

    要连接到选定的 MediaBrowserService,请为给定的服务组件和 connect 创建 MediaBrowser 实例。建立连接后,可以通过 getSessionToken 获取 MediaSession 的句柄。

    无线电应用可以在其服务的 onGetRoot 实现中限制允许连接的客户端软件包。应用应允许系统应用在不列入许可名单的情况下连接。如需了解有关许可名单的详情,请参阅接受 Assistant 应用软件包和签名

    如果特定于来源的应用(例如,无线电应用)安装在没有此类来源支持的设备上,它仍会声明自己正在处理 ACTION_PLAY_BROADCASTRADIO Intent,但其 MediaBrowser 树将不包含特定于无线电的标记。因此,想要检查给定来源是否在设备上可用的客户端必须

    1. 发现无线电服务(为 ACTION_PLAY_BROADCASTRADIO 调用 resolveService)。
    2. 创建 MediaBrowser,然后连接到它。
    3. 确定是否存在带有 EXTRA_BCRADIO_FOLDER_TYPE 附加信息的 MediaItem

    注意: 在大多数情况下,客户端必须扫描所有可用的 MediaBrowser 树,以检测给定设备的所有可用来源。

    频段名称

    频段列表由一组顶层目录表示,这些目录的文件夹类型标记设置为 BCRADIO_FOLDER_TYPE_BAND。它们的 MediaItem 的标题是表示频段名称的本地化字符串。在大多数情况下,它将与英文翻译相同,但客户端不能依赖于该假设。

    为了为查找某些频段提供稳定的机制,为频段文件夹添加了一个额外的标记 EXTRA_BCRADIO_BAND_NAME_EN。这是频段的非本地化名称,并且只能采用以下预定义的值之一

    • AM
    • FM
    • DAB

    如果频段不在列表中,则不应设置频段名称标记。但是,如果频段在列表中,则必须设置标记。高清无线电不枚举单独的频段,因为它使用与 AM/FM 相同的底层介质。

    通用播放 Intent

    每个专用于播放给定来源(如无线电或 CD)的应用都必须处理通用的播放 Intent,以从可能处于非活动状态(例如,启动后)开始播放某些内容。如何选择要播放的内容由应用决定,但通常是最近播放的无线电节目或 CD 曲目。为每个音频来源定义了单独的 Intent

    • android.car.intent.action.PLAY_BROADCASTRADIO
    • android.car.intent.action.PLAY_AUDIOCD:CD-DA 或 CD-Text
    • android.car.intent.action.PLAY_DATADISC:光盘数据光盘,如 CD/DVD,但不是 CD-DA(可能是混合模式 CD)
    • android.car.intent.action.PLAY_AUX:未指定哪个 AUX 端口
    • android.car.intent.action.PLAY_BLUETOOTH
    • android.car.intent.action.PLAY_USB:未指定哪个 USB 设备
    • android.car.intent.action.PLAY_LOCAL:本地媒体存储(内置闪存)

    选择 Intent 用于通用播放命令,因为它们一次解决了两个问题:通用播放命令本身和服务发现。拥有此类 Intent 的额外好处是可以执行如此简单的操作,而无需打开 MediaBrowser 会话。

    服务发现实际上是使用这些 Intent 解决的更重要的问题。服务发现的过程很容易且明确(请参阅发现和服务连接)。

    为了使某些客户端实现更容易,还有另一种发出此类“播放”命令的方法(无线电应用也必须实现):使用根节点的 rootId(用作 mediaId)发出 playFromMediaId。虽然 根节点 并非旨在可播放,但其 rootId 是一个任意字符串,可以使其可作为 mediaId 使用。但是,客户端不需要理解这种细微差别。

    ProgramSelector

    虽然 mediaId 足以从 MediaBrowserService 中选择频道,但它会绑定到会话,并且在提供商之间不一致。在某些情况下,客户端可能需要绝对指针(例如,绝对频率)以在会话和设备之间维护它。

    在数字无线电广播时代,仅频率不足以调谐到特定电台。因此,请使用 ProgramSelector 调谐到模拟或数字频道。ProgramSelector 由两部分组成

    • 主标识符。给定无线电台的唯一且稳定的标识符,它不会更改,但可能不足以调谐到该电台。例如,RDS PI 代码,在美国可以转换为呼号。
    • 辅助标识符。有助于调谐到该电台的其他标识符(例如,频率),可能包括来自其他无线电技术的标识符。例如,DAB 电台可能具有模拟广播回退。

    为了使 ProgramSelector 适合基于 MediaBrowserMediaSession 的解决方案,请定义 URI 架构以对其进行序列化。架构定义如下

    broadcastradio://program/<primary ID type>/<primary ID>?
    <secondary ID type>=<secondary ID>&<secondary ID type>=<secondary ID>

    在此示例中,辅助标识符部分(问号 (?) 之后)是可选的,可以移除以提供稳定的标识符以用作 mediaId。例如

    • broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=88500&AMFM_FREQUENCY=103300
    • broadcastradio://program/AMFM_FREQUENCY/102100
    • broadcastradio://program/DAB_SID_EXT/14895264?RDS_PI=1234

    program 的授权部分(又名主机)为将来的架构扩展提供了一些空间。标识符类型字符串被精确地指定为其在 HAL 2.x IdentifierType 定义中的名称,并且值格式为十进制或十六进制(带有 0x 前缀)数字。

    所有供应商特定的标识符都由 VENDOR_ 前缀表示。例如,VENDOR_0 用于 VENDOR_STARTVENDOR_1 用于 VENDOR_START 加 1。此类 URI 特定于生成它们的无线电硬件,并且不能在不同 OEM 制造的设备之间传输。

    这些 URI 必须分配给顶层无线电文件夹下的每个 MediaItem。此外,MediaSession 必须同时支持 playFromMediaIdplayFromUri。但是,URI 主要用于无线电元数据提取(如 FM 频率)和持久存储。不能保证 URI 将适用于所有媒体项目(例如,当框架尚不支持主要 ID 类型时)。另一方面,Media ID 始终有效。不建议客户端使用 URI 从当前 MediaBrowser 会话中选择项目。而是使用 playFromMediaId。也就是说,对于服务应用来说,它不是可选的,并且缺少 URI 保留用于有充分理由的情况。

    初始设计在架构部分之后使用单冒号而不是 :// 序列。但是,前者不受 android.net.Uri 用于绝对分层 URI 引用的支持。

    其他来源类型

    可以类似地处理其他音频来源。例如,辅助输入和音频 CD 播放器。

    单个应用可以服务于多种类型的来源。在这种情况下,建议您为每种类型的来源创建一个单独的 MediaBrowserService。即使在设置了多个服务来源/MediaBrowserService 的情况下,也强烈建议在单个应用中拥有单个 MediaSession。

    音频 CD

    类似于音频 CD,提供此类光盘的应用会公开 MediaBrowser,其中包含一个可浏览的条目(如果系统有 CD 换碟机,则可以有多个),该条目进而包含给定 CD 的所有曲目。如果系统不了解每张 CD 上的曲目(例如,当所有光盘一次性插入盒匣且系统未读取所有光盘时),则整张光盘的 MediaItem 将仅为 PLAYABLE,而不是 BROWSABLEPLAYABLE。如果给定插槽中没有光盘,则该项目既不是 PLAYABLE 也不是 BROWSABLE(但每个插槽必须始终存在于树中)。

     Audio CD tree structure
    图 3. 音频 CD 树结构。

    这些条目标记方式与广播电台文件夹类似;它们将包含 MediaDescription API 中定义的其他 extra 字段

    • EXTRA_CD_TRACK:对于音频 CD 上的每个 MediaItem,从 1 开始的曲目号。
    • EXTRA_CD_DISK:从 1 开始的光盘号。

    对于启用 CD-Text 的系统和兼容光盘,顶层 MediaItem 将具有光盘标题。同样,曲目的 MediaItem 将具有曲目标题。

    辅助输入

    提供辅助输入的应用会公开一个 MediaBrowser 树,其中包含一个条目(或多个条目,当存在多个端口时)来表示 AUX 输入端口。相应的 MediaSession 获取其 mediaId,并在收到 playFromMediaId 请求后切换到该源。

    AUX tree structure
    图 4. AUX 树结构。

    每个 AUX MediaItem 条目都将具有一个 extra 字段 EXTRA_AUX_PORT_NAME,设置为不包含“AUX”短语的端口的非本地化名称。例如,“AUX 1”应设置为“1”,“AUX front”应设置为“front”,“AUX”应设置为空字符串。在非英语语言环境中,名称标记将保持相同的英语字符串。与 EXTRA_BCRADIO_BAND_NAME_EN 不同,这些值是 OEM 定义的,不受预定义列表的约束。

    如果硬件可以检测到连接到 AUX 端口的设备,则仅当输入已连接时,硬件才应将 MediaItem 标记为 PLAYABLE。如果此端口未连接任何设备,则仍应枚举硬件(但不是 PLAYABLE)。如果硬件不具备此功能,则 MediaItem 必须始终设置为 PLAYABLE

    Extra 字段

    定义以下字段

    • EXTRA_CD_TRACK = "android.media.extra.CD_TRACK"
    • EXTRA_CD_DISK = "android.media.extra.CD_DISK"
    • EXTRA_AUX_PORT_NAME = "android.media.extra.AUX_PORT_NAME"

    客户端需要查看顶层 MediaItem,以查找设置了 EXTRA_CD_DISKEXTRA_AUX_PORT_NAME extra 字段的元素。

    详细示例

    以下示例介绍了此设计中源类型的 MediaBrowser 树结构。

    广播电台 MediaBrowserService(处理 ACTION_PLAY_BROADCASTRADIO

    • 电台(可浏览)EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_PROGRAMS
      • BBC One(可播放)URI:broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=90500
      • ABC 88.1(可播放)URI:broadcastradio://program/RDS_PI/5678?AMFM_FREQUENCY=88100
      • ABC 88.1 HD1(可播放)URI:broadcastradio://program/HD_STATION_ID_EXT/158241DEADBEEF?AMFM_FREQUENCY=88100&RDS_PI=5678
      • ABC 88.1 HD2(可播放)URI:broadcastradio://program/HD_STATION_ID_EXT/158242DEADBEFE
      • 90.5 FM(可播放)- 没有 RDS 的 FMURI:broadcastradio://program/AMFM_FREQUENCY/90500
      • 620 AM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/620
      • BBC One(可播放)URI:broadcastradio://program/DAB_SID_EXT/1E24102?RDS_PI=1234
    • 收藏夹(可浏览、可播放)EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_FAVORITES
      • BBC One(可播放)URI:broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=101300
      • BBC Two(不可播放)URI:broadcastradio://program/RDS_PI/1300?AMFM_FREQUENCY=102100
    • AM(可浏览、可播放):EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="AM"
      • 530 AM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/530
      • 540 AM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/540
      • 550 AM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/550
    • FM(可浏览、可播放):EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="FM"
      • 87.7 FM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/87700
      • 87.9 FM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/87900
      • 88.1 FM(可播放)URI:broadcastradio://program/AMFM_FREQUENCY/88100
    • DAB(可播放):EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="DAB"

    音频 CD MediaBrowserService(处理 ACTION_PLAY_AUDIOCD

    • 光盘 1(可播放)EXTRA_CD_DISK=1
    • 光盘 2(可浏览、可播放)EXTRA_CD_DISK=2
      • 曲目 1(可播放)EXTRA_CD_TRACK=1
      • 曲目 2(可播放)EXTRA_CD_TRACK=2
    • 我的音乐 CD(可浏览、可播放)EXTRA_CD_DISK=3
      • All By Myself(可播放)EXTRA_CD_TRACK=1
      • Reise, Reise(可播放)EXTRA_CD_TRACK=2
    • 空插槽 4(不可播放)EXTRA_CD_DISK=4

    AUX MediaBrowserService(处理 ACTION_PLAY_AUX

    • AUX front(可播放)EXTRA_AUX_PORT_NAME="front"
    • AUX rear(可播放)EXTRA_AUX_PORT_NAME="rear"