O Google Assistente permite usar comandos de voz para controlar vários dispositivos, como o Google Home, seu smartphone e muito mais. Ele tem um recurso integrado para entender os comandos de mídia ("tocar algo da Beyoncé") e é compatível com controles de mídia (como pausar, pular, avançar, gostei).
O Google Assistente se comunica com apps de música do Android usando uma sessão de mídia. Ela pode usar intents ou serviços para iniciar o app e a reprodução. Para ter os melhores resultados, seu app precisa implementar todos os recursos descritos nesta página.
.Usar uma sessão de mídia
Cada app de áudio e vídeo precisa implementar uma sessão de mídia para que o Google Assistente possa operar os controles de transporte quando a reprodução for iniciada.
Embora o Google Assistente use apenas as ações listadas nesta seção, a
prática recomendada é implementar todas as APIs de preparação e reprodução para garantir
a compatibilidade com outros apps. Para ações incompatíveis,
os callbacks de sessão de mídia podem simplesmente retornar um erro usando
ERROR_CODE_NOT_SUPPORTED
.
Ative os controles de mídia e transporte definindo estas flags no objeto
MediaSession
do app:
Kotlin
session.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
Java
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
A sessão de mídia do app precisa declarar as ações compatíveis e implementar os
callbacks de sessão de mídia correspondentes. Declare as ações com suporte em
setActions()
.
O projeto de exemplo Universal Android Music Player (link em inglês) é um bom exemplo de como configurar uma sessão de mídia.
Ações de reprodução
Para iniciar a reprodução a partir de um serviço, uma sessão de mídia precisa ter as seguintes ações (PLAY
) e callbacks correspondentes:
Ação | Callback |
---|---|
ACTION_PLAY |
onPlay() |
ACTION_PLAY_FROM_SEARCH |
onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) |
onPlayFromUri() |
A sessão também precisa implementar estas ações de preparação (PREPARE
) e os callbacks correspondentes:
Ação | Callback |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
Ao implementar as APIs de preparação, a latência de reprodução após um comando de voz pode ser reduzida. Os apps de mídia que quiserem melhorar a latência de reprodução podem usar o tempo extra para começar a armazenar o conteúdo em cache e preparar a reprodução de mídia.
Analisar consultas de pesquisa
Quando um usuário pesquisa um item de mídia específico, como "Tocar jazz no
[nome do app]" ou "Ouvir [título da música]", o método de callback
onPrepareFromSearch()
ou
onPlayFromSearch()
recebe um parâmetro de consulta e um pacote de extras.
Seu app precisa analisar a consulta de pesquisa por voz e iniciar a reprodução seguindo estas etapas:
- Use o pacote de extras e a string de consulta de pesquisa retornada da pesquisa por voz para filtrar os resultados.
- Crie uma fila de reprodução com base nesses resultados.
- Toque o item de mídia mais relevante dos resultados.
O método onPlayFromSearch()
usa um parâmetro de extras com informações mais detalhadas da pesquisa
por voz. Esses extras ajudam a encontrar o conteúdo de áudio a ser tocado no app.
Se os resultados da pesquisa não fornecerem esses dados, implemente uma lógica
para analisar a consulta de pesquisa bruta e tocar as faixas adequadas com base na
consulta.
Os seguintes extras são compatíveis com o Android Automotive OS e o Android Auto:
O snippet de código a seguir mostra como substituir o método onPlayFromSearch()
na implementação de MediaSession.Callback
para analisar a consulta de pesquisa por voz e iniciar a reprodução:
Kotlin
override fun onPlayFromSearch(query: String?, extras: Bundle?) { if (query.isNullOrEmpty()) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS) if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) { isArtistFocus = true artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) { isAlbumFocus = true album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue var result: String? = when { isArtistFocus -> artist?.also { searchMusicByArtist(it) } isAlbumFocus -> album?.also { searchMusicByAlbum(it) } else -> null } result = result ?: run { // No focus found, search by query for song title query?.also { searchMusicBySongTitle(it) } } if (result?.isNotEmpty() == true) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result) } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Java
@Override public void onPlayFromSearch(String query, Bundle extras) { if (TextUtils.isEmpty(query)) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS); if (TextUtils.equals(mediaFocus, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { isArtistFocus = true; artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { isAlbumFocus = true; album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM); } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue if (isArtistFocus) { result = searchMusicByArtist(artist); } else if (isAlbumFocus) { result = searchMusicByAlbum(album); } if (result == null) { // No focus found, search by query for song title result = searchMusicBySongTitle(query); } if (result != null && !result.isEmpty()) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result); } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Para ver um exemplo mais detalhado de como implementar a pesquisa por voz para tocar conteúdo de áudio no app, consulte o exemplo Universal Android Music Player (link em inglês).
Gerenciar consultas vazias
Se onPrepare()
, onPlay()
, onPrepareFromSearch()
ou onPlayFromSearch()
forem chamados sem uma consulta de pesquisa, seu app de música precisará abrir a mídia "atual". Se não houver mídia atual, o app vai tentar tocar algo, como
uma música da playlist mais recente ou uma fila aleatória. O assistente usa
essas APIs quando um usuário pede para "Tocar música no [nome do app]" sem
outras informações.
Quando um usuário diz "Tocar música no [nome do seu app]", o Android Automotive OS ou
o Android Auto tenta iniciar o app e tocar áudio chamando o método onPlayFromSearch()
do app. No entanto, como o usuário não disse o nome do item de mídia, o método onPlayFromSearch()
recebe um parâmetro de consulta vazio. Nesses casos, seu app precisa
responder tocando áudio imediatamente, como uma música da playlist
mais recente ou de uma fila aleatória.
Declarar compatibilidade legada para ações de voz
Na maioria dos casos, o processamento das ações de reprodução descritas acima oferece ao app toda a funcionalidade de reprodução de que ele precisa. No entanto, alguns sistemas exigem que o app contenha um filtro de intent para a pesquisa. Declare suporte para esse filtro de intent nos arquivos de manifesto do app.
Inclua este código no arquivo de manifesto de um app para smartphones:
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Controles de transporte
Depois que a sessão de mídia do app estiver ativa, o Google Assistente poderá emitir comandos de voz para controlar a reprodução e atualizar os metadados de mídia. Para que isso funcione, o código precisa ativar as ações abaixo e implementar os callbacks correspondentes:
Ação | Callback | Descrição |
---|---|---|
ACTION_SKIP_TO_NEXT |
onSkipToNext() |
Próximo vídeo |
ACTION_SKIP_TO_PREVIOUS |
onSkipToPrevious() |
Música anterior |
ACTION_PAUSE, ACTION_PLAY_PAUSE |
onPause() |
Pausar |
ACTION_STOP |
onStop() |
Parar |
ACTION_PLAY |
onPlay() |
Retomar |
ACTION_SEEK_TO |
onSeekTo() |
Retroceder 10 segundos |
ACTION_SET_RATING |
onSetRating(android.support.v4.media.RatingCompat) |
Sinal de Gostei/Não gostei |
ACTION_SET_CAPTIONING_ENABLED |
onSetCaptioningEnabled(boolean) |
Ativar/desativar legendas |
Observação:
- Para que comandos de busca funcionem, o
PlaybackState
precisa estar atualizado comstate, position, playback speed, and update time
. O app precisa chamarsetPlaybackState()
quando o estado for modificado. - O app também precisa manter os metadados da sessão de mídia atualizados. Isso é compatível com perguntas como "qual música está tocando?" O app precisa chamar
setMetadata()
quando os campos aplicáveis (como título da faixa, artista e nome) são modificados. MediaSession.setRatingType()
precisa ser definido para indicar o tipo de avaliação compatível e o app precisa implementaronSetRating()
. Se o app não for compatível com classificação, ele precisará definir o tipo comoRATING_NONE
.
As ações por voz compatíveis provavelmente variam de acordo com o tipo de conteúdo.
Tipo de conteúdo | Ações necessárias |
---|---|
Música |
Suporte obrigatório: "Tocar", "Pausar", "Parar", "Pular para o próximo" e "Pular para o anterior" Suporte altamente recomendado para: buscar para |
Podcast |
Suporte necessário: "Tocar", "Pausar", "Parar" e "Procurar" Suporte recomendado para: "Pular para o próximo" e "Pular para o anterior" |
Audiolivro | Suporte necessário: "Tocar", "Pausar", "Parar" e "Procurar" |
Rádio | Suporte necessário: "Tocar", "Pausar" e "Parar" |
Notícias | Suporte obrigatório: "Tocar", "Pausar", "Parar", "Pular para o próximo" e "Pular para o anterior" |
Vídeo |
Suporte obrigatório: "Tocar", "Pausar", "Parar", "Procurar", "Retroceder" e "Avançar" Suporte altamente recomendado: pular para o próximo e pular para o anterior |
Você precisa oferecer suporte ao maior número possível das ações listadas acima, mas ainda assim responder adequadamente a quaisquer outras ações. Por exemplo, se apenas usuários premium conseguirem retornar ao item anterior, você poderá gerar um erro caso um usuário de nível sem custo financeiro peça ao Google Assistente para retornar ao item anterior. Consulte a seção sobre tratamento de erros para mais orientações.
Exemplos de consultas de voz para testar
A tabela a seguir descreve alguns exemplos de consultas que você precisa usar ao testar sua implementação:
Callback MediaSession | Frase de "Ok Google" a ser usada | |
---|---|---|
onPlay() |
"Reproduzir." "Retomar". |
|
onPlayFromSearch()
onPlayFromUri() |
Música |
"Tocar músicas ou músicas no (nome do app)". Essa consulta está vazia. "Tocar (song | artist | album | genre | playlist) no (nome do app)." |
Rádio | "Tocar (frequência | estação) no (nome do app)." | |
Audiobook |
"Ler meu audiolivro no (nome do app)." "Ler (audiolivro) em (nome do app)." |
|
Podcasts | “Ouvir (podcast) no (nome do app).” | |
onPause() |
"Pausar." | |
onStop() |
“Parar.” | |
onSkipToNext() |
"Next (música | episódio | faixa)." | |
onSkipToPrevious() |
"Anterior (música | episódio | faixa)." | |
onSeekTo() |
"Reiniciar." "Pular ## segundos." "Voltar ## minutos." |
|
N/A (mantenha o
MediaMetadata
atualizado) |
"O que está tocando?" |
Erros
O Google Assistente processa os erros de uma sessão de mídia quando eles ocorrem e os informa
aos usuários. Confira se a sessão de mídia atualiza o estado de transporte e
o código de erro no PlaybackState
corretamente, conforme descrito em Como trabalhar com uma
sessão de mídia. O Google Assistente
reconhece todos os códigos de erro retornados por
getErrorCode()
.
Casos mal processados
Confira abaixo alguns exemplos de casos de erro que precisam ser processados corretamente:
- O usuário precisa fazer login.
- Defina o código do erro
PlaybackState
comoERROR_CODE_AUTHENTICATION_EXPIRED
. - Defina a mensagem de erro
PlaybackState
. - Se necessário para reprodução, defina o estado
PlaybackState
comoSTATE_ERROR
. Caso contrário, mantenha o restante doPlaybackState
como está.
- Defina o código do erro
- O usuário solicita uma ação indisponível.
- Defina o código do erro
PlaybackState
corretamente. Por exemplo, definaPlaybackState
comoERROR_CODE_NOT_SUPPORTED
se a ação não tiver suporte ou comoERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
se ela estiver protegida por login. - Defina a mensagem de erro
PlaybackState
. - Mantenha o restante do
PlaybackState
como está.
- Defina o código do erro
- O usuário solicita conteúdo que não está disponível no app.
- Defina o código do erro
PlaybackState
corretamente. Por exemplo, useERROR_CODE_NOT_AVAILABLE_IN_REGION
. - Defina a mensagem de erro
PlaybackState
. - Defina o estado
PlaybackSate
comoSTATE_ERROR
para interromper a reprodução. Caso contrário, mantenha o restante doPlaybackState
como está.
- Defina o código do erro
- O usuário solicita conteúdo no qual uma correspondência exata não está disponível. Por exemplo, um
usuário de nível sem custo financeiro que solicita conteúdo disponível apenas para usuários do nível premium.
- Recomendamos que você não retorne um erro e priorize a descoberta de algo semelhante. O Google Assistente vai processar a resposta de voz mais relevante antes do início da reprodução.
Reprodução com um intent
O Google Assistente pode iniciar um app de áudio ou vídeo e iniciar a reprodução enviando uma intent com um link direto.
O intent e o link direto podem ter origens diferentes:
- Quando o Google Assistente está iniciando um app para dispositivos móveis, ele pode usar a Pesquisa Google para acessar o conteúdo marcado que fornece uma ação de assistir com um link.
- Quando o Google Assistente inicia um app de TV, seu app precisa incluir um
provedor de pesquisa de TV
para expor URIs para conteúdo de mídia. O Google Assistente envia uma consulta ao
provedor de conteúdo, que precisa retornar uma intent com um URI para o link direto e
uma ação opcional.
Se a consulta retornar uma ação na intent,
o Google Assistente vai enviar essa ação e o URI de volta ao app.
Se o provedor não tiver especificado
uma ação, o Google Assistente adicionará
ACTION_VIEW
à intent.
O Google Assistente adiciona o EXTRA_START_PLAYBACK
extra com o valor true
à intent enviada ao app. O app precisa iniciar a reprodução quando
receber uma intent com EXTRA_START_PLAYBACK
.
Como gerenciar intents enquanto ativo
Os usuários podem pedir ao Google Assistente para tocar algo enquanto o app ainda está reproduzindo conteúdo de uma solicitação anterior. Isso significa que o app pode receber novas intents para iniciar a reprodução enquanto a atividade de reprodução já estiver iniciada e ativa.
As atividades que oferecem suporte a intents com links diretos precisam modificar
onNewIntent()
para processar novas solicitações.
Ao iniciar a reprodução, o Google Assistente pode adicionar outras
sinalizações
à intent que envia ao app. Em particular, ele pode adicionar
FLAG_ACTIVITY_CLEAR_TOP
,
FLAG_ACTIVITY_NEW_TASK
ou ambos. Embora o código
não precise processar essas flags, o sistema Android responde a elas.
Isso pode afetar o comportamento do app quando uma segunda solicitação de reprodução com um novo URI chegar
enquanto o URI anterior ainda estiver sendo reproduzido. É uma boa ideia testar como o app responde nesse caso. Você pode usar a ferramenta de linha de comando adb
para simular a situação (a constante 0x14000000
é o booleano bit a bit OR das duas sinalizações):
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<first_uri>"' -f 0x14000000
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<second_uri>"' -f 0x14000000
Reprodução a partir de um serviço
Se o app tiver uma
media browser service
que permita conexões com o Google Assistente,
ele poderá iniciar o app se comunicando com o
media session
do serviço.
O serviço de navegador de mídia não pode iniciar uma atividade.
O Google Assistente vai iniciar a atividade com base no PendingIntent
definido
com setSessionActivity().
Defina MediaSession.Token ao inicializar o serviço de navegador de mídia. Lembre-se de definir as ações de reprodução compatíveis sempre, inclusive durante a inicialização. O Google Assistente espera que seu app de música defina as ações de reprodução antes de enviar o primeiro comando de reprodução.
Para iniciar a partir de um serviço, o Assistente implementa as APIs de cliente do navegador de mídia. Ela realiza chamadas de TransportControls que acionam callbacks de ação PLAY na sessão de mídia do app.
O diagrama a seguir mostra a ordem das chamadas geradas pelo Google Assistente e os callbacks de sessão de mídia correspondentes. Os callbacks de preparação são enviados somente se o app tiver suporte a eles. Todas as chamadas são assíncronas. O Assistente não aguardará nenhuma resposta do app.
Quando um usuário emite um comando de voz para reprodução, o Assistente responde com um breve aviso. Assim que o aviso é concluído, o Assistente emite uma ação PLAY. Ele não espera nenhum estado de reprodução específico.
Se o app for compatível com as ações ACTION_PREPARE_*
, o Google Assistente vai chamar PREPARE
antes de iniciar o aviso.
Como se conectar a um MediaBrowserService
Para usar um serviço para iniciar o app, o Google Assistente precisa se conectar ao MediaBrowserService do app e
recuperar o MediaSession.Token dele. As solicitações de conexão são processadas no método
onGetRoot()
do serviço. Há duas maneiras de processar solicitações:
- Aceitar todas as solicitações de conexão
- Aceitar solicitações de conexão apenas do app Assistente
Aceitar todas as solicitações de conexão
Você precisa retornar um BrowserRoot para permitir que o Assistente envie comandos para a sessão de mídia. A maneira mais fácil é permitir que todos os apps do MediaBrowser se conectem ao MediaBrowserService. Você precisa retornar um BrowserRoot não nulo. Veja o código aplicável do Universal Music Player (link em inglês):
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. Log.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. Returning empty " + "browser root so all apps can use MediaController. $clientPackageName") return MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null) } // Return browser roots for browsing... }
Java
@Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. " + "Returning empty browser root so all apps can use MediaController." + clientPackageName); return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null); } // Return browser roots for browsing... }
Aceitar o pacote e a assinatura do app do Assistente
Você pode permitir explicitamente que o Assistente se conecte ao serviço de navegador de mídia verificando o nome e a assinatura do pacote. O app receberá o nome do pacote no método onGetRoot do MediaBrowserService. Você precisa retornar um BrowserRoot para permitir que o Assistente envie comandos para a sessão de mídia. O exemplo do Universal Music Player (link em inglês) mantém uma lista de nomes de pacotes e assinaturas conhecidos. Veja abaixo os nomes dos pacotes e as assinaturas usadas pelo Google Assistente.
<signature name="Google" package="com.google.android.googlequicksearchbox">
<key release="false">19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00</key>
<key release="true">f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83</key>
</signature>
<signature name="Google Assistant on Android Automotive OS" package="com.google.android.carassistant">
<key release="false">17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15</key>
<key release="true">74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2</key>
</signature>