Google Assistente e apps de mídia

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

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

  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 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 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.

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 como ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Defina a mensagem de erro PlaybackState.
    • Se necessário para reprodução, defina o estado PlaybackState como STATE_ERROR. mantenha o restante do PlaybackState como está.
  • O usuário solicita uma ação indisponível
    • Defina o código de erro PlaybackState corretamente. Por exemplo, defina PlaybackState a ERROR_CODE_NOT_SUPPORTED se a ação não for compatível ou ERROR_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á.
  • O usuário solicita conteúdo não disponível no app
    • Defina o código de 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. mantenha o restante do PlaybackState como ele está.
  • 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.

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