Google Assistente e apps de mídia

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()

(*) As ações baseadas em URI do Google Assistente só funcionam para empresas que fornecem URIs ao Google. Para saber mais sobre como descrever seu conteúdo de mídia para o Google, consulte Ações de mídia.

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:

  1. Use o pacote de extras e a string de consulta de pesquisa retornada da pesquisa por voz para filtrar os resultados.
  2. Crie uma fila de reprodução com base nesses resultados.
  3. 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 com state, position, playback speed, and update time. O app precisa chamar setPlaybackState() 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 implementar onSetRating(). Se o app não for compatível com classificação, ele precisará definir o tipo como RATING_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 como ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Defina a mensagem de erro PlaybackState.
    • Se necessário para reprodução, defina o estado PlaybackState como STATE_ERROR. Caso contrário, mantenha o restante do PlaybackState como está.
  • O usuário solicita uma ação indisponível.
    • Defina o código do erro PlaybackState corretamente. Por exemplo, defina PlaybackState como ERROR_CODE_NOT_SUPPORTED se a ação não tiver suporte ou como ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED se ela estiver protegida por login.
    • Defina a mensagem de erro PlaybackState.
    • Mantenha o restante do PlaybackState como está.
  • O usuário solicita conteúdo que não está disponível no app.
    • Defina o código do erro PlaybackState corretamente. Por exemplo, use ERROR_CODE_NOT_AVAILABLE_IN_REGION.
    • Defina a mensagem de erro PlaybackState.
    • Defina o estado PlaybackSate como STATE_ERROR para interromper a reprodução. Caso contrário, mantenha o restante do PlaybackState como está.
  • 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.

Iniciar a reprodução com uma sessão de mídia

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>