Google Assistant позволяет вам использовать голосовые команды для управления многими устройствами, такими как Google Home, телефон и многое другое. Он имеет встроенную возможность понимать мультимедийные команды («воспроизвести что-нибудь Бейонсе») и поддерживает элементы управления мультимедиа (например, пауза, пропуск, перемотка вперед, большой палец вверх).
Ассистент взаимодействует с мультимедийными приложениями Android с помощью мультимедийного сеанса . Он может использовать намерения или службы для запуска вашего приложения и начала воспроизведения. Для достижения наилучших результатов в вашем приложении должны быть реализованы все функции, описанные на этой странице.
Используйте медиа-сессию
Каждое аудио- и видеоприложение должно реализовать медиа-сеанс , чтобы Помощник мог управлять транспортировкой после начала воспроизведения.
Обратите внимание: хотя Ассистент использует только действия, перечисленные в этом разделе, рекомендуется реализовать все API подготовки и воспроизведения, чтобы обеспечить совместимость с другими приложениями. Для любых действий, которые вы не поддерживаете, обратные вызовы медиа-сеанса могут просто вернуть ошибку, используя ERROR_CODE_NOT_SUPPORTED
.
Включите элементы управления мультимедиа и транспортом, установив эти флаги в объекте MediaSession
вашего приложения:
Котлин
session.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
Ява
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
Медиа-сеанс вашего приложения должен объявить действия, которые он поддерживает, и реализовать соответствующие обратные вызовы медиа-сеанса. Объявите поддерживаемые действия в setActions()
.
Пример проекта универсального музыкального проигрывателя Android — хороший пример настройки мультимедийного сеанса.
Действия воспроизведения
Чтобы начать воспроизведение из сервиса , медиасеанс должен иметь следующие действия PLAY
и их обратные вызовы:
Действие | Перезвонить |
---|---|
ACTION_PLAY | onPlay() |
ACTION_PLAY_FROM_SEARCH | onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) | onPlayFromUri() |
В вашем сеансе также должны быть реализованы действия PREPARE
и их обратные вызовы:
Действие | Перезвонить |
---|---|
ACTION_PREPARE | onPrepare() |
ACTION_PREPARE_FROM_SEARCH | onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) | onPrepareFromUri() |
За счет реализации API подготовки можно уменьшить задержку воспроизведения после голосовой команды. Медиа-приложения, которые хотят уменьшить задержку воспроизведения, могут использовать дополнительное время для начала кэширования контента и подготовки воспроизведения мультимедиа.
Парсинг поисковых запросов
Когда пользователь ищет определенный медиа-элемент, например «Включи джаз в [название вашего приложения]» или «Слушай [название песни]» , метод обратного вызова onPrepareFromSearch()
или onPlayFromSearch()
получает параметр запроса и пакет дополнительных функций. .
Ваше приложение должно проанализировать запрос голосового поиска и начать воспроизведение, выполнив следующие действия:
- Используйте пакет дополнительных возможностей и строку поискового запроса, полученную в результате голосового поиска, для фильтрации результатов.
- Постройте очередь воспроизведения на основе этих результатов.
- Воспроизведите наиболее релевантный медиа-элемент из результатов.
Метод onPlayFromSearch()
принимает дополнительный параметр с более подробной информацией из голосового поиска. Эти дополнительные возможности помогут вам найти аудиоконтент в вашем приложении для воспроизведения. Если результаты поиска не могут предоставить эти данные, вы можете реализовать логику для анализа необработанного поискового запроса и воспроизведения соответствующих дорожек на основе запроса.
Следующие дополнительные возможности поддерживаются в Android Automotive OS и Android Auto:
В следующем фрагменте кода показано, как переопределить метод onPlayFromSearch()
в вашей реализации MediaSession.Callback
, чтобы проанализировать запрос голосового поиска и начать воспроизведение:
Котлин
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 } }
Ява
@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 } }
Более подробный пример реализации голосового поиска для воспроизведения аудиоконтента в вашем приложении см. в примере универсального музыкального проигрывателя Android .
Обработка пустых запросов
Если onPrepare()
, onPlay()
, onPrepareFromSearch()
или onPlayFromSearch()
вызываются без поискового запроса, ваше мультимедийное приложение должно воспроизводить «текущий» медиафайл. Если текущий носитель отсутствует, приложение должно попытаться воспроизвести что-нибудь, например песню из самого последнего списка воспроизведения или случайной очереди. Помощник использует эти API, когда пользователь просит «Воспроизвести музыку в [название вашего приложения]» без дополнительной информации.
Когда пользователь говорит «Воспроизвести музыку в [имя вашего приложения]» , Android Automotive OS или Android Auto пытается запустить ваше приложение и воспроизвести звук, вызывая метод onPlayFromSearch()
вашего приложения. Однако, поскольку пользователь не указал имя элемента мультимедиа, метод onPlayFromSearch()
получает пустой параметр запроса. В этих случаях ваше приложение должно немедленно реагировать, воспроизводя звук, например песню из самого последнего плейлиста или случайной очереди.
Объявить устаревшую поддержку голосовых действий
В большинстве случаев обработка действий воспроизведения, описанных выше, дает вашему приложению все необходимые функции воспроизведения. Однако некоторые системы требуют, чтобы ваше приложение содержало фильтр намерений для поиска. Вы должны объявить поддержку этого фильтра намерений в файлах манифеста вашего приложения.
Включите этот код в файл манифеста приложения для телефона:
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Транспортный контроль
После того, как медиа-сеанс вашего приложения активен, Ассистент может подавать голосовые команды для управления воспроизведением и обновления метаданных мультимедиа. Чтобы это работало, ваш код должен разрешить следующие действия и реализовать соответствующие обратные вызовы:
Действие | Перезвонить | Описание |
---|---|---|
ACTION_SKIP_TO_NEXT | onSkipToNext() | Следующее видео |
ACTION_SKIP_TO_PREVIOUS | onSkipToPrevious() | Предыдущая песня |
ACTION_PAUSE, ACTION_PLAY_PAUSE | onPause() | Пауза |
ACTION_STOP | onStop() | Останавливаться |
ACTION_PLAY | onPlay() | Резюме |
ACTION_SEEK_TO | onSeekTo() | Перемотка назад на 30 секунд |
ACTION_SET_RATING | onSetRating(android.support.v4.media.RatingCompat) | Большой палец вверх/вниз. |
ACTION_SET_CAPTIONING_ENABLED | onSetCaptioningEnabled(boolean) | Включите/выключите субтитры. |
Пожалуйста, обрати внимание:
- Чтобы команды поиска работали,
PlaybackState
должен быть актуальным поstate, position, playback speed, and update time
. Приложение должно вызыватьsetPlaybackState()
при изменении состояния. - Мультимедийное приложение также должно поддерживать актуальность метаданных медиа-сеанса. Это поддерживает такие вопросы, как «какая песня играет?» Приложение должно вызывать
setMetadata()
при изменении соответствующих полей (таких как название трека, исполнитель и имя). -
MediaSession.setRatingType()
должен быть установлен для указания типа рейтинга, который поддерживает приложение, и приложение должно реализоватьonSetRating()
. Если приложение не поддерживает рейтинг, ему следует установить тип рейтингаRATING_NONE
.
Поддерживаемые вами голосовые действия, скорее всего, будут различаться в зависимости от типа контента.
Тип контента | Необходимые действия |
---|---|
Музыка | Должна поддерживаться : воспроизведение, пауза, остановка, переход к следующему и переход к предыдущему. Настоятельно рекомендуем поддержку : Seek To |
Подкаст | Должна поддерживаться : воспроизведение, пауза, остановка и поиск. Рекомендовать поддержку для : Перейти к следующему и Перейти к предыдущему. |
Аудиокнига | Должна поддерживаться : воспроизведение, пауза, остановка и поиск. |
Радио | Должна поддерживаться : воспроизведение, пауза и остановка. |
Новости | Должна поддерживаться : воспроизведение, пауза, остановка, переход к следующему и переход к предыдущему. |
Видео | Должна поддерживаться : воспроизведение, пауза, остановка, поиск, перемотка назад и вперед. Настоятельно рекомендуем поддержку : Перейти к следующему и Перейти к предыдущему. |
Вы должны поддерживать столько действий, перечисленных выше, сколько позволяют ваши предложения продуктов, но при этом корректно реагировать на любые другие действия. Например, если только премиум-пользователи имеют возможность вернуться к предыдущему элементу, вы можете вызвать ошибку, если пользователь бесплатного уровня попросит Ассистента вернуться к предыдущему элементу. Дополнительную информацию см. в разделе «Обработка ошибок» .
Примеры голосовых запросов, которые стоит попробовать
В следующей таблице приведены некоторые примеры запросов, которые следует использовать при тестировании реализации:
Обратный вызов MediaSession | Фраза «Окей, Google» для использования | |
---|---|---|
onPlay() | "Играть." "Резюме." | |
onPlayFromSearch() onPlayFromUri() | Музыка | «Включи музыку или песни в (название приложения) ». Это пустой запрос. «Воспроизвести (песню | исполнителя | альбом | жанр | плейлист) в (название приложения) ». |
Радио | «Включить (частота | станция) на (название приложения) ». | |
Аудиокнига | «Прочтите мою аудиокнигу в (название приложения) ». «Читайте (аудиокнигу) в (название приложения) ». | |
Подкасты | «Включи (подкаст) в (название приложения) ». | |
onPause() | «Пауза». | |
onStop() | "Останавливаться." | |
onSkipToNext() | «Следующий (песня | эпизод | трек) ». | |
onSkipToPrevious() | «Предыдущая (песня | серия | трек) ». | |
onSeekTo() | "Перезапуск." «Пропустить вперед ## секунды». «Вернитесь на ## минут назад». | |
Н/Д (обновляйте свои MediaMetadata ) | «Что играет?» |
Ошибки
Помощник обрабатывает ошибки сеанса мультимедиа, когда они происходят, и сообщает о них пользователям. Убедитесь, что ваш медиа-сеанс правильно обновляет состояние транспорта и код ошибки в своем PlaybackState
, как описано в разделе «Работа с медиа-сеансом» . Помощник распознает все коды ошибок, возвращаемые getErrorCode()
.
Распространенные случаи неправильного решения
Вот несколько примеров ошибок, которые следует правильно обрабатывать:
- Пользователю необходимо войти в систему
- Установите для кода ошибки
PlaybackState
значениеERROR_CODE_AUTHENTICATION_EXPIRED
. - Установите сообщение об ошибке
PlaybackState
. - Если это необходимо для воспроизведения, установите для состояния
PlaybackState
значениеSTATE_ERROR
, в противном случае сохраните остальную частьPlaybackState
как есть.
- Установите для кода ошибки
- Пользователь запрашивает недоступное действие
- Установите соответствующий код ошибки
PlaybackState
. Например, задайте дляPlaybackState
значениеERROR_CODE_NOT_SUPPORTED
если действие не поддерживается, илиERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
, если действие защищено входом в систему. - Установите сообщение об ошибке
PlaybackState
. - Остальную часть
PlaybackState
сохраните как есть.
- Установите соответствующий код ошибки
- Пользователь запрашивает контент, недоступный в приложении
- Установите соответствующий код ошибки
PlaybackState
. Например, используйтеERROR_CODE_NOT_AVAILABLE_IN_REGION
. - Установите сообщение об ошибке
PlaybackState
. - Установите для состояния
PlaybackSate
значениеSTATE_ERROR
, чтобы прервать воспроизведение, в противном случае сохраните остальную частьPlaybackState
как есть.
- Установите соответствующий код ошибки
- Пользователь запрашивает контент, точное совпадение которого невозможно. Например, пользователь бесплатного уровня запрашивает контент, доступный только пользователям премиум-уровня.
- Мы рекомендуем вам не возвращать ошибку, а вместо этого уделить первоочередное внимание поиску чего-то похожего на игру. Ассистент проговорит наиболее подходящий голосовой ответ перед началом воспроизведения.
Воспроизведение с намерением
Ассистент может запустить аудио- или видеоприложение и начать воспроизведение, отправив намерение с глубокой ссылкой .
Намерение и его глубокая ссылка могут исходить из разных источников:
- Когда Ассистент запускает мобильное приложение, он может использовать поиск Google для получения размеченного контента, который содержит ссылку на действие просмотра .
- Когда Ассистент запускает телевизионное приложение, ваше приложение должно включать поставщика ТВ-поиска , чтобы предоставлять URI для мультимедийного контента. Помощник отправляет запрос поставщику контента, который должен вернуть намерение, содержащее URI для глубокой ссылки и необязательное действие. Если запрос возвращает действие в намерении, Помощник отправляет это действие и URI обратно в ваше приложение. Если провайдер не указал действие, Ассистент добавит
ACTION_VIEW
в намерение.
Ассистент добавляет дополнительный EXTRA_START_PLAYBACK
со значением true
намерению, которое он отправляет в ваше приложение. Ваше приложение должно начать воспроизведение, когда оно получит намерение с EXTRA_START_PLAYBACK
.
Обработка намерений во время активности
Пользователи могут попросить Ассистента воспроизвести что-нибудь, пока ваше приложение все еще воспроизводит контент из предыдущего запроса. Это означает, что ваше приложение может получить новые намерения начать воспроизведение, в то время как его действие воспроизведения уже запущено и активно.
Действия, которые поддерживают намерения с помощью глубоких ссылок, должны переопределять onNewIntent()
для обработки новых запросов.
При запуске воспроизведения Ассистент может добавить дополнительные флаги к намерению, которое он отправляет в ваше приложение. В частности, он может добавить FLAG_ACTIVITY_CLEAR_TOP
или FLAG_ACTIVITY_NEW_TASK
или оба. Хотя вашему коду не требуется обрабатывать эти флаги, система Android реагирует на них. Это может повлиять на поведение вашего приложения, когда поступит второй запрос воспроизведения с новым URI, в то время как предыдущий URI все еще воспроизводится. Хорошей идеей будет проверить, как ваше приложение отреагирует в этом случае. Вы можете использовать инструмент командной строки adb
для моделирования ситуации (константа 0x14000000
представляет собой логическое поразрядное ИЛИ двух флагов):
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
Воспроизведение с сервиса
Если в вашем приложении есть media browser service
, которая разрешает соединения с Помощником, Помощник может запустить приложение, связавшись с media session
службы. Служба медиабраузера никогда не должна запускать действие. Помощник запустит вашу активность на основе PendingIntent
который вы определяете с помощью setSessionActivity() .
Обязательно установите MediaSession.Token при инициализации службы медиабраузера . Не забывайте всегда устанавливать поддерживаемые действия воспроизведения , в том числе во время инициализации. Помощник ожидает, что ваше мультимедийное приложение настроит действия по воспроизведению до того, как Помощник отправит первую команду воспроизведения.
Чтобы начать работу со службой, Assistant реализует клиентские API медиабраузера. Он выполняет вызовы TransportControls, которые запускают обратные вызовы действия PLAY в сеансе мультимедиа вашего приложения.
На следующей диаграмме показан порядок вызовов, генерируемых Ассистентом, и соответствующие обратные вызовы сеанса мультимедиа. (Обратные вызовы подготовки отправляются только в том случае, если ваше приложение их поддерживает.) Все вызовы являются асинхронными. Ассистент не ждет ответа от вашего приложения.
Когда пользователь дает голосовую команду для воспроизведения, Ассистент отвечает коротким объявлением. Как только объявление будет завершено, Ассистент выполнит действие ВОСПРОИЗВЕДЕНИЕ. Он не ожидает какого-либо конкретного состояния воспроизведения.
Если ваше приложение поддерживает действия ACTION_PREPARE_*
, Ассистент вызывает действие PREPARE
перед запуском объявления.
Подключение к MediaBrowserService
Чтобы использовать службу для запуска вашего приложения, Помощник должен иметь возможность подключаться к MediaBrowserService приложения и получать его MediaSession.Token. Запросы на подключение обрабатываются в методе службы onGetRoot()
. Существует два способа обработки запросов:
- Принимать все запросы на подключение
- Принимайте запросы на подключение только из приложения Assistant.
Принимать все запросы на подключение
Вы должны вернуть BrowserRoot, чтобы позволить Помощнику отправлять команды в ваш мультимедийный сеанс. Самый простой способ — разрешить всем приложениям MediaBrowser подключаться к вашему MediaBrowserService. Вы должны вернуть ненулевой BrowserRoot. Вот применимый код из универсального музыкального проигрывателя :
Котлин
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... }
Ява
@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... }
Примите пакет приложения Assistant и подпись.
Вы можете явно разрешить Помощнику подключаться к службе вашего медиабраузера, проверив имя и подпись его пакета. Ваше приложение получит имя пакета в методе onGetRoot вашего MediaBrowserService. Вы должны вернуть BrowserRoot, чтобы позволить Помощнику отправлять команды в ваш мультимедийный сеанс. Образец Universal Music Player поддерживает список известных имен и подписей пакетов. Ниже приведены названия и подписи пакетов, которые используются Google Assistant.
<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>