电台控制实现

电台控制实现基于 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" />

此 widget 会启动 AppSelectionFragment,后者会显示一个 可切换到的媒体来源列表。如果需要其他界面 您可以创建自定义小部件,在调用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 树结构
图 2.MediaBrowserService 树结构

每个文件夹 (AM/FM/Programs) 中的每个元素都是一个 MediaItem,具有可用于 来调整 MediaSession每个顶级文件夹 (AM/FM/Programs) 都是一个 MediaItem,其 mediaId 符合 可与 MediaSession 搭配使用以触发播放,具体由 OEM 自行决定。对于 例如,“播放 FM”“播放 AM”以及“播放电台”都是使用 要发送到 OEM 电台应用的 mediaId。从电台应用播放什么内容由电台应用决定 通用请求和 mediaId。

MediaSession

由于还没有暂停广播流的概念,因此播放、暂停和停止操作 并不总是适用于电台使用电台时,“停止”操作与将直播静音 而 Play 则与取消静音有关。

某些无线电调谐器(或应用)提供以下功能来模拟广播流暂停 缓存内容,以便稍后播放。在这种情况下,请使用 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 支持评分功能,可用于控制收藏夹。onSetRating 使用类型的评分进行调用 RATING_HEART 在“收藏”列表中添加或删除当前调到的电台。

与旧版预设相反,此模型假定收藏夹是无序、无界限的 列表,当每个保存的收藏被分配到数字槽(通常为 1 到 6)时。 因此,基于预设的系统与 onSetRating 不兼容 操作。

MediaSession API 的局限性在于,只有当前调到的电台 添加或删除项目例如,必须先选择内容,然后才能 可以移除这只是 MediaBrowser 客户端的限制,例如 配套应用。电台应用没有类似的限制。此部分为可选内容 当应用不支持收藏夹时。

MediaBrowser

为了表示哪些频率或实体频道名称(调谐时 适用于给定无线电技术的任意频道)均适用于 则会列出每个频段的所有有效信道(频率)。 在美国地区,这相当于有 101 个 FM 频道,范围为 87.8 到 87.8 108.0 MHz 范围(使用 0.2MHz 间隔)和 117 AM 信道,在 530 范围内 1700 kHz(间隔为 10 kHz)。HD 电台使用相同的信道空间 不会单独显示。

当前可播放的电台节目列表是空的,不允许播放 显示架构,例如按直接音频广播 (DAB) 集成分组。

“收藏夹”列表中的条目可能无法正常播放。例如,如果给定的 程序超出了范围。电台应用不一定能检测该条目 可以预先调整到在这种情况下,电台应用可能不会将条目标记为可播放。

为了识别顶级文件夹,系统会采用与蓝牙相同的机制。 也就是说, MediaDescription 的 Extra 捆绑包 对象包含调谐器专用字段,就像蓝牙处理 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。

    通常,无需针对 当前节目或收藏夹列表(因为客户端应在媒体上操作) ID)。不过,如果出现此类需求(例如,出于展示目的), 存在于 URI 中,并且可以解析为 ProgramSelector。 尽管如此,我们不建议您使用 URI 在当前所选内容中选择项目 会话。有关详情,请参阅 ProgramSelector

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

    注意:默认情况下,在 onLoadChildren() 变体。

    所有类型列表(原始频道、已找到的节目和 收藏)可能具有不同的 mediaId(由电台应用决定;支持 会有所不同)。URI(采用 ProgramSelector 形式) 原始频道与大多数情况下找到的节目之间有什么区别(FM 除外) 但找到的节目和收藏的节目之间大致相同(除了 例如,在更新广告资源预测时)。

    为来自不同类型列表的条目使用不同的 mediaId 会导致 可以对其执行不同的操作。您可以遍历收藏列表或 onSkipToNext上的“所有程序”列表,具体取决于 已选择MediaItem(请参阅 MediaSession)。

    特殊调谐操作

    节目列表可让用户调到特定电台,但不允许用户 发出一些一般性请求(例如“调到 FM”),这可能会导致调谐到 收听 FM 频段的电台。

    为了支持此类操作,部分顶层目录包含 FLAG_PLAYABLE 标志设置(以及 FLAG_BROWSABLE) 表示文件夹)。

    操作 调谐目标 如何发出
    播放电台 任意电台频道 startService(ACTION_PLAY_BROADCASTRADIO)



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

    具体调到哪个节目由应用决定。这是 通常是指定列表中最近调到的频道。如需详细了解 ACTION_PLAY_BROADCASTRADIO,参见 常规播放 intent

    发现和服务连接

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

    已解析的服务处理 android.media.browse.MediaBrowserService 绑定 意图。此操作已通过 GTS 验证。

    如需连接到所选的 MediaBrowserService, 创建 给定服务组件的 MediaBrowser 实例和 connect。 建立连接后,可以通过 getSessionToken

    电台应用可以限制允许在 onGetRoot 服务的实现应用应允许系统应用进行连接 而无需列入白名单如需详细了解白名单,请参阅 接受 Google 助理应用软件包和签名

    如果特定于来源的应用(例如,电台应用)安装在 设备,则仍会通告自己处理 ACTION_PLAY_BROADCASTRADIO intent,但其 MediaBrowser 树 不包含电台专用标记。因此,客户端愿意检查给定的 来源在设备上可用,必须:

    1. 探索电台服务(为以下号码调用 resolveServiceACTION_PLAY_BROADCASTRADIO)。
    2. 创建 MediaBrowser,然后与其连接。
    3. 通过 EXTRA_BCRADIO_FOLDER_TYPE 确定是否存在 MediaItem extra。

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

    频段名称

    频段列表由一组具有文件夹类型的顶层目录表示 代码设为 BCRADIO_FOLDER_TYPE_BAND。对方的MediaItem title 是表示频段名称的本地化字符串。在大多数情况下 这与英语翻译相同,但客户不能依赖这种假设。

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

    • AM
    • FM
    • DAB

    如果频段不在此列表中,则不应设置频段名称标记。 但是,如果频段在列表中,则必须设置标记。HD 电台不能 列举单独的频段,因为它使用与 AM/FM 相同的底层媒介。

    常规播放 intent

    每个专用于播放指定来源(如电台或 CD)的应用都必须处理 常规 play 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:本地媒体存储空间 (内置 Flash)

    之所以选择将 intent 用于常规播放命令,是因为它们解决 同时解决两个问题:常规的播放命令本身和服务发现。 拥有此类意图的另一个好处是, 无需打开 MediaBrowser 会话的简单操作。

    服务发现实际上是通过这些服务来解决的 intent。通过这种方式,服务发现的过程简单且明确(请参阅 发现和服务连接)。

    为了简化某些客户端实现,有一种替代方法 发出此类“播放”命令(该命令也必须由电台应用实现): 发出 playFromMediaId 并传入根节点的 rootId(用作 mediaId)。虽然 根节点 并非为可播放,其 rootId 是任意字符串 它可作为 mediaId 使用。不过,客户端不是必需的 来理解这种细微差别。

    ProgramSelector

    虽然使用 mediaId 足以从 MediaBrowserService,就会绑定到会话,并且不一致 提供程序之间进行通信在某些情况下,客户端可能需要绝对指针(例如 绝对频率),以便在会话和设备之间维持此目标。

    在数字无线电广播时代,单纯频率是不够的, 调到特定电台。因此,请使用 ProgramSelector 进行调谐 到模拟或数字频道ProgramSelector 由两部分组成:

    • 主要标识符。指定电台的唯一且稳定的标识符 虽然没有改变,但可能不足以调到该电台。例如: RDS PI 代码,在美国可能会转换为呼号。
    • 辅助标识符。用于调参的其他标识符 发送到该电台的标识符(例如频率),其中可能包括来自其他电台的标识符 无线电技术例如,DAB 电台可能具有模拟广播回退机制。

    为了使 ProgramSelector 适合 MediaBrowser- 或 基于 MediaSession 的解决方案,请定义 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 的权威部分(也称为主机)为 扩展。精确指定标识符类型字符串 IdentifierType 作为其名称(在 HAL 2.x 定义中)和值 格式是一个十进制或十六进制(带 0x 前缀)数字。

    所有供应商专用标识符都由 VENDOR_ 表示, 前缀。例如,VENDOR_0 表示 VENDOR_STARTVENDOR_1 x VENDOR_START + 1。此类 URI 仅适用于 生成它们的无线电硬件,不能在设备之间传输 不同原始设备制造商 (OEM) 制造的产品。

    必须将这些 URI 分配给顶级电台下的每个 MediaItem 文件夹中。此外,MediaSession 必须同时支持 playFromMediaIdplayFromUri。不过,URI 主要用于电台 元数据提取(例如 FM 频率)和永久性存储。没有任何 可以保证 URI 可用于所有媒体项(例如,当主 ID 类型不受框架支持)。另一方面,媒体 ID 始终有效。 不建议客户端使用 URI 从 当前 MediaBrowser 会话触发。相反,应使用 playFromMediaId。也就是说, 对于服务应用而言不是可选的,并且保留缺失的 URI 以用于合理情形。

    最初的设计使用一个冒号来代替 :// 序列 。不过,Google Cloud 不支持 android.net.Uri,适用于绝对分层 URI 引用。

    其他来源类型

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

    单个应用可以提供多种类型的来源。在这种情况下, 建议为 每种类型的来源即使在具有多个提供的来源/MediaBrowserService 的设置中, 强烈建议在单个 应用。

    音频 CD

    与音频 CD 类似,提供此类磁盘的应用 公开具有单个可浏览条目(或多个,如果系统具有 CD 换碟机),而该 CD 会反过来包含指定 CD 的所有曲目。如果系统 并不了解每张 CD 上的曲目(例如,当所有磁盘 但未能读取所有墨盒的内容) 整个磁盘的 MediaItem 将只是 PLAYABLE,而不是 BROWSABLE + PLAYABLE。如果整个集群中没有磁盘 给定的广告位,则商品既不是 PLAYABLE,也不是 BROWSABLE (但每个广告位必须始终存在于树中)。

    音频 CD 树结构
    图 3. 音频 CD 树结构。

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

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

    对于支持 CD-Text 的系统和兼容磁盘,顶级 MediaItem 将 包含磁盘标题同样,曲目的 MediaItem 将包含 曲目的标题。

    辅助输入

    提供辅助输入的应用公开了具有单个(或多个 表示端口中的 AUX)。相应的 MediaSession 将获取其 mediaId,并在收到 playFromMediaId 请求后切换到该来源。

    AUX 树结构
    图 4. AUX 树结构。

    每个 AUX MediaItem 条目都有一个额外的字段 EXTRA_AUX_PORT_NAME 设置为端口的非本地化名称 没有“AUX”词组。例如:“AUX 1”应设置为“1”、“AUX” 前”移至“前端”和“AUX”一个空字符串。在非英语语言区域,名称为 则使用同一个英文字符串。对于 EXTRA_BCRADIO_BAND_NAME_EN,值由 OEM 定义,而非 预定义的列表

    如果硬件可以检测到连接到 AUX 端口的设备,则硬件应标记 作为 PLAYABLE 的 MediaItem。硬件应 如果没有任何连接,则仍会枚举(而非 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 个额外字段 。

    详细示例

    以下示例说明了 。

    广播电台 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 的 FM URI: 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"