O Android Auto e o Android Automotive OS ajudam a levar o conteúdo do seu app de música para os usuários no carro. Um app de música para carros precisa oferecer um serviço de navegador de mídia para que o Android Auto e o Android Automotive OS (ou outro app com um navegador de mídia) possam descobrir e mostrar o conteúdo.
Este guia presume que você já tenha um app de música que reproduz áudio em um smartphone e que ele seja compatível com a arquitetura de app de música do Android.
Este guia descreve os componentes necessários de um MediaBrowserService
e de uma
MediaSession
que seu app precisa para funcionar no Android Auto ou no Android
Automotive OS. Depois de concluir a infraestrutura de mídia principal, você pode
adicionar suporte ao Android Auto e ao
Android Automotive OS no seu app
de música.
Antes de começar
- Leia a documentação da API de mídia do Android.
- Consulte Criar apps de mídia para orientações sobre design.
- Revise os principais termos e conceitos listados nesta seção.
Principais termos e conceitos
- Serviço de navegador de mídia
- Um serviço do Android implementado pelo seu app de música de acordo com a
API
MediaBrowserServiceCompat
. O app usa esse serviço para expor seu conteúdo. - Navegador de mídia
- Uma API usada por apps de música para descobrir serviços de navegador de mídia e mostrar o conteúdo deles. O Android Auto e o Android Automotive OS usam um navegador de mídia para encontrar o serviço de navegação de mídia do seu app.
- Item de mídia
O navegador de mídia organiza o conteúdo em uma árvore de objetos
MediaItem
. Um item de mídia pode ter uma ou as duas flags a seguir:FLAG_PLAYABLE
: indica que o item é uma folha na árvore de conteúdo. O item representa um único fluxo de som, como uma música de um álbum, o capítulo de um audiolivro ou o episódio de um podcast.FLAG_BROWSABLE
: indica que o item é um nó na árvore de conteúdo e tem filhos. Por exemplo, o item representa um álbum e seus filhos são as músicas desse álbum.
Um item de mídia que seja tanto navegável quanto reproduzível funciona como uma playlist. Você pode selecionar o item para reproduzir todos os filhos ou navegar por eles.
- Otimizado para veículos
Uma atividade de um app Android Automotive OS que segue as diretrizes de design do Android Automotive OS. A interface das atividades não é desenhada por esse sistema. Portanto, verifique se o seu app segue as diretrizes de design. Normalmente, isso inclui áreas de toque e tamanhos de fonte maiores, suporte para os modos diurno e noturno e taxas de contraste mais altas.
As interfaces do usuário otimizadas para veículos só podem ser exibidas quando as Restrições de experiência do usuário veicular (CUXRs, na sigla em inglês) não estão em vigor, porque essas interfaces podem exigir atenção ou interação prolongada do usuário. As CUXRs não estão em vigor quando o carro está parado ou estacionado, mas sempre estão em vigor quando o carro está em movimento.
Não é necessário criar atividades para o Android Auto, porque ele projeta a própria interface otimizada para veículos usando as informações do serviço de navegação de mídia.
Configurar os arquivos de manifesto do app
Antes de criar o serviço de navegador de mídia, é necessário configurar os arquivos de manifesto do app.
Declarar o serviço de navegação de mídia
Tanto o Android Auto quanto o Android Automotive OS se conectam ao seu app pelo serviço de navegador de mídia para procurar itens de mídia. Declare o serviço de navegador de mídia no manifesto para permitir que o Android Automotive OS e o Android Auto descubram o serviço e se conectem ao seu app.
O snippet de código abaixo mostra como declarar o serviço de navegador de mídia no seu manifesto. Inclua este código no arquivo de manifesto do módulo do Android Automotive OS e no arquivo de manifesto do seu app para smartphones.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
Especificar ícones do app
Você precisa especificar ícones que o Android Auto e o Android Automotive OS possam usar para representar seu app na interface do sistema. Dois tipos de ícone são necessários:
- Ícone na tela de início
- Ícone de atribuição
Ícone na tela de início
O ícone na tela de início representa seu app na interface do sistema, como na tela de início e na bandeja de ícones. Você pode especificar que quer usar o ícone do app para dispositivos móveis para representar o app de música do carro usando a seguinte declaração de manifesto:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
Para usar um ícone diferente do seu app para dispositivos móveis, defina a propriedade android:icon
no elemento <service>
do serviço de navegação de mídia no manifesto:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Ícone de atribuição
O ícone de atribuição é usado em locais em que o conteúdo de mídia tem precedência, como cards de mídia. Reutilize a versão pequena usada nas notificações. Esse ícone precisa ser monocromático. Especifique o ícone que vai ser usado para representar seu app com a seguinte declaração de manifesto:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
Criar o serviço de navegador de mídia
Crie um serviço de navegador de mídia ampliando a classe
MediaBrowserServiceCompat
. O Android Auto e o Android Automotive OS podem usar o serviço
para as seguintes ações:
- Navegar na hierarquia de conteúdo do seu app para apresentar um menu ao usuário.
- Receber o token para o objeto
MediaSessionCompat
do seu app para controlar a reprodução de áudio.
Você também pode usar o serviço de navegação de mídia para permitir que outros clientes acessem o conteúdo de mídia do seu app. Esses clientes de mídia podem ser outros apps no smartphone de um usuário ou outros clientes remotos.
Fluxo de trabalho do serviço de navegador de mídia
Esta seção descreve como o Android Automotive OS e o Android Auto interagem com o serviço de navegação de mídia durante um fluxo de trabalho comum do usuário.
- O usuário inicia seu app no Android Automotive OS ou no Android Auto.
- O Android Automotive OS ou o Android Auto entra em contato com o serviço de
navegação de mídia do seu app usando o método
onCreate()
. Na implementação desse métodoonCreate()
, você precisa criar e registrar um objetoMediaSessionCompat
e o respectivo objeto de callback. - O Android Automotive OS ou o Android Auto chama o método
onGetRoot()
do serviço para receber o item de mídia raiz na hierarquia de conteúdo. O item de mídia raiz não é exibido, mas é usado para recuperar mais conteúdo do seu app. - O Android Automotive OS ou Android Auto chama o método
onLoadChildren()
do seu serviço para receber os filhos do item de mídia raiz. Esses sistemas mostram os itens de mídia como o nível superior dos itens de conteúdo. Consulte a seção Estruturar o menu raiz desta página para ver mais informações sobre o que o sistema espera nesse nível. - Se o usuário selecionar um item de mídia navegável, o método
onLoadChildren()
será chamado novamente para extrair os filhos do item de menu selecionado. - Se o usuário selecionar um item de mídia reproduzível, o Android Automotive OS ou o Android Auto chamará o método de callback da sessão de mídia adequado para executar essa ação.
- Se o app permitir, o usuário também vai poder pesquisar seu conteúdo. Nesse
caso, o Android Automotive OS ou o Android Auto chama o método
onSearch()
do serviço.
Criar sua hierarquia de conteúdo
O Android Auto e o Android Automotive OS chamam o serviço de navegação de mídia do seu app para
descobrir qual conteúdo está disponível. É necessário implementar dois métodos no
serviço do navegador de mídia para oferecer suporte a esse recurso: onGetRoot()
e
onLoadChildren()
Implementar onGetRoot
O método onGetRoot()
do serviço retorna informações sobre o nó raiz da hierarquia de conteúdo.
O Android Auto e o Android Automotive OS usam esse nó raiz para solicitar o restante do
seu conteúdo usando o
método
onLoadChildren()
.
O snippet de código a seguir mostra uma implementação simples do
método onGetRoot()
:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? = // Verify that the specified package is allowed to access your // content. You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. null } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // Verify that the specified package is allowed to access your // content. You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. return null; } return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null); }
Para conferir um exemplo mais detalhado, consulte o método onGetRoot()
no app de exemplo Universal Android Music Player no GitHub (link em inglês).
Adicionar validação de pacote para onGetRoot()
Quando uma chamada é feita para o método onGetRoot()
do seu serviço, o pacote de chamada transmite informações de identificação para o serviço. Ele
pode usar essas informações para decidir se o pacote pode acessar seu
conteúdo. Por exemplo, é possível restringir o acesso ao conteúdo do app a uma lista de
pacotes aprovados comparando o clientPackageName
à lista de permissões e
verificando o certificado usado para assinar o APK do pacote. Se não for possível
verificar o pacote, retorne null
para negar acesso ao seu conteúdo.
Para que os apps
do sistema (como o Android Auto e o Android Automotive OS) acessem o conteúdo, seu serviço precisa sempre retornar um valor
BrowserRoot
não nulo quando esses apps chamam o método
onGetRoot()
. A assinatura do app para Android Automotive OS pode variar de acordo
com a marca e o modelo do carro. Portanto, é necessário permitir conexões de todos
os apps do sistema para oferecer suporte ao Android Automotive OS.
O snippet de código a seguir mostra como seu serviço pode validar que o pacote de chamada é um app do sistema:
fun isKnownCaller(
callingPackage: String,
callingUid: Int
): Boolean {
...
val isCallerKnown = when {
// If the system is making the call, allow it.
callingUid == Process.SYSTEM_UID -> true
// If the app was signed by the same certificate as the platform
// itself, also allow it.
callerSignature == platformSignature -> true
// ... more cases
}
return isCallerKnown
}
Esse snippet de código é um trecho da classe PackageValidator
no app de exemplo Universal Android Music Player no GitHub (link em inglês). Confira nessa classe um exemplo
mais detalhado de como implementar a validação de pacote para
o método
onGetRoot()
do seu serviço.
Além de permitir apps do sistema, você precisa permitir que o Google Assistente
se conecte ao MediaBrowserService
. Perceba que o Google Assistente tem
nomes de pacote separados
para o smartphone (que inclui o Android Auto) e o Android Automotive OS.
Implementar onLoadChildren()
Depois que recebem seu objeto de nó raiz, o Android Auto e o Android Automotive OS
criam um menu de nível superior chamando onLoadChildren()
nesse objeto para receber os filhos dele. Os apps clientes criam submenus
chamando esse mesmo método usando objetos de um nó filho.
Cada nó da sua hierarquia de conteúdo é representado por um objeto
MediaBrowserCompat.MediaItem
. Cada um desses itens de mídia é identificado por uma string de ID exclusiva. Os apps
clientes tratam essas strings de ID como tokens opacos. Quando um app cliente quer navegar
para um submenu ou abrir um item de mídia, ele transmite o token. O app é responsável
por associar o token ao respectivo item de mídia.
O snippet de código a seguir mostra uma implementação simples do método
onLoadChildren()
:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: Result<List<MediaBrowserCompat.MediaItem>> ) { // Assume for example that the music catalog is already loaded/cached. val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf() // Check whether this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level // and put them in the mediaItems list. } else { // Examine the passed parentMediaId to see which submenu we're at // and put the children of that menu in the mediaItems list. } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaBrowserCompat.MediaItem>> result) { // Assume for example that the music catalog is already loaded/cached. List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); // Check whether this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level // and put them in the mediaItems list. } else { // Examine the passed parentMediaId to see which submenu we're at // and put the children of that menu in the mediaItems list. } result.sendResult(mediaItems); }
Para conferir um exemplo completo, consulte o método
onLoadChildren()
no app de exemplo Universal Android Music Player no GitHub (link em inglês).
Estruturar o menu raiz
O Android Auto e o Android Automotive OS têm restrições específicas sobre a
estrutura do menu raiz. Elas são comunicados ao MediaBrowserService
por dicas raiz, que podem ser lidas pelo argumento Bundle
transmitido para
onGetRoot()
.
Seguindo essas dicas, o sistema pode exibir o conteúdo raiz da maneira ideal
como guias de navegação. Se você não seguir essas dicas, alguns conteúdos raiz poderão
ser descartados ou passar a ser menos detectáveis pelo sistema. Duas dicas são enviadas:
- Um limite para o número de filhos raiz: na maioria dos casos, esse número pode ser quatro. Isso significa que não é possível exibir mais de quatro guias.
- Flags aceitas nos filhos raiz:
esse valor pode ser
MediaItem#FLAG_BROWSABLE
. Isso significa que somente itens navegáveis, e não jogáveis, podem ser mostrados como guias.
Use o código abaixo para ler as dicas raiz relevantes:
Kotlin
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
Java
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
Você pode optar por ramificar a lógica da estrutura de sua hierarquia de conteúdo
com base nos valores dessas dicas, especialmente se a hierarquia variar entre
integrações de MediaBrowser
fora do Android Auto e do Android Automotive OS.
Por exemplo, se você normalmente mostra um item raiz reproduzível, convém aninhá-lo
em um item navegável raiz devido ao valor da dica de flag
aceita.
Além das dicas raiz, há algumas outras diretrizes a serem seguidas para garantir que as guias sejam renderizadas de maneira ideal:
- Forneça ícones monocromáticos (preferencialmente brancos) para cada item da guia.
- Forneça etiquetas curtas, mas significativas, para cada item da guia. Etiquetas curtas reduzem a chance de truncamento das strings.
Exibir arte de mídia
A arte em itens de mídia precisa ser transmitida como um URI local usando
ContentResolver.SCHEME_CONTENT
ou ContentResolver.SCHEME_ANDROID_RESOURCE
.
Esse URI local precisa ser resolvido para um bitmap ou um drawable vetorial nos
recursos do aplicativo. Para objetos MediaDescriptionCompat
que representam itens na
hierarquia de conteúdo, transmita o URI usando setIconUri()
.
Para objetos MediaMetadataCompat
que representam o item em reprodução no momento, transmita o
URI com putString()
,
usando uma das chaves abaixo:
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
MediaMetadataCompat.METADATA_KEY_ART_URI
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
As etapas a seguir descrevem como fazer o download da arte de um URI da Web e exibi-la
usando um URI local. Para conferir um exemplo mais completo, consulte a
implementação
de openFile()
e os métodos próximos no app de exemplo
Universal Android Music.
Crie um URI
content://
correspondente ao URI da Web. O serviço de navegador de mídia e a sessão de mídia transmitem esse URI de conteúdo ao Android Auto e o Android Automotive OS.Kotlin
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Java
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
Na sua implementação de
ContentProvider.openFile()
, verifique se existe um arquivo para o URI correspondente. Caso contrário, faça o download e armazene o arquivo de imagem em cache. O snippet de código abaixo usa o Glide.Kotlin
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Java
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
Para mais detalhes sobre provedores de conteúdo, consulte Como criar um provedor de conteúdo.
Aplicar estilos de conteúdo
Depois de criar sua hierarquia de conteúdo usando itens navegáveis ou reproduzíveis, você pode aplicar estilos de conteúdo que determinam como esses itens vão ser mostrados no carro.
Você pode usar os seguintes estilos de conteúdo:
- Itens em lista
-
Esse estilo de conteúdo dá prioridade a títulos e metadados, em vez de imagens.
- Itens em grade
-
Esse estilo de conteúdo dá prioridade a imagens, em vez de títulos e metadados.
Definir estilos de conteúdo padrão
Você pode definir padrões globais para a forma como seus itens de mídia são mostrados incluindo
certas constantes no pacote de extras BrowserRoot
do método
onGetRoot()
do seu serviço. O Android Auto e o Android Automotive OS leem esse pacote e procuram
essas constantes para determinar o estilo adequado.
Os seguintes extras podem ser usados como chaves no pacote:
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: aponta uma dica de apresentação para todos os itens visualizáveis na árvore de navegação.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: aponta uma dica de apresentação para todos os itens reproduzíveis da árvore de navegação.
As chaves podem ser correspondidas com os seguintes valores constantes inteiros para influenciar a apresentação desses itens:
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: os itens correspondentes são apresentados como itens de lista.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: os itens correspondentes são apresentados como itens de grade.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: os itens correspondentes são apresentados como itens de lista de "categoria". Eles são iguais aos de lista comuns, mas as margens são aplicadas em torno dos ícones dos itens, já que eles ficam melhores quando são pequenos. Os ícones precisam ser drawables vetoriais tingíveis. Essa dica precisa ser fornecida apenas para itens navegáveis.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: os itens correspondentes são apresentados como itens da grade de "categoria". Eles são iguais aos itens de grade comuns, mas as margens precisam ser aplicadas em torno dos ícones dos itens, já que os ícones ficam melhores pequenos. Os ícones precisam ser drawables vetoriais tingíveis. Essa dica precisa ser fornecida apenas para itens navegáveis.
O snippet de código abaixo mostra como configurar o estilo de conteúdo padrão para itens navegáveis como grades e para itens reproduzíveis como listas:
Kotlin
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
Definir estilos de conteúdo por item
A API Content Style permite substituir o estilo de conteúdo padrão para filhos de qualquer item de mídia navegável e para qualquer item de mídia.
Para substituir o estilo padrão de filhos de um item de mídia navegável, crie um
pacote extra na MediaDescription
do item de mídia e adicione as mesmas
dicas mencionadas anteriormente. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
é aplicado a filhos reproduzíveis desse item, ao passo que
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
se aplica a filhos
navegáveis desse item.
Para substituir o padrão de um item de mídia específico em si (e não dos
filhos), crie um pacote de extras na MediaDescription
do item de mídia
e adicione uma dica com a chave
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
.
Use os mesmos valores descritos anteriormente para especificar a apresentação desse item.
O snippet de código abaixo mostra como criar um MediaItem
navegável que
substitui o estilo de conteúdo padrão nele e nos filhos dele. Ele é definido
como um item de lista de categorias, os filhos navegáveis como itens de lista e os
filhos reproduzíveis como itens de grade:
Kotlin
import androidx.media.utils.MediaConstants private fun createBrowsableMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createBrowsableMediaItem( String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Agrupar itens usando dicas de título
Para agrupar itens de mídia relacionados, use uma dica por item. Cada item de mídia
de um grupo precisa declarar um pacote de extras na MediaDescription
que
inclui um mapeamento com a chave
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
e um valor de string idêntico. Localize essa string, que é usada como
título do grupo.
O snippet de código a seguir mostra como criar um MediaItem
com o
título de subgrupo "Songs"
:
Kotlin
import androidx.media.utils.MediaConstants private fun createMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
É necessário que o app transmita todos os itens de mídia que você quer agrupar como um bloco contíguo. Por exemplo, suponha que você queira exibir dois grupos de itens de mídia, "Songs" e "Albums" (nessa ordem) e que seu app tenha transmitido cinco itens de mídia na seguinte ordem:
- Item de mídia A com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia B com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- Item de mídia C com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia D com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia E com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
Como os itens de mídia dos grupos "Songs" e "Albums" não são mantidos juntos em blocos contíguos, o Android Auto e o Android Automotive OS interpretam isso na forma dos quatro grupos a seguir:
- Grupo 1 chamado "Songs" e contendo o item de mídia A
- Grupo 2 chamado "Albums" e contendo o item de mídia B
- Grupo 3 chamado "Songs" e contendo itens de mídia C e D
- Grupo 4 chamado "Albums" e contendo o item de mídia E
Para exibir esses itens em dois grupos, o app precisa transmitir os itens de mídia na seguinte ordem:
- Item de mídia A com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia C com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia D com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- Item de mídia B com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- Item de mídia E com
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
Exibir mais indicadores de metadados
Você pode incluir mais indicadores de metadados para fornecer informações resumidas sobre o conteúdo da árvore de navegação de mídia e durante a reprodução. O Android Auto e o Android Automotive OS leem os extras associados a um item na árvore de navegação e procuram certas constantes para determinar quais indicadores mostrar. Durante a reprodução de mídia, o Android Auto e o Android Automotive OS leem os metadados da sessão de mídia e procuram certas constantes para determinar os indicadores a serem exibidos.
As constantes abaixo podem ser usadas nos extras de descrição MediaItem
e
nos extras de MediaMetadata
:
EXTRA_DOWNLOAD_STATUS
: indica o status do download de um item. Use essa constante como a chave. As constantes longas abaixo são os valores possíveis:STATUS_DOWNLOADED
: o download do item já foi feito.STATUS_DOWNLOADING
: o item está sendo transferido por download.STATUS_NOT_DOWNLOADED
: o download do item não foi feito.
METADATA_KEY_IS_EXPLICIT
: indica se o item tem conteúdo explícito. Para indicar que um item é explícito, use essa constante como a chave e oMETADATA_VALUE_ATTRIBUTE_PRESENT
longo como o valor.
As constantes a seguir podem ser usadas apenas nos extras de descrição MediaItem
:
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indica o estado de conclusão de um conteúdo longo, como episódios de podcast ou audiolivros. Use essa constante como a chave. As seguintes constantes inteiras são os valores possíveis:DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: o item não foi reproduzido.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: o item foi parcialmente reproduzido, e a posição atual está em algum lugar no meio.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: o item foi concluído.
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indica o progresso da reprodução para conteúdos em formato longo como um tipo double, com valores entre 0,0 e 1,0. Esse extra fornece mais informações sobre o estadoPARTIALLY_PLAYING
para que o Android Auto ou o Android Automotive OS mostre um indicador de progresso mais significativo, como uma barra de progresso. Se você usar esse extra, consulte a seção Atualizar a barra de progresso na visualização de navegação enquanto o conteúdo é reproduzido neste guia para saber como manter o indicador atualizado depois da impressão inicial.
Para mostrar indicadores que aparecem enquanto o usuário estiver percorrendo a árvore
de navegação, crie um pacote de extras que inclua uma ou mais dessas constantes e
transmita o pacote ao método MediaDescription.Builder.setExtras()
.
O snippet de código abaixo mostra como exibir indicadores para um item de mídia explícito com 70% de conteúdo reproduzido:
Kotlin
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Java
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
Para exibir os indicadores de um item de mídia que está sendo reproduzido no momento, você pode
declarar valores Long
para METADATA_KEY_IS_EXPLICIT
ou EXTRA_DOWNLOAD_STATUS
no
método MediaMetadataCompat
da mediaSession
. Não é possível exibir os
indicadores DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
ou
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
na visualização de reprodução.
O snippet de código a seguir mostra como indicar que a música atual na visualização de reprodução é explícita e foi salva:
Kotlin
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Java
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Atualizar a barra de progresso na visualização de navegação enquanto o conteúdo é reproduzido
Conforme mencionado anteriormente, você pode usar o extra
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
para mostrar uma barra de progresso para conteúdo parcialmente reproduzido na
visualização de navegação. No entanto, se o usuário continuar reproduzindo esse conteúdo
no Android Auto ou no Android Automotive OS, o indicador não vai ser
preciso com o passar do tempo.
Para que o Android Auto e o Android Automotive OS
mantenham a barra de progresso atualizada, é possível fornecer mais informações em
MediaMetadataCompat
e PlaybackStateCompat
para vincular o conteúdo em andamento a
itens de mídia na visualização de navegação. Os requisitos abaixo precisam ser atendidos para que o
item de mídia tenha uma barra de progresso com atualização automática:
- Quando criado, o
MediaItem
precisa enviarDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
nos extras, com um valor entre 0,0 e 1,0. MediaMetadataCompat
precisa enviarMETADATA_KEY_MEDIA_ID
com um valor de string igual ao código da mídia transmitido aoMediaItem
.PlaybackStateCompat
precisa incluir um extra com a chavePLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
, que é mapeada para um valor de string igual ao código da mídia. transmitido aoMediaItem
.
O snippet de código abaixo mostra como indicar que o item em reprodução no momento está vinculado a um item na visualização de navegação:
Kotlin
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Java
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Exibir resultados de pesquisa navegáveis
Seu app pode fornecer resultados da pesquisa contextuais que são exibidos para os usuários quando eles iniciam uma consulta de pesquisa. O Android Auto e o Android Automotive OS mostram esses resultados usando interfaces de consulta de pesquisa ou recursos que giram em torno de consultas feitas anteriormente na sessão. Para saber mais, consulte a seção Suporte para comandos de voz neste guia.
Para mostrar resultados de pesquisa navegáveis, inclua a chave de constante
BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
no pacote de extras do método onGetRoot()
do seu
serviço, mapeando para o booleano true
.
O snippet de código a seguir mostra como ativar a compatibilidade no método
onGetRoot()
:
Kotlin
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
Para começar a fornecer resultados da pesquisa, modifique o método onSearch()
no seu serviço de navegação de mídia. O Android Auto e o Android Automotive OS
encaminham os termos de pesquisa de um usuário para esse método sempre que o usuário chama uma interface de consulta
de pesquisa ou uma funcionalidade de "resultados da pesquisa".
Você pode organizar os resultados
da pesquisa do método onSearch()
do seu serviço usando itens de título
para torná-los mais navegáveis. Por exemplo, se o seu app toca música, você pode
organizar os resultados da pesquisa por álbum, artista e músicas.
O snippet de código a seguir mostra uma implementação simples do
método
onSearch()
:
Kotlin
fun onSearch(query: String, extras: Bundle) { // Detach from results to unblock the caller (if a search is expensive). result.detach() object:AsyncTask() { internal var searchResponse:ArrayList internal var succeeded = false protected fun doInBackground(vararg params:Void):Void { searchResponse = ArrayList() if (doSearch(query, extras, searchResponse)) { succeeded = true } return null } protected fun onPostExecute(param:Void) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse) } else { // This invokes onError() on the search callback. result.sendResult(null) } return null } }.execute() } // Populates resultsToFill with search results. Returns true on success or false on error. private fun doSearch( query: String, extras: Bundle, resultsToFill: ArrayList ): Boolean { // Implement this method. }
Java
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<MediaItem> searchResponse; boolean succeeded = false; @Override protected Void doInBackground(Void... params) { searchResponse = new ArrayList<MediaItem>(); if (doSearch(query, extras, searchResponse)) { succeeded = true; } return null; } @Override protected void onPostExecute(Void param) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse); } else { // This invokes onError() on the search callback. result.sendResult(null); } } }.execute() } /** Populates resultsToFill with search results. Returns true on success or false on error. */ private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) { // Implement this method. }
Ações de navegação personalizadas
As ações de navegação personalizadas permitem adicionar ícones e rótulos personalizados aos objetos
MediaItem
do seu app no app de mídia do carro e processar as interações do usuário com
essas ações. Com isso, você pode estender a funcionalidade do app de mídia de
várias maneiras, como adicionando ações "Fazer o download", "Adicionar à fila", "Tocar rádio",
"Adicionar aos favoritos" ou "Remover".
Se houver mais ações personalizadas do que o OEM permite, um menu flutuante será apresentado ao usuário.
Como elas funcionam?
Cada ação de navegação personalizada é definida por:
- um ID da ação (um identificador de string exclusivo);
- um rótulo de ação (o texto exibido para o usuário);
- um URI de ícone de ação (um drawable vetorial que pode ser colorido).
Você define uma lista de ações de navegação personalizadas globalmente como parte da sua
BrowseRoot
. Em seguida, é possível anexar um subconjunto dessas ações a cada
MediaItem.
Quando um usuário interage com uma ação de navegação personalizada, seu app recebe um callback
em onCustomAction()
. Em seguida, você pode processar a ação e atualizar a lista de
ações para o MediaItem
, se necessário. Isso é útil para ações com estado,
como "Adicionar aos favoritos" e "Fazer o download". No caso de ações que não precisam ser atualizadas, como "Tocar
rádio", não é necessário atualizar a lista.
Você também pode anexar ações de navegação personalizadas a uma raiz de nó de navegação. Essas ações vão ser mostradas em uma barra de ferramentas secundária abaixo da barra de ferramentas principal.
Como implementar ações de navegação personalizadas
Siga estas etapas para adicionar ações de navegação personalizadas ao projeto:
- Substitua dois métodos na
implementação de
MediaBrowserServiceCompat
: - Analise os limites de ação durante a execução:
- Em
onGetRoot()
, confira o número máximo de ações permitidas para cadaMediaItem
usando a chaveBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
norootHints
Bundle
. Um limite de 0 indica que o sistema não oferece suporte ao recurso.
- Em
- Crie a lista global de ações de navegação personalizadas:
- Para cada ação, crie um objeto
Bundle
com as seguintes chaves: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: o ID da ação *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: o rótulo da ação *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: o URI do ícone de ação * Adicione todos os objetosBundle
de ação a uma lista.
- Para cada ação, crie um objeto
- Adicione a lista global à
BrowseRoot
:- Nos
Bundle
extras daBrowseRoot
, adicione a lista de ações como umParcelable
Arraylist
usando a chaveBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- Nos
- Adicione ações aos objetos
MediaItem
:- Você pode adicionar ações a objetos
MediaItem
individuais incluindo a lista de IDs de ação nosMediaDescriptionCompat
extras usando a chaveDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. Essa lista precisa ser um subconjunto da lista global de ações definida naBrowseRoot
.
- Você pode adicionar ações a objetos
- Processe as ações e retorne o progresso ou os resultados:
- Em
onCustomAction
, processe a ação com base no ID dela e em outros dados necessários. Você pode conferir o ID doMediaItem
que acionou a ação nos extras usando a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. - Para atualizar a lista de ações para um
MediaItem
, inclua a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
no pacote de progresso ou de resultados.
- Em
Confira algumas mudanças que você pode fazer no BrowserServiceCompat
para começar
a usar as ações de navegação personalizadas.
Substituir BrowserServiceCompat
Substitua os métodos abaixo em MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Analisar limite de ações
Confira quantas ações de navegação personalizadas têm suporte.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Criar uma ação de navegação personalizada
Cada ação precisa ser empacotada em um Bundle
separado.
- ID da ação
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Rótulo da ação
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- URI do ícone de ação
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Adicionar ações de navegação personalizadas a Parceable
ArrayList
Adicione todos os objetos Bundle
das ações de navegação personalizadas a uma ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Adicionar a lista de ações de navegação personalizadas à raiz de navegação
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Adicionar ações a um MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Resultado do build onCustomAction
- Analise o mediaId de
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- Para resultados assíncronos, remova o resultado.
result.detach()
- Pacote de resultados do build
- Mensagem para o usuário
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Atualizar item(use para atualizar as ações em um item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Abrir a visualização de reprodução
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Atualizar nó de navegação
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Mensagem para o usuário
- Se ocorrer um erro, chame
result.sendError(resultBundle).
. - Se o progresso for atualizado, chame
result.sendProgressUpdate(resultBundle)
. - Para concluir, chame
result.sendResult(resultBundle)
.
Atualizar estado da ação
Ao usar o método result.sendProgressUpdate(resultBundle)
com a chave
EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
,
você pode atualizar o MediaItem
para refletir o novo estado da ação. Isso
permite fornecer feedback em tempo real ao usuário sobre o progresso e
o resultado da ação.
Exemplo: ação de download
Confira um exemplo de como usar esse recurso para implementar uma ação de download com três estados:
- Fazer o download: é o estado inicial da ação. Quando o usuário selecionar
essa ação, troque por "Fazendo o download" e chame
sendProgressUpdate
para atualizar a interface. - Fazendo o download: este estado indica que o download está em andamento. É possível usar esse estado para mostrar uma barra de progresso ou outro indicador ao usuário.
- Salvo: este estado indica que o download foi concluído. Quando o
download for concluído, troque "Fazendo o download" por "Salvo" e chame
sendResult
com a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
para indicar que o item precisa ser atualizado. Além disso, você pode usar a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
para mostrar uma mensagem de êxito para o usuário.
Essa abordagem permite fornecer um feedback claro ao usuário sobre o processo de download e o estado atual dele. Você pode adicionar ainda mais detalhes com ícones para mostrar os estados de download de 25%, 50% e 75%.
Exemplo: ação de adicionar aos favoritos
Outro exemplo é uma ação de adicionar aos favoritos com dois estados:
- Adicionar aos favoritos: esta ação é exibida para itens que não estão na
lista de favoritos do usuário. Quando o usuário selecionar essa ação, será possível trocá-la
por "Adicionado aos favoritos" e chamar
sendResult
com a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
para atualizar a interface. - Adicionado aos favoritos: esta ação é exibida para itens que estão na lista de favoritos
do usuário. Quando o usuário selecionar essa ação, troque por
"Adicionar aos favoritos" e chame
sendResult
com a chaveEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
para atualizar a interface.
Essa abordagem oferece uma maneira clara e consistente para os usuários gerenciarem os itens favoritos.
Esses exemplos mostram a flexibilidade das ações de navegação personalizadas e como você pode usá-las para implementar uma variedade de funcionalidades com feedback em tempo real para melhorar a experiência do usuário no app de mídia do carro.
Para um exemplo completo de implementação desse recurso, consulte o
projeto
TestMediaApp
.
Ativar controle de mídia
O Android Auto e o Android Automotive OS enviam comandos de controle de mídia usando o método
MediaSessionCompat
do seu serviço.
Registre uma sessão e implemente os métodos de callback associados.
Registrar uma sessão de mídia
No método onCreate()
do seu serviço de navegação de mídia, crie um MediaSessionCompat
e registre a sessão de mídia chamando setSessionToken()
.
O snippet de código a seguir mostra como criar e registrar uma sessão de mídia:
Kotlin
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Java
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
Ao criar o objeto de sessão de mídia, você define um objeto de callback usado
para processar solicitações de controle de mídia. Crie esse objeto de callback
fornecendo uma implementação da classe MediaSessionCompat.Callback
para o app. A próxima seção discute como implementar esse objeto.
Implementar comandos de reprodução
Quando um usuário solicita a reprodução de um item de mídia no seu app, o Android Automotive OS
e o Android Auto usam a classe MediaSessionCompat.Callback
no objeto MediaSessionCompat
recebido do serviço de navegação de mídia do app. Quando um usuário
quer controlar a reprodução de conteúdo, como pausar a reprodução ou pular para a
próxima faixa, o Android Auto e o Android Automotive OS invocam um dos métodos do
objeto de callback.
Para gerenciar a reprodução de conteúdo, seu app precisa ampliar a classe abstrata
MediaSessionCompat.Callback
e implementar os métodos compatíveis.
Implemente todos os métodos de callback a seguir que estejam relacionados ao tipo de conteúdo que seu app oferece:
onPrepare()
- Invocado quando a fonte de mídia é alterada. O Android Automotive OS também chama esse método imediatamente após a inicialização. Esse método precisa ser implementado pelo seu app de música.
onPlay()
- Invocado se o usuário quer ativar a reprodução sem escolher um item específico. O
app vai precisar abrir o conteúdo padrão ou, se a reprodução tiver sido pausada com
onPause()
, o app vai retomá-la.Observação: não é recomendado que seu app comece automaticamente a tocar músicas quando o Android Automotive OS ou o Android Auto se conectarem ao serviço de navegação de mídia. Para ver mais informações, consulte a seção sobre como definir o estado inicial da reprodução.
onPlayFromMediaId()
- Invocado quando o usuário escolhe reproduzir um item específico. O método recebe o ID que o serviço de navegação de mídia atribuiu ao item de mídia na sua hierarquia de conteúdo.
onPlayFromSearch()
- Invocado quando o usuário escolhe reproduzir em uma consulta de pesquisa. O app precisa fazer uma escolha apropriada com base na string de pesquisa que foi transmitida.
onPause()
- Invocado quando o usuário decide pausar a reprodução.
onSkipToNext()
- Invocado quando o usuário decide pular para o próximo item.
onSkipToPrevious()
- Invocado quando o usuário decide pular para o item anterior.
onStop()
- Invocado quando o usuário decide parar a reprodução.
Modifique esses métodos no seu app para fornecer a funcionalidade desejada. Não
é necessário implementar um método caso a funcionalidade dele não ofereça suporte para o app. Por
exemplo, se o app fizer uma transmissão ao vivo (como uma esportiva), não será necessário implementar o método onSkipToNext()
. Em vez disso, use a implementação
padrão de onSkipToNext()
.
Seu app não precisa de nenhuma lógica especial para reproduzir o conteúdo pelos alto-falantes do carro. Quando o app recebe uma solicitação para reproduzir conteúdo, ele pode tocar o áudio da mesma forma que reproduzi o conteúdo pelos alto-falantes do smartphone ou fones de ouvido do usuário. O Android Auto e o Android Automotive OS enviam automaticamente o conteúdo de áudio ao sistema do carro para tocar nos alto-falantes do carro.
Para saber mais sobre como tocar conteúdo de áudio, consulte Visão geral do MediaPlayer, Visão geral do app de áudio e Visão geral do ExoPlayer.
Definir ações de reprodução padrão
O Android Auto e o Android Automotive OS mostram controles de mídia com base nas
ações ativadas no objeto
PlaybackStateCompat
.
Por padrão, seu app precisa oferecer suporte para as seguintes ações:
O app também pode oferecer suporte às ações abaixo caso elas sejam relevantes para o conteúdo:
Além disso, você tem a opção de criar uma fila de reprodução que pode ser exibida para
o usuário, mas não é obrigatória. Para fazer isso, chame os métodos setQueue()
e setQueueTitle()
,
ative a ação ACTION_SKIP_TO_QUEUE_ITEM
e defina o callback onSkipToQueueItem()
.
Além disso, adicione suporte ao ícone Tocando agora, que é um indicador do que está
sendo reproduzido. Para fazer isso, chame o método setActiveQueueItemId()
e transmita o ID do item em reprodução na fila. É necessário
atualizar o setActiveQueueItemId()
sempre que houver uma mudança na fila.
O Android Auto e o Android Automotive OS mostram botões para cada ação ativada
e para a fila de reprodução. Ao receber um clique no botão,
o sistema invoca o callback correspondente de
MediaSessionCompat.Callback
.
Reservar espaço não utilizado
O Android Auto e o Android Automotive OS reservam espaço na interface para as
ações ACTION_SKIP_TO_PREVIOUS
e ACTION_SKIP_TO_NEXT
. Se o app não oferecer suporte
a uma dessas funções, o Android Auto e o Android Automotive OS usarão o espaço para
mostrar as ações personalizadas que você criar.
Se você não quiser preencher esses espaços com ações personalizadas, poderá reservá-los
para que o Android Auto e o Android Automotive OS deixem o espaço em branco
sempre que o app não oferecer suporte para a função correspondente. Para fazer isso, chame
o método setExtras()
com um pacote de extras que tenha constantes correspondentes às
funções reservadas.
SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponde a ACTION_SKIP_TO_NEXT
e
SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponde a ACTION_SKIP_TO_PREVIOUS
. Use essas constantes como chaves no
pacote e use o booleano true
para os valores delas.
Configurar o PlaybackState inicial
À medida que o Android Auto e o Android Automotive OS se comunicam com o serviço de navegação
de mídia, a sessão de mídia informa o status da reprodução do conteúdo usando
PlaybackStateCompat
.
Não é recomendado que o app comece a tocar músicas automaticamente quando o Android Automotive OS ou
o Android Auto se conectarem ao serviço de navegação de mídia. Em vez disso, o Android
Auto e o Android Automotive OS precisam retomar ou iniciar a reprodução com base no estado do
carro ou nas ações do usuário.
Para isso, configure o
PlaybackStateCompat
inicial da sessão de mídia como
STATE_STOPPED
,
STATE_PAUSED
,
STATE_NONE
,
ou STATE_ERROR
.
As sessões de mídia no Android Auto e no Android Automotive OS duram apenas o
tempo da viagem, portanto, os usuários iniciam e interrompem essas sessões com frequência. Para
promover uma experiência contínua entre viagens, monitore o estado anterior
da sessão do usuário para que, quando o app de música receber uma
solicitação de retomada, o usuário possa continuar de onde parou (or exemplo, o último item de mídia reproduzido, o PlaybackStateCompat
e a fila).
Adicionar ações de reprodução personalizadas
Você pode adicionar ações de reprodução personalizadas para mostrar mais ações
com suporte do seu app de música. Se o espaço permitir e não estiver reservado, o Android adicionará
ações personalizadas aos controles de transporte. Caso contrário, as ações personalizadas
serão mostradas no menu flutuante. As ações personalizadas são exibidas na ordem
em que são adicionadas ao PlaybackStateCompat
.
Use-as para oferecer um comportamento diferente das ações padrão. Não as use para substituir ou duplicar ações padrão.
Adicione ações personalizadas usando o método addCustomAction()
da classe
PlaybackStateCompat.Builder
.
O snippet de código a seguir mostra como adicionar uma ação personalizada "Iniciar um canal de rádio":
Kotlin
val customActionExtras = Bundle() customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO) stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon // or R.drawable.media3_icon_radio ).run { setExtras(customActionExtras) build() } )
Java
Bundle customActionExtras = new Bundle(); customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO); stateBuilder.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) // or R.drawable.media3_icon_radio .setExtras(customActionExtras) .build());
Para conferir um exemplo mais detalhado, consulte o método setCustomAction()
no app de exemplo Universal Android Music Player no GitHub (link em inglês).
Após criar a ação personalizada, sua sessão de mídia poderá responder à ação
substituindo o método
onCustomAction()
.
O snippet de código a seguir mostra como o app pode responder a uma ação "Iniciar um canal de rádio":
Kotlin
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Java
@Override public void onCustomAction(@NonNull String action, Bundle extras) { if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) { ... } }
Para conferir um exemplo mais detalhado, consulte o método onCustomAction
no app de exemplo Universal Android Music Player no GitHub (link em inglês).
Ícones para ações personalizadas
Cada ação personalizada que você cria requer um ícone.
Se a descrição do ícone corresponder a uma das constantes
CommandButton.ICON_
, defina esse valor inteiro para a chave EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT
dos
extras da ação personalizada. Em sistemas com suporte, isso vai substituir o recurso de ícone
transmitido para CustomAction.Builder
, permitindo que os componentes do sistema renderizem sua ação
e outras ações de reprodução em um estilo consistente.
Você também precisa especificar um recurso de ícone. Os apps de carros podem ser executados em diferentes tamanhos e densidades de tela. Portanto, os ícones que você fornece precisam ser drawables vetoriais. Um drawable vetorial permite dimensionar recursos sem perder os detalhes. Ele também facilita o alinhamento de bordas e cantos a limites de pixels em resoluções menores.
Se uma ação personalizada tiver um estado (por exemplo, ativar ou desativar uma configuração de reprodução), forneça um ícone diferente para cada estado. Assim, os usuários poderão notar uma mudança visual ao selecionarem a ação.
Oferecer estilos alternativos de ícones para ações desativadas
Quando uma ação personalizada não estiver disponível para o contexto atual, troque o ícone da ação personalizada por um ícone alternativo que mostre que a ação está desativada.
Indicar formato de áudio
Para indicar que a mídia em reprodução no momento usa um formato de áudio especial,
é possível especificar ícones renderizados em carros com suporte a esse recurso. Você
pode definir
KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
e
KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
no pacote de extras do item de mídia em reprodução (transmitido para
MediaSession.setMetadata()
). Defina ambos
os extras para acomodar layouts diferentes.
Além disso, você pode definir o extra KEY_IMMERSIVE_AUDIO
para informar aos OEMs de carro que esse é um áudio imersivo. Eles precisam ter muito cuidado
ao decidir se vão aplicar efeitos de áudio que possam interferir no
conteúdo imersivo.
Adicionar links do item em reprodução
Você pode configurar o item de mídia em reprodução para que as legendas, a descrição ou ambos sejam links para outros itens de mídia. Isso permite que o usuário acesse rapidamente itens relacionados. Por exemplo, ele pode pular para outras músicas do mesmo artista, outros episódios desse podcast etc. Se o carro tiver suporte a esse recurso, os usuários poderão tocar no link para navegar até esse conteúdo.
Para adicionar links, configure os metadados
KEY_SUBTITLE_LINK_MEDIA_ID
,
no caso de links que usam a legenda, ou
KEY_DESCRIPTION_LINK_MEDIA_ID
, nos que usam a descrição. Para mais detalhes, consulte a documentação de referência desses
campos de metadados.
Oferecer compatibilidade com comandos de voz
O app de música precisa oferecer suporte para comandos de voz para ajudar a fornecer aos motoristas uma experiência segura e conveniente que minimize as distrações. Por exemplo, se o app estiver reproduzindo um item de mídia, o usuário poderá dizer "Tocar [título da música]" para solicitar ao app a reprodução de um item diferente sem olhar ou tocar na tela do carro. Os usuários podem iniciar consultas clicando nos botões correspondentes no volante ou falando as hotwords "Ok Google".
Quando o Android Auto ou o Android Automotive OS detecta e interpreta uma ação
por voz, ela é enviada ao app pelo
onPlayFromSearch()
.
Ao receber esse callback, o app encontra conteúdo correspondente à string query
e inicia a reprodução.
O usuário pode especificar diferentes categorias de termos nas consultas, como gênero, artista,
álbum, nome da música, estação de rádio ou playlist, entre outros. Ao desenvolver o
suporte à pesquisa, considere todas as categorias que fazem sentido para o app.
Se o Android Auto ou o Android Automotive OS detectar que uma consulta se encaixa em
algumas categorias, ele anexa extras ao parâmetro extras
. Os
extras a seguir podem ser enviados:
Considere uma string query
vazia, que pode ser enviada pelo
Android Auto ou pelo Android Automotive OS se o usuário não especificar termos de pesquisa.
Por exemplo, se o usuário disser "Tocar música". Nesse caso, o app pode
optar por iniciar uma faixa reproduzida recentemente ou uma faixa sugerida.
Se não for possível processar uma pesquisa rapidamente, não bloqueie onPlayFromSearch()
.
Em vez disso, defina o estado de reprodução como STATE_CONNECTING
e realize a pesquisa em uma linha de execução assíncrona.
Quando a reprodução começar, considere preencher a fila da sessão de mídia com conteúdos relacionados. Por exemplo, se o usuário solicitar a reprodução de um álbum, o app pode preencher a fila com a lista de faixas do álbum. Considere também implementar suporte a resultados de pesquisa navegáveis, para que o usuário possa escolher uma faixa diferente, mas que também corresponde à consulta.
Além de consultas de "tocar", o Android Auto e o Android Automotive OS reconhecem as
consultas de voz para controlar a reprodução como "pausar música" e "próxima
música", e usam os callbacks de sessão de mídia correspondentes aos comandos,
como onPause()
e onSkipToNext()
.
Para conferir um exemplo mais detalhado de como implementar a pesquisa por voz para tocar conteúdo de áudio no app, consulte Google Assistente e apps de música.
Implementar salvaguardas de distração
Como o smartphone do usuário está conectado aos alto-falantes do carro enquanto o Android Auto é usado, tome outras precauções para ajudar a evitar a distração do motorista.
Suprimir alarmes no carro
Os apps de música do Android Auto não devem começar a tocar áudio pelos alto-falantes do carro, a menos que o usuário inicie a reprodução, por exemplo, pressionando o botão "play". Nem mesmo um alarme do app de música programado pelo usuário pode começar a tocar música pelos alto-falantes do carro.
Para atender a esse requisito, o app
pode usar CarConnection
como sinal antes de reproduzir qualquer áudio. Para verificar se o smartphone está
projetando a visualização na tela de um carro, o app pode observar o tipo de conexão
com o carro
em LiveData
e conferir se ele é igual a
CONNECTION_TYPE_PROJECTION
.
Caso o smartphone do usuário esteja sendo projetado na tela do carro, os apps de música que oferecem suporte a alarmes precisam realizar uma destas ações:
- Desativar o alarme.
- Tocar o alarme em
STREAM_ALARM
e fornecer uma interface na tela do smartphone para desativar o alarme.
Processar anúncios de mídia
Por padrão, o Android Auto exibe uma notificação quando os metadados de mídia são alterados
durante uma sessão de reprodução de áudio. Quando um app de música muda da reprodução de música
para a exibição de um anúncio, exibir uma notificação para o usuário
pode causar distração. Para impedir que o Android Auto mostre uma notificação
nesse caso, configure a chave de metadados de mídia
METADATA_KEY_IS_ADVERTISEMENT
como
METADATA_VALUE_ATTRIBUTE_PRESENT
,
conforme indicado no snippet de código abaixo:
Kotlin
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Java
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Gerenciar erros gerais
Quando o app apresentar um erro, configure o estado de reprodução como STATE_ERROR
e forneça uma mensagem de erro usando o método
setErrorMessage()
. Consulte PlaybackStateCompat
para conferir uma lista de códigos de erro que podem ser usados ao definir a mensagem de erro.
As mensagens de erro precisarão ser voltadas para o usuário e traduzidas para a localidade
dele. O Android Auto e o Android Automotive OS poderão mostrar a mensagem
de erro para o usuário.
Por exemplo, se o conteúdo não estiver disponível na região atual do usuário, use o código ERROR_CODE_NOT_AVAILABLE_IN_REGION
ao definir a mensagem de erro.
Kotlin
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Java
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
Para saber mais sobre estados de erro, consulte Como usar uma sessão de mídia: estados e erros.
Se um usuário do Android Auto precisar abrir seu app para smartphone a fim de resolver um erro, forneça essa informação a ele na mensagem. Por exemplo, a mensagem de erro diria "Faça login no [nome do seu app]", em vez de "Faça login".
Outros recursos
- Exemplo do Universal Media Player (link em inglês)
- Visão geral do app de áudio
- Visão geral do ExoPlayer