Com o Google Assistente, você pode usar comandos de voz para controlar vários dispositivos, como Google Home, seu smartphone e muito mais. Ele tem um recurso integrado compreender comandos de mídia ("tocar algo da Beyoncé") e oferece suporte controles de mídia (como pausar, pular, avançar, marcar com "Gostei").
O Google Assistente se comunica com apps de mídia do Android usando uma mídia sessão. Ele pode usar intents ou serviços para abrir o app e iniciar 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
Todo app de áudio e vídeo precisa implementar uma sessão de mídia para que o Assistente possa operar controles de transporte depois que a reprodução for iniciada.
Embora o 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 aplicativos. Para as ações não compatíveis,
os callbacks de sessão de mídia podem retornar um erro usando
ERROR_CODE_NOT_SUPPORTED
Ative os controles de mídia e transporte definindo essas sinalizações no
Objeto MediaSession
:
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 com suporte e implementar a
callbacks de sessão de mídia correspondentes. Declare as ações com suporte no
setActions()
A Universal Android Music Player (link em inglês) exemplo de projeto é 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 da reprodução após um comando de voz podem ser reduzidos. Os apps de mídia que querem melhorar a latência de reprodução podem usar o tempo extra para começar a armazenar 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 “Toque jazz no
[nome do app]" ou "Ouvir [título da música]", os
onPrepareFromSearch()
ou
onPlayFromSearch()
callback recebe um parâmetro de consulta e um pacote de extras.
Seu aplicativo deve analisar a consulta de pesquisa por voz e iniciar a reprodução seguindo estes 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 onPlayFromSearch()
método usa um parâmetro de extras com informações mais detalhadas da voz
pesquisa. Esses extras ajudam a encontrar o conteúdo de áudio a ser tocado no app.
Se os resultados da pesquisa não puderem fornecer esses dados, implemente a lógica
para analisar a consulta de pesquisa bruta e tocar as faixas apropriadas com base
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 onPlayFromSearch()
.
na classe MediaSession.Callback
implementação 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 um exemplo mais detalhado de como implementar a pesquisa por voz para reproduzir áudio conteúdo em seu app, consulte Universal Android Music Player amostra.
Gerenciar consultas vazias
Se onPrepare()
, onPlay()
, onPrepareFromSearch()
ou onPlayFromSearch()
forem chamados sem uma consulta de pesquisa, seu aplicativo de mídia deverá reproduzir a
mídia. Se não houver mídia no momento, o app tentará abrir algo, como
como uma música da lista de reprodução mais recente ou de uma fila aleatória. O assistente usa
essas APIs quando um usuário pede para “Tocar música no [nome do app]” sem
informações adicionais.
Quando um usuário diz "Tocar música no [nome do app]", o Android Automotive OS ou
O Android Auto tenta iniciar seu app e tocar áudio chamando onPlayFromSearch()
. No entanto, como o usuário não disse o nome do item de mídia, o onPlayFromSearch()
recebe um parâmetro de consulta vazio. Nesses casos, o app deve
respondam imediatamente tocando áudio, como uma música da última vez
playlist específica ou 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 dá ao app tudo a funcionalidade de reprodução necessária. No entanto, alguns sistemas exigem que o app contêm um filtro de intenção para pesquisa. Declare suporte para essa intent filtrar nos arquivos de manifesto do seu 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 seu app estiver ativa, o Google Assistente poderá emitir comandos de voz para controlar a reprodução e atualizar metadados de mídia. Para que isso funcione, suas deve ativar as seguintes ações e implementar a retornos de chamada:
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
.
Os comandos de voz compatíveis provavelmente variam de acordo com o tipo de conteúdo.
Tipo de conteúdo | Ações necessárias |
---|---|
Música |
Precisa ser compatível: Reproduzir, Pausar, Parar, Pular para o próximo e Pular para a anterior Recomendamos muito suporte para: "Ir para" |
Podcast |
Precisa oferecer suporte: "Reproduzir", "Pausar", "Parar" e "Ir para" Suporte recomendado para: pular para o próximo item e pular para o anterior |
Audiolivro | Precisa oferecer suporte: "Reproduzir", "Pausar", "Parar" e "Ir para" |
Rádio | Precisa oferecer suporte: Reproduzir, Pausar e Parar |
Notícias | Precisa ser compatível: Reproduzir, Pausar, Parar, Pular para o próximo e Pular para a anterior |
Vídeo |
Precisa oferecer suporte: "Tocar", "Pausar", "Parar", "Ir para", "Retroceder" e "Avançar" Recomendamos suporte para: Pular para próxima e Pular para anterior |
Você precisa oferecer suporte ao maior número possível de ações listadas acima e às suas ofertas de produtos permitir, mas ainda responder corretamente a qualquer outra ação. Por exemplo, se apenas os usuários premium podem retornar ao item anterior, você poderá aumentar um erro se um usuário de nível sem custo financeiro pedir para o Assistente retornar ao item anterior. Consulte a seção 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ê deve usar à medida que testar a implementação:
Callback MediaSession | Frase do "Ok Google" a ser usada | |
---|---|---|
onPlay() |
"Reproduzir." "Retomar." |
|
onPlayFromSearch()
onPlayFromUri() |
Música |
"Tocar música ou tocar no (nome do app)." Esta é uma consulta vazia. "Tocar (música | artista | álbum | gênero | 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 (audiobook) no (nome do app)." |
|
Podcasts | "Tocar (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." "Avance ## segundos." "Voltar ## minutos." |
|
N/A (mantenha seu
MediaMetadata
atualizada) |
"O que está tocando?" |
Erros
O Google Assistente lida com os erros de uma sessão de mídia quando eles ocorrem e informa
para os usuários. Confira se a sessão de mídia atualiza o estado de transporte e
o código do erro em seu PlaybackState
corretamente, como descrito em Como trabalhar com um
sessão de mídia de áudio. O Assistente
reconhece todos os códigos de erro retornados por
getErrorCode()
Casos que costumam ser tratados de maneira errada
Aqui estão alguns exemplos de casos de erro com os quais você precisa lidar corretamente:
- O usuário precisa fazer login
- Defina o código de erro
PlaybackState
comoERROR_CODE_AUTHENTICATION_EXPIRED
. - Defina a mensagem de erro
PlaybackState
. - Se necessário para reprodução, defina o estado
PlaybackState
comoSTATE_ERROR
. mantenha o restante doPlaybackState
como está.
- Defina o código de erro
- O usuário solicita uma ação indisponível
- Defina o código de erro
PlaybackState
corretamente. Por exemplo, definaPlaybackState
aERROR_CODE_NOT_SUPPORTED
se a ação não for compatível ouERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
se a ação for protegida contra login. - Defina a mensagem de erro
PlaybackState
. - Mantenha o restante do
PlaybackState
como está.
- Defina o código de erro
- O usuário solicita conteúdo não disponível no app
- Defina o código de 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. mantenha o restante doPlaybackState
como ele está.
- Defina o código de erro
- O usuário solicita conteúdo quando uma correspondência exata não está disponível. Por exemplo,
usuários de nível sem custo financeiro solicitando conteúdo disponível apenas para usuários de nível Premium.
- Recomendamos que você não retorne um erro e, em vez disso, priorize encontrar algo semelhante para jogar. O Google Assistente será capaz de falar mais uma resposta de voz 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 um com um link direto.
O intent e o link direto podem ter origens diferentes:
- Quando o Assistente estiver iniciar um aplicativo móvel, ele pode usar a Pesquisa Google para recuperar o conteúdo marcado que fornece uma ação de assistir com um link.
- Quando o Google Assistente inicia um app de TV, ele precisa incluir uma
Provedor de pesquisa de TV
para expor URIs de conteúdo de mídia. O Assistente envia uma consulta para
o provedor de conteúdo, que deve retornar uma intent contendo 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 especificou
uma ação, o Google Assistente vai adicionar
ACTION_VIEW
à intent.
O Google Assistente adiciona mais EXTRA_START_PLAYBACK
com o valor true
à intent enviada ao app. O app vai iniciar a reprodução quando
recebe uma intent com EXTRA_START_PLAYBACK
.
Como gerenciar intents enquanto ativo
Os usuários podem pedir ao Google Assistente para abrir algo enquanto o app ainda está aberto o conteúdo de uma solicitação anterior. Isso significa que seu app pode receber novas intents para iniciar a reprodução enquanto a atividade de reprodução já estiver iniciada e ativa.
As atividades compatíveis com intents com links diretos precisam substituir
onNewIntent()
para lidar com novas solicitações.
Ao iniciar a reprodução, o Assistente pode adicionar outros
sinalizações
à intent enviada ao app. Em particular, ele pode adicionar
FLAG_ACTIVITY_CLEAR_TOP
ou
FLAG_ACTIVITY_NEW_TASK
ou ambos. Embora seu código
não precisar processar essas sinalizações, o sistema Android responderá 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 está sendo reproduzido. É uma boa ideia testar como o app responde nesse caso. É possível usar o comando adb
ferramenta de linha 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 um
media browser service
que permita conexões a partir do Google Assistente,
o Assistente pode iniciar o app se comunicando com o
media session
.
O serviço de navegador de mídia não pode iniciar uma atividade.
O Google Assistente vai iniciar sua atividade com base no PendingIntent
que você definir.
com setSessionActivity().
Defina MediaSession.Token inicialize o serviço de navegador de mídia. Lembre-se de definir as ações de reprodução compatíveis o tempo todo, inclusive durante a inicialização. O Google Assistente espera que sua mídia para definir as ações de reprodução antes que o Google Assistente envie a primeira reprodução kubectl.
Para iniciar a partir de um serviço, o Assistente implementa as APIs de cliente do navegador de mídia. Ele executa chamadas TransportControls que acionam callbacks de ação PLAY no sessão de mídia do seu app.
O diagrama a seguir mostra a ordem das chamadas geradas pelo Assistente e as callbacks de sessão de mídia correspondentes. (Os retornos de chamada de preparação são enviados apenas caso seu app seja compatível. Todas as chamadas são assíncronas. O Google Assistente não aguardar uma 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 chamará a ação PREPARE
antes de iniciar o aviso.
Como se conectar a um MediaBrowserService
Para usar um serviço para iniciar seu app, o Assistente deve ser capaz de se conectar ao MediaBrowserService do app e
recuperar o MediaSession.Token dele. As solicitações de conexão são tratadas no
onGetRoot()
. 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. A Universal Music Player (link em inglês) O sample mantém uma lista de nomes de pacotes e assinaturas conhecidas. 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>