Radio control implementation is based on MediaSession
and
MediaBrowse
, which enable Media and voice assistant apps to
control the radio. For more information, see
Build media apps for cars on developer.android.com.
A media browse tree implementation is provided in the car-broadcastradio-support
library in packages/apps/Car/libs
. This library also contains extensions of
ProgramSelector to convert to and from URI. It is recommended that radio implementations
use this library to build the associated browse tree.
Media source switcher
To provide a seamless transition between radio and other apps displayed in media,
the car-media-common library contains classes that should be integrated into the radio
app. MediaAppSelectorWidget
can be included in the XML for the radio app
(the icon and drop-down used in the reference media and radio apps):
<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" />
This widget launches the AppSelectionFragment
, which displays a
list of media sources that can be switched to. If a UI other than that provided is desired,
you can create a custom widget to launch the AppSelectionFragment
when the
switcher should be displayed.
AppSelectionFragment newFragment = AppSelectionFragment.create(widget, packageName, fullScreen); newFragment.show(mActivity.getSupportFragmentManager(), null);
A sample implementation is provided in the reference radio app implementation,
located in packages/apps/Car/Radio
.
Detailed control specifications
The MediaSession
(through MediaSession.Callback
)
interface provides control mechanisms for the currently playing radio program:
onPlay
,onStop
. (Un)mute radio playback.onPause
. Time-shifted pause (if supported).onPlayFromMediaId
. Play any content from a top-level folder. For example, "Play FM" or "Play Radio."onPlayFromUri
. Play a specific frequency. For example, "Play 88.5 FM."onSkipToNext
,onSkipToPrevious
. Tune to a next or previous station.onSetRating
. Add or remove to or from Favorites.
The MediaBrowser exposes a tunable MediaItem over three types of top-level directories:
- (Optional) Programs (stations). This mode is typically used by dual-tuner radios to indicate all available tuneable radio stations at the user's location.
- Favorites. Radio programs added to the Favorites list, some may be unavailable (out of reception range).
- Band channels. All physically possible channels in the current region (87.9, 88.1, 88.3, 88.5, 88.7, 88.9, 89.1 and so on). Every band has a separate top-level directory.
Each element in each of these folders (AM/FM/Programs) is a MediaItem with a URI that can be used with MediaSession to tune. Each top-level folder (AM/FM/Programs) is a MediaItem with a mediaId that can be used with MediaSession to trigger playback and is up to the discretion of the OEM. For example, "Play FM," "Play AM," and "Play Radio" are all non-specific radio queries that use a mediaId to send to the OEM radio app. It's up to the radio app to determine what to play from the generic request and the mediaId.
MediaSession
Given there is no concept of pausing a broadcast stream, the Play, Pause, and Stop actions do not always apply to radio. With radio, the Stop action is associated with muting the stream while Play is associated with removing the mute.
Some radio tuners (or apps) provide the ability to simulate a broadcast stream pause by
caching content and then playing it back later. In such cases, use onPause
.
Playing from mediaId and URI actions is intended to tune to a station fetched from the MediaBrowser interface. The mediaId is an arbitrary string provided by the radio app to impose a unique (so a given ID points to only one item) and stable (so a given item has the same ID through the whole session) value with which to identify a given station. The URI will be of a well-defined schema. In short, a URI-ized form of ProgramSelector. While this preserves the uniquity attribute, it need not be stable, although it can change when the station moves to a different frequency.
By design, onPlayFromSearch
is not used. Is is the responsibility of the client
(companion app) to select a search result from the MediaBrowser tree. Moving
that responsibility to the radio app would increase complexity, require formal contracts on how
string queries should appear, and result in an uneven user experience on different hardware
platforms.
Note: The radio app does not contain additional information that would be useful to search for a station name not exposed to the client through the MediaBrowser interface.
Skipping to the next or previous station depends on the current context:
- When an app is tuned to a station from the Favorites list, the app can move to the next station from the Favorites list.
- Listening to a station from the Program list may result in tuning to the next available station, sorted according to channel number.
- Listening to an arbitrary channel may result in tuning to the next physical channel, even when there is no broadcast signal.
The radio app handles these actions.
Error handling
TransportControls
actions (Play, Stop, and Next) doesn't provide feedback as to whether the action
succeeds or not. The only way to indicate an error is to set the MediaSession
state to
STATE_ERROR
with an error message.
The radio app must handle those actions and either execute them or set an error state.
If executing the Play command is not immediate, the playback state should be changed to
STATE_CONNECTING
(in case of direct tune) or
STATE_SKIPPING_TO_PREVIOUS
or
NEXT
while the command is being executed.
The client should watch the
PlaybackState
and verify that the session changed the current program to what was requested or entered into the
error state. STATE_CONNECTING
must not exceed 30s. However, a direct tune to a given
AM/FM frequency should perform much faster.
Add and remove favorites
MediaSession has rating support, which can be used to control Favorites. onSetRating
called with a rating of type
RATING_HEART
adds or removes the currently tuned station to or from the Favorites list.
Contrary to legacy presets, this model assumes an unordered and unbounded Favorites
list, when each saved favorite was allocated to a numerical slot (typically, 1 to 6).
As a result, preset-based systems would be incompatible with onSetRating
operation.
The limitation of the MediaSession API is that only the station currently tuned to can be added or removed. For example, items must be selected first before they can be removed. This is only a limitation of the MediaBrowser client, such as a companion app. The radio app is not similarly restricted. This portion is optional when an app doesn't support Favorites.
MediaBrowser
To express which frequencies or physical channel names (when tuning to an arbitrary channel is suitable for a given radio technology) are valid for a given region, all valid channels (frequencies) are listed for each band. In the US region, this amounts to 101 FM channels from in the range of 87.8 to 108.0 MHz range (using 0.2MHz spacing) and 117 AM channels in the range of 530 to 1700 kHz (using 10kHz spacing). Because HD radio uses the same channel space, it is not presented separately.
The list of currently available radio programs is flat in that this doesn't allow display schemes such as grouping by direct audio broadcast (DAB) ensemble.
Entries on the Favorite list may not be tunable. For example if a given program is out of range. The radio app may or may not detect if the entry can be tuned to beforehand. If so, it may not mark the entry as playable.
To identify top-level folders, the same mechanism used by Bluetooth is applied.
That is, an Extras bundle of the
MediaDescription
object contains a tuner-specific field just as Bluetooth does with
EXTRA_BT_FOLDER_TYPE
.
In the case of broadcast radio, this leads to defining the following new fields in the
public API:
EXTRA_BCRADIO_FOLDER_TYPE = "android.media.extra.EXTRA_BCRADIO_FOLDER_TYPE"
. One of the following values:BCRADIO_FOLDER_TYPE_PROGRAMS = 1
. Currently available programs.BCRADIO_FOLDER_TYPE_FAVORITES = 2
. Favorites.BCRADIO_FOLDER_TYPE_BAND = 3
. All physical channels for a given band.
There is no need to define any radio-specific custom metadata fields, as all the relevant data fits into the existing
MediaBrowser.MediaItem
scheme:- Program name (RDS PS, DAB service name).
MediaDescription.getTitle
. - FM frequency. URI (see
ProgramSelector) or
MediaDescription.getTitle
(if an entry is in theBROADCASTRADIO_FOLDER_TYPE_BAND
folder). - Radio-specific identifiers (RDS PI, DAB SId).
MediaDescription.getMediaUri
parsed to ProgramSelector.
Typically, there is no need to fetch FM frequency for an entry on the current program or Favorites list (as the client should operate on media IDs). However, if such a need were to arise (for example, for display purposes), it's present in the URI and can be parsed to
ProgramSelector
. That said, it's not recommended the URI be used to select items within the current session. For details, seeProgramSelector
.To avoid performance or binder-related issues, the MediaBrowser service must support pagination:
EXTRA_PAGE
EXTRA_PAGE_SIZE
- Extra parameters for
subscribe()
Note: By default, pagination is implemented by default in the
onLoadChildren()
variant without options handling.Related entries from all types of lists (raw channels, programs found and favorites) may have different mediaIds (it's up to the radio app; support library will have them different). The URIs (in ProgramSelector form) differ between raw channels and programs found in most cases (except for FM without RDS), but are mostly the same between programs found and favorites (except, for example, when AF was updated).
Having different mediaIds for entries from different types of lists makes it possible to take different actions on them. You can traverse either the Favorites list or the All Programs list on
onSkipToNext
, depending on the folder of recently selectedMediaItem
(see MediaSession).Special tune actions
Program list enables users to tune to a specific station, but does not allow users to make general requests such as "Tune to FM", which could result in tuning to a recently listened to station on the FM band.
To support such actions, some top-level directories have the
FLAG_PLAYABLE
flag set (along withFLAG_BROWSABLE
for folders).Action Tunes to How to issue Play radio Any radio channel startService(ACTION_PLAY_BROADCASTRADIO)
OR,
playFromMediaId(MediaBrowser.getRoot())
Play FM Any FM channel Play from the mediaId
of the FM band.The determination of which program to tune to is up to the app. This is typically the most recently tuned to channel from the given list. For details on
ACTION_PLAY_BROADCASTRADIO
, see General play intents.Discovery and service connection
PackageManager
can directly find the MediaBrowserService serving broadcast radio tree. To do so, callresolveService
with theACTION_PLAY_BROADCASTRADIO
intent (see General play intents) andMATCH_SYSTEM_ONLY
flag. To find all services that serve radio (there may be more than one; for example, separate AM/FM and satellite), usequeryIntentServices
.The resolved service handles the
android.media.browse.MediaBrowserService
bind intent, too. This is verified with GTS.To connect to the selected MediaBrowserService, create
MediaBrowser
instance for a given service component andconnect
. After establishing the connection, a handle to MediaSession can be obtained viagetSessionToken
.The Radio app can restrict client packages allowed to connect in an
onGetRoot
implementation of their service. The app should allow system apps to connect without whitelisting. For details about whitelisting, see Accept the Assistant app package and signature.If the source-specific app (for example, a radio app) is installed on a device without such source support, it would still advertise itself as handling the
ACTION_PLAY_BROADCASTRADIO
intent, but its MediaBrowser tree would not contain radio-specific tags. Thus, a client willing to check if a given source is available on a device, must:- Discover the radio service (call
resolveService
forACTION_PLAY_BROADCASTRADIO
). - Create
MediaBrowser
and then connect to it. - Determine the presence of
MediaItem
withEXTRA_BCRADIO_FOLDER_TYPE
extra.
Note: In most cases, the client must scan all available MediaBrowser trees to detect all available sources for a given device.
Band names
Band list is represented by a set of top-level directories with a folder type tag set to
BCRADIO_FOLDER_TYPE_BAND
. TheirMediaItem
's titles are localized strings representing band names. In most cases it will be the same as English translation, but the client can't depend on that assumption.To provide a stable mechanism for looking up certain bands, an extra tag is added for band folders,
EXTRA_BCRADIO_BAND_NAME_EN
. This is a non-localized name of the band and can only take one of these predefined values:AM
FM
DAB
If the band is not on this list, the band name tag should not be set. However, if the band is on the list, it must have a tag set. HD radio doesn't enumerate separate bands as it uses the same underlying medium as AM/FM.
General play intents
Each app dedicated for playing given source (like radio or CD) must handle a general play intent to start playing some content possibly from inactive state (for example, after boot). It's up to the app how to select content to play, but it's usually the recently played radio program or CD track.There is a separate intent defined for each audio source:
android.car.intent.action.PLAY_BROADCASTRADIO
android.car.intent.action.PLAY_AUDIOCD
: CD-DA or CD-Textandroid.car.intent.action.PLAY_DATADISC
: Optical data disc like CD/DVD, but not CD-DA (may be Mixed Mode CD)android.car.intent.action.PLAY_AUX
: Without specifying which AUX portandroid.car.intent.action.PLAY_BLUETOOTH
android.car.intent.action.PLAY_USB
: Without specifying which USB deviceandroid.car.intent.action.PLAY_LOCAL
: Local media storage (built-in flash)
Intents were chosen to be used for general play command, because they solve two problems at once: the general play command itself and service discovery. Additional benefit of having such intent would be a possibility to execute such simple action without opening MediaBrowser session.
Service discovery is actually the more important problem solved with these intents. Procedure for service discovery is easy and unequivocal this way (see Discovery and service connection).
To make some client implementations easier, there is an alternative way of issuing such Play command (that also has to be implemented by the radio app): issuing
playFromMediaId
with the rootId of the root node (used as mediaId). While the root node is not meant to be playable, its rootId is an arbitrary string which can be made to be consumable as mediaId. However, clients are not required to understand this nuance.ProgramSelector
While
mediaId
is enough to select a channel from theMediaBrowserService
, it becomes bound to a session and not consistent between providers. In some cases the client may need an absolute pointer (such as an absolute frequency) to maintain it between sessions and devices.In the era of digital radio broadcasts, a bare frequency is not sufficient to tune to a specific station. Therefore, use
ProgramSelector
to tune to an analog or digital channel.ProgramSelector
consists of two parts:- Primary identifier. A unique and stable identifier for a given radio station that doesn't change but may not be enough to tune to that station. For example, RDS PI code, which may be translated to the call sign in the US.
- Secondary identifiers. Additional identifiers useful for tuning to that station (for example, frequency), possibly including identifiers from other radio technologies. For example, a DAB station may have an analog broadcasting fallback.
To enable
ProgramSelector
to fit into theMediaBrowser
- orMediaSession
-based solution, define a URI schema to serialize it. The schema is defined as follows:broadcastradio://program/<primary ID type>/<primary ID>? <secondary ID type>=<secondary ID>&<secondary ID type>=<secondary ID>
In this example, the secondary Identifiers portion (after the question mark (
?
)) is optional and can be removed to provide a stable identifier for use asmediaId
. For example: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
The authority part (AKA host) of
program
provides some room for scheme extension in the future. The identifier type strings are precisely specified as their names in the HAL 2.x definition ofIdentifierType
and the value format is a decimal or hexadecimal (with0x
prefix) number.All vendor-specific identifiers are represented by the
VENDOR_
prefix. For example,VENDOR_0
forVENDOR_START
andVENDOR_1
forVENDOR_START
plus 1. Such URIs are specific to the radio hardware on which they were generated and cannot be transferred between devices made by different OEMs.These URIs must be assigned to each MediaItem under the top-level radio folders. In addition, the MediaSession must support both
playFromMediaId
andplayFromUri
. However, the URI is primarily intended for radio metadata extraction (such as FM frequency) and persistent storage. There is no guarantee the URI will be available for all media items (for example, when the primary ID type is not yet supported by the framework). On the other hand, Media ID always works. It is not recommended that clients use URI to select items from the current MediaBrowser session. Instead, useplayFromMediaId
. That said, it is not optional for the serving app and missing URIs are reserved for well-justified cases.The initial design used a single colon instead of the
://
sequence after the scheme part. However, the former is not supported byandroid.net.Uri
for absolute hierarchical URI references.Other source types
Other audio sources can be handled similarly. For example, auxiliary input and the Audio CD player.
A single app may serve multiple types of sources. In such cases, it's recommended you create a separate MediaBrowserService for each type of source. Even in a set-up with multiple served sources/MediaBrowserServices, it's strongly recommended to have a single MediaSession within a single app.
Audio CD
Similar to Audio CD in that the app that serves such disks would expose MediaBrowser with a single browsable entry (or more, if the system has a CD changer), which in turn would contain all tracks of a given CD. If the system does not have the knowledge about the tracks on every CD (for example, when all disks are inserted in a cartridge at once and it doesn't read them all), then MediaItem for the entire disk would be just
PLAYABLE
, notBROWSABLE
plusPLAYABLE
. If there is no disk in a given slot, the item would be neitherPLAYABLE
norBROWSABLE
(but each slot must always be present in the tree).These entries would be marked in a similar way that broadcast radio folders are; they would contain additional extra fields defined in the MediaDescription API:
EXTRA_CD_TRACK
: For everyMediaItem
on Audio CD, 1-based track number.EXTRA_CD_DISK
: 1-based disk number.
For CD-Text enabled system and compatible disk, the top-level MediaItem would have a title of the disk. Similarly, the MediaItems for tracks, would have a title of the track.
Auxiliary input
The app that serves auxiliary input exposes a MediaBrowser tree with a single entry (or more, when multiple ports exist) representing the AUX in port. The respective MediaSession takes its mediaId and switches to that source after getting the
playFromMediaId
request.Each AUX MediaItem entry would have an extra field
EXTRA_AUX_PORT_NAME
set to the non-localized name of the port without the "AUX" phrase. For example, "AUX 1" would have be set to "1", "AUX front" to "front" and "AUX" to an empty string. In non-English locales, the name tag would remain the same English string. Unlikely as forEXTRA_BCRADIO_BAND_NAME_EN
, the values are OEM-defined and not constrained to a predefined list.If the hardware can detect devices connected to the AUX port, the hardware should mark the MediaItem as
PLAYABLE
, only if input is connected. The hardware should still be enumerated (but notPLAYABLE
) if nothing was connected to this port. If the hardware has no such capability, the MediaItem must always be set toPLAYABLE
.Extra fields
Define the following fields:
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"
Client needs to review the top-level MediaItems for elements having the
EXTRA_CD_DISK
orEXTRA_AUX_PORT_NAME
extra field set.Detailed examples
The following examples address the MediaBrowser tree structure for source types that are part of this design.
Broadcast radio MediaBrowserService (handles
ACTION_PLAY_BROADCASTRADIO
):- Stations (browsable)
EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_PROGRAMS
- BBC One (playable) URI:
broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=90500
- ABC 88.1 (playable) URI:
broadcastradio://program/RDS_PI/5678?AMFM_FREQUENCY=88100
- ABC 88.1 HD1 (playable) URI:
broadcastradio://program/HD_STATION_ID_EXT/158241DEADBEEF?AMFM_FREQUENCY=88100&RDS_PI=5678
- ABC 88.1 HD2 (playable) URI:
broadcastradio://program/HD_STATION_ID_EXT/158242DEADBEFE
- 90.5 FM (playable) - FM without RDSURI:
broadcastradio://program/AMFM_FREQUENCY/90500
- 620 AM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/620
- BBC One (playable) URI:
broadcastradio://program/DAB_SID_EXT/1E24102?RDS_PI=1234
- BBC One (playable) URI:
- Favorites (browsable, playable)
EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_FAVORITES
- BBC One (playable) URI:
broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=101300
- BBC Two (not playable)URI:
broadcastradio://program/RDS_PI/1300?AMFM_FREQUENCY=102100
- BBC One (playable) URI:
- AM (browsable, playable):
EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="AM"
- 530 AM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/530
- 540 AM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/540
- 550 AM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/550
- 530 AM (playable) URI:
- FM (browsable, playable):
EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="FM"
- 87.7 FM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/87700
- 87.9 FM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/87900
- 88.1 FM (playable) URI:
broadcastradio://program/AMFM_FREQUENCY/88100
- 87.7 FM (playable) URI:
- DAB (playable):
EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="DAB"
Audio CD MediaBrowserService (handles
ACTION_PLAY_AUDIOCD
):- Disc 1 (playable)
EXTRA_CD_DISK=1
- Disc 2 (browsable, playable)
EXTRA_CD_DISK=2
- Track 1 (playable)
EXTRA_CD_TRACK=1
- Track 2 (playable)
EXTRA_CD_TRACK=2
- Track 1 (playable)
- My music CD (browsable, playable)
EXTRA_CD_DISK=3
- All By Myself (playable)
EXTRA_CD_TRACK=1
- Reise, Reise (playable)
EXTRA_CD_TRACK=2
- All By Myself (playable)
- Empty slot 4 (not playable)
EXTRA_CD_DISK=4
AUX MediaBrowserService (handles
ACTION_PLAY_AUX
):- AUX front (playable)
EXTRA_AUX_PORT_NAME="front"
- AUX rear (playable)
EXTRA_AUX_PORT_NAME="rear"