Google Ассистент позволяет управлять многими устройствами с помощью голосовых команд, такими как Google Home, телефон и многое другое. Он обладает встроенной функцией распознавания команд воспроизведения мультимедиа («воспроизвести что-нибудь от Бейонсе») и поддерживает элементы управления воспроизведением (например, пауза, пропуск, перемотка вперед, лайк).
Ассистент взаимодействует с медиаприложениями Android, используя медиасессию . Он может использовать интенты или сервисы для запуска вашего приложения и начала воспроизведения. Для достижения наилучших результатов ваше приложение должно реализовывать все функции, описанные на этой странице.
Используйте медиасессию
Каждое аудио- и видеоприложение должно поддерживать медиасессию , чтобы голосовой помощник мог управлять элементами управления воспроизведением после начала воспроизведения.
Обратите внимание, что хотя Ассистент использует только действия, перечисленные в этом разделе, рекомендуется реализовать все API для подготовки и воспроизведения, чтобы обеспечить совместимость с другими приложениями. Для любых действий, которые вы не поддерживаете, функции обратного вызова медиасессии могут просто возвращать ошибку с помощью ERROR_CODE_NOT_SUPPORTED .
Включите элементы управления воспроизведением и транспортом, установив следующие флаги в объекте MediaSession вашего приложения:
Котлин
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);
В медиасессии вашего приложения необходимо объявить поддерживаемые действия и реализовать соответствующие коллбэки медиасессии. Объявите поддерживаемые действия в 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() |
(*) Действия Google Ассистента на основе URI работают только для компаний, которые предоставляют URI Google. Чтобы узнать больше о том, как описывать свой медиаконтент для Google, см. раздел «Действия с медиаконтентом» .
Внедрение API-интерфейсов для подготовки позволяет уменьшить задержку воспроизведения после голосовой команды. Медиаприложения, стремящиеся сократить задержку воспроизведения, могут использовать это дополнительное время для кэширования контента и подготовки к воспроизведению медиафайлов.
Анализ поисковых запросов
Когда пользователь ищет определенный медиафайл, например, «Воспроизвести джаз в [название вашего приложения]» или «Послушать [название песни]» , метод обратного вызова onPrepareFromSearch() или onPlayFromSearch() получает параметр запроса и дополнительный пакет данных.
Ваше приложение должно обработать запрос голосового поиска и начать воспроизведение, выполнив следующие шаги:
- Используйте пакет дополнительных функций и строку поискового запроса, полученные в результате голосового поиска, для фильтрации результатов.
- На основе полученных результатов создайте очередь воспроизведения.
- Воспроизведите наиболее подходящий медиа-материал из результатов поиска.
Метод onPlayFromSearch() принимает параметр extras, содержащий более подробную информацию из голосового поиска. Эти данные помогут вам найти аудиоконтент в вашем приложении для воспроизведения. Если результаты поиска не предоставляют эти данные, вы можете реализовать логику для анализа исходного поискового запроса и воспроизведения соответствующих треков в зависимости от запроса.
В операционной системе 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 } }
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 } }
Более подробный пример реализации голосового поиска для воспроизведения аудиоконтента в вашем приложении можно найти в примере универсального музыкального проигрывателя для Android .
Обработка пустых запросов
Если onPrepare() , onPlay() , onPrepareFromSearch() или onPlayFromSearch() вызываются без поискового запроса, ваше медиаприложение должно воспроизводить "текущий" медиафайл. Если текущего медиафайла нет, приложение должно попытаться воспроизвести что-нибудь другое, например, песню из последнего плейлиста или случайную очередь. Ассистент использует эти API, когда пользователь запрашивает "Воспроизвести музыку в [название вашего приложения]" без дополнительной информации.
Когда пользователь говорит «Воспроизвести музыку в [название вашего приложения]» , Android Automotive OS или Android Auto пытается запустить ваше приложение и воспроизвести аудио, вызывая метод onPlayFromSearch() вашего приложения. Однако, поскольку пользователь не назвал имя медиафайла, метод onPlayFromSearch() получает пустой параметр запроса. В таких случаях ваше приложение должно немедленно ответить воспроизведением аудио, например, песни из последнего плейлиста или из случайной очереди.
Объявить поддержку устаревших функций для голосовых команд
В большинстве случаев обработка описанных выше действий воспроизведения обеспечивает вашему приложению всю необходимую функциональность воспроизведения. Однако некоторые системы требуют наличия в вашем приложении фильтра Intent для поиска. Вам следует объявить о поддержке этого фильтра Intent в файлах манифеста вашего приложения.
Включите этот код в файл манифеста для мобильного приложения:
<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 этой службы. Служба просмотра медиафайлов никогда не должна запускать Activity. Ассистент запустит вашу Activity на основе PendingIntent , который вы определяете с помощью setSessionActivity() .
Обязательно установите MediaSession.Token при инициализации службы просмотра медиафайлов . Не забывайте всегда устанавливать поддерживаемые действия воспроизведения , в том числе и во время инициализации. Ассистент ожидает, что ваше медиаприложение установит действия воспроизведения до того, как Ассистент отправит первую команду воспроизведения.
Для запуска сервиса Ассистент реализует API клиента медиабраузера. Он выполняет вызовы TransportControls, которые запускают обратные вызовы действия PLAY в медиасессии вашего приложения.
На следующей диаграмме показан порядок вызовов, генерируемых Ассистентом, и соответствующие обратные вызовы медиасессии. (Обратные вызовы подготовки отправляются только в том случае, если ваше приложение их поддерживает.) Все вызовы являются асинхронными. Ассистент не ожидает ответа от вашего приложения.

Когда пользователь отдаёт голосовую команду на воспроизведение, Ассистент отвечает коротким сообщением. Как только сообщение заканчивается, Ассистент отправляет команду ВОСПРОИЗВЕДЕНИЕ. Он не ждёт какого-либо определённого состояния воспроизведения.
Если ваше приложение поддерживает действия ACTION_PREPARE_* , то перед началом объявления Ассистент вызывает действие PREPARE .
Подключение к MediaBrowserService
Для запуска приложения с помощью сервиса Ассистент должен иметь возможность подключиться к MediaBrowserService приложения и получить его MediaSession.Token. Запросы на подключение обрабатываются в методе onGetRoot() сервиса. Существует два способа обработки запросов:
- Принимать все запросы на подключение.
- Принимайте запросы на подключение только от приложения «Ассистент».
Принимать все запросы на подключение.
Для того чтобы Ассистент мог отправлять команды в вашу медиасессию, необходимо вернуть объект BrowserRoot. Самый простой способ — разрешить всем приложениям MediaBrowser подключаться к вашему MediaBrowserService. Необходимо вернуть ненулевой объект BrowserRoot. Вот соответствующий код из Universal Music Player :
Котлин
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... }
Примите пакет приложения «Ассистент» и подпись.
Вы можете явно разрешить Ассистенту подключаться к вашей службе просмотра медиафайлов, проверив имя пакета и сигнатуру. Ваше приложение получит имя пакета в методе onGetRoot вашего MediaBrowserService. Для того чтобы Ассистент мог отправлять команды в вашу медиасессию, необходимо вернуть BrowserRoot. В примере Universal Music Player есть список известных имен пакетов и сигнатур. Ниже приведены имена пакетов и сигнатуры, используемые Google Ассистентом.
<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>