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

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

(*) Действия на основе URI Google Assistant работают только для компаний, которые предоставляют URI Google. Дополнительную информацию об описании вашего медиаконтента для Google см. в разделе «Действия с медиа» .

За счет реализации API подготовки можно уменьшить задержку воспроизведения после голосовой команды. Медиа-приложения, которые хотят уменьшить задержку воспроизведения, могут использовать дополнительное время для начала кэширования контента и подготовки воспроизведения мультимедиа.

Парсинг поисковых запросов

Когда пользователь ищет определенный медиа-элемент, например «Включи джаз в [название вашего приложения]» или «Слушай [название песни]» , метод обратного вызова onPrepareFromSearch() или onPlayFromSearch() получает параметр запроса и пакет дополнительных функций. .

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

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

Метод 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>