Google Ассистент и мультимедийные приложения

Google Ассистент позволяет управлять многими устройствами, например Google Home и телефоном, с помощью голосовых команд. Он понимает команды, связанные с медиаконтентом (например, "Включи что-нибудь из Бейонсе"), и поддерживает элементы управления мультимедиа (например, паузу, пропуск, перемотку вперед, лайк).

Ассистент взаимодействует с мультимедийными приложениями Android через медиасеанс. Для запуска приложения и воспроизведения могут использоваться намерения или сервисы. Чтобы добиться наилучших результатов, реализуйте в своем приложении все функции, описанные на этой странице.

Как использовать сеанс воспроизведения медиаконтента

В каждом аудио- и видеоприложении должен быть реализован медиасеанс, чтобы Ассистент мог управлять воспроизведением.

Обратите внимание, что хотя Ассистент использует только действия, перечисленные в этом разделе, рекомендуется реализовать все API подготовки и воспроизведения, чтобы обеспечить совместимость с другими приложениями. Для любых неподдерживаемых действий обратные вызовы сеанса мультимедиа могут просто возвращать ошибку с помощью ERROR_CODE_NOT_SUPPORTED.

Чтобы включить элементы управления мультимедиа и транспортом, установите следующие флаги в объекте 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);

В медиасеансе приложения должны быть объявлены поддерживаемые действия и реализованы соответствующие обратные вызовы. Объявите поддерживаемые действия в файле setActions().

Пример настройки сеанса воспроизведения можно найти в проекте Universal Android Music Player.

Действия при воспроизведении

Чтобы начать воспроизведение из сервиса, в медиасеансе должны быть следующие действия 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() получает параметр запроса и набор дополнительных данных.

Приложение должно проанализировать голосовой поисковый запрос и начать воспроизведение, выполнив следующие действия:

  1. Используйте пакет extras и строку поискового запроса, возвращенную голосовым поиском, чтобы отфильтровать результаты.
  2. Создайте очередь воспроизведения на основе этих результатов.
  3. Воспроизвести наиболее подходящий медиаобъект из результатов.

Метод onPlayFromSearch() принимает параметр extras с более подробной информацией из голосового поиска. Эти дополнительные параметры помогают найти аудиоконтент в приложении для воспроизведения. Если результаты поиска не могут предоставить эти данные, вы можете реализовать логику для анализа необработанного поискового запроса и воспроизведения соответствующих треков на основе запроса.

В Android Automotive OS и Android Auto поддерживаются следующие дополнительные параметры:

В приведенном ниже фрагменте кода показано, как переопределить метод onPlayFromSearch() в реализации MediaSession.Callback, чтобы обработать голосовой поисковый запрос и начать воспроизведение:

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

Более подробный пример того, как реализовать голосовой поиск для воспроизведения аудиоконтента в приложении, можно найти в примере Universal Android Music Player.

Обработка пустых запросов

Если onPrepare(), onPlay(), onPrepareFromSearch() или onPlayFromSearch() вызываются без поискового запроса, мультимедийное приложение должно воспроизвести текущий контент. Если в данный момент ничего не воспроизводится, приложение должно попытаться воспроизвести что-нибудь, например песню из последнего плейлиста или случайную очередь. Ассистент использует эти API, когда пользователь просит включить музыку в вашем приложении, не указывая дополнительную информацию.

Когда пользователь говорит "Включи музыку в [название приложения]", Android Automotive OS или Android Auto пытается запустить ваше приложение и воспроизвести аудио, вызывая метод onPlayFromSearch(). Однако, поскольку пользователь не назвал мультимедийный объект, метод onPlayFromSearch() получает пустой параметр запроса. В таких случаях приложение должно сразу же начать воспроизведение аудио, например трека из последнего плейлиста или случайной очереди.

Как объявить о поддержке голосовых команд устаревшей версии

В большинстве случаев описанных выше действий достаточно для реализации всех необходимых функций воспроизведения. Однако в некоторых системах требуется, чтобы в приложении был фильтр 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.

Подкаст

Обязательные: Play, Pause, Stop и Seek To.

Рекомендуется поддержка перехода к следующему и предыдущему трекам.

Аудиокнига Обязательные: Play, Pause, Stop и Seek To.
Радио Обязательные функции: воспроизведение, пауза и остановка.
Новости Обязательные функции: воспроизведение, пауза, остановка, переход к следующему и предыдущему треку.
Видео

Обязательные: Play, Pause, Stop, Seek To, Rewind и Fast Forward.

Настоятельно рекомендуется поддержка перехода к следующему и предыдущему трекам.

Вы должны поддерживать как можно больше действий из списка выше, но при этом корректно реагировать на любые другие действия. Например, если только пользователи с премиум-подпиской могут вернуться к предыдущему элементу, вы можете выдать ошибку, если пользователь с бесплатным планом попросит Ассистента вернуться к предыдущему элементу. Подробные инструкции приведены в разделе Обработка ошибок.

Примеры голосовых запросов

В таблице ниже приведены примеры запросов, которые можно использовать для тестирования.

Обратный вызов MediaSession Фраза "Окей, Google"
onPlay()

"Включи".

"Продолжи воспроизведение".

onPlayFromSearch()
onPlayFromUri()
Музыку

"Включи музыку или песни в название приложения". Это пустой запрос.

"Включи (песню | исполнителя | альбом | жанр | плейлист) в (название приложения)".

Радио "Включи (частота или название радиостанции) в (название приложения)".
Audiobook

"Прочитай мою аудиокнигу в название приложения".

"Прочитай (название аудиокниги) в (название приложения)".

Подкасты "Включи (название подкаста) в (название приложения)".
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. Ассистент запустит действие на основе объекта PendingIntent, который вы определили с помощью метода setSessionActivity().

Обязательно задайте MediaSession.Token при инициализации сервиса медиабраузера. Не забудьте настроить поддерживаемые действия воспроизведения, которые будут доступны всегда, в том числе во время инициализации. Ассистент ожидает, что ваше мультимедийное приложение настроит действия воспроизведения до того, как Ассистент отправит первую команду воспроизведения.

Чтобы запустить сервис, Ассистент использует клиентские API медиабраузера. Он выполняет вызовы TransportControls, которые запускают обратные вызовы действия PLAY в сеансе воспроизведения медиаконтента в приложении.

На следующей диаграмме показан порядок вызовов, генерируемых Ассистентом, и соответствующие обратные вызовы сеанса мультимедиа. (Обратные вызовы подготовки отправляются, только если они поддерживаются приложением.) Все вызовы являются асинхронными. Ассистент не ждет ответа от приложения.

Как начать воспроизведение с помощью медиасеанса

Когда пользователь дает голосовую команду на воспроизведение, Ассистент отвечает коротким сообщением. После завершения объявления Ассистент выполняет действие PLAY. Оно не ждет определенного состояния воспроизведения.

Если ваше приложение поддерживает действия ACTION_PREPARE_*, Ассистент вызывает действие PREPARE перед началом объявления.

Подключение к MediaBrowserService

Чтобы запустить приложение, Ассистент должен подключиться к его службе MediaBrowserService и получить токен MediaSession.Token. Запросы на подключение обрабатываются в методе onGetRoot() сервиса. Есть два способа обработки запросов:

  • Принять все запросы на подключение
  • Принимать запросы на подключение только из приложения "Помощник"

Принять все запросы на подключение

Чтобы Ассистент мог отправлять команды в ваш медиасеанс, необходимо вернуть BrowserRoot. Проще всего разрешить всем приложениям MediaBrowser подключаться к вашему сервису MediaBrowserService. Вы должны вернуть ненулевой BrowserRoot. Ниже приведен соответствующий код из Universal Music Player.

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

Примите пакет приложения "Помощник" и подпись

Вы можете явно разрешить Ассистенту подключаться к вашему сервису просмотра медиаконтента, проверив название и подпись пакета. Приложение получит название пакета в методе 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>