Создавайте мультимедийные приложения для автомобилей

Android Auto и Android Automotive OS помогут вам донести контент вашего медиаприложения до пользователей в их автомобиле.

Существует два способа создания медиаприложений для автомобилей:

  • В этом руководстве объясняется, как использовать MediaBrowserService и MediaSession для создания приложения, к которому Android Auto и Android Automotive OS смогут подключаться для отображения представлений просмотра и воспроизведения мультимедиа, оптимизированных для использования в автомобиле.

  • Медиаприложения также можно создавать с использованием шаблонов Car App Library для настраиваемого форматирования, возможностей просмотра и расширенных пользовательских действий. Подробнее о реализации см. в статье «Создание шаблонного медиаприложения» . Шаблонные медиаприложения в настоящее время поддерживаются только в Android Auto.

В этом руководстве описаны необходимые компоненты MediaBrowserService и MediaSession , необходимые вашему приложению для работы на Android Auto или Android Automotive OS. После завершения создания базовой инфраструктуры мультимедиа вы можете добавить поддержку Android Auto и Android Automotive OS в своё медиаприложение.

В этом руководстве предполагается, что у вас уже есть медиа-приложение, воспроизводящее аудио на телефоне, и что ваше медиа-приложение соответствует архитектуре медиа-приложений Android.

Прежде чем начать

  1. Ознакомьтесь с документацией по API мультимедиа Android .
  2. Ознакомьтесь с разделом Создание медиа-приложений для получения рекомендаций по дизайну.
  3. Ознакомьтесь с основными терминами и понятиями, перечисленными в этом разделе.

Ключевые термины и понятия

Служба медиабраузера
Служба Android, реализуемая вашим медиаприложением, соответствующая API MediaBrowserServiceCompat . Ваше приложение использует эту службу для отображения своего контента.
Медиабраузер
API, используемый медиаприложениями для обнаружения сервисов медиабраузера и отображения их контента. Android Auto и Android Automotive OS используют медиабраузер для поиска сервиса медиабраузера вашего приложения.
Медиа-элемент

Медиабраузер организует свой контент в виде дерева объектов MediaItem . Медиаэлемент может иметь один или оба следующих флага:

  • FLAG_PLAYABLE : указывает, что элемент является листом в дереве контента. Элемент представляет собой отдельный звуковой поток, например песню в альбоме, главу в аудиокниге или выпуск подкаста.
  • FLAG_BROWSABLE : указывает, что элемент является узлом в дереве контента и имеет дочерние элементы. Например, элемент представляет альбом, а его дочерние элементы — песни в альбоме.

Медиафайл, который можно просматривать и воспроизводить, действует как плейлист. Вы можете выбрать сам элемент для воспроизведения всех его дочерних элементов или просмотреть его дочерние элементы.

Оптимизировано для транспортного средства

Activity для приложения Android Automotive OS, соответствующее принципам дизайна Android Automotive OS . Интерфейс этих Activity не разрабатывается Android Automotive OS, поэтому вам необходимо убедиться, что ваше приложение соответствует принципам дизайна. Как правило, это включает в себя более крупные области нажатия и размеры шрифтов, поддержку дневного и ночного режимов, а также более высокую контрастность.

Пользовательские интерфейсы, оптимизированные для транспортных средств, могут отображаться только при отсутствии ограничений пользовательского опыта (CUXR), поскольку такие интерфейсы могут требовать от пользователя повышенного внимания или взаимодействия. CUXR не действуют, когда автомобиль остановлен или припаркован, но всегда действуют, когда он движется.

Вам не нужно разрабатывать действия для Android Auto, поскольку Android Auto рисует собственный оптимизированный для автомобиля интерфейс, используя информацию из службы вашего медиабраузера.

Настройте файлы манифеста вашего приложения

Прежде чем создать службу медиабраузера, вам необходимо настроить файлы манифеста вашего приложения.

Объявите свой сервис медиабраузера

Android Auto и Android Automotive OS подключаются к вашему приложению через службу медиабраузера для просмотра медиафайлов. Объявите службу медиабраузера в манифесте, чтобы Android Auto и Android Automotive OS могли обнаружить её и подключиться к вашему приложению.

Следующий фрагмент кода показывает, как объявить службу медиабраузера в манифесте. Включите этот код в файл манифеста для модуля Android Automotive OS и в файл манифеста для вашего приложения для телефона.

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

Укажите значки приложений

Вам необходимо указать значки приложений, которые Android Auto и Android Automotive OS смогут использовать для представления вашего приложения в системном интерфейсе. Требуются два типа значков:

  • Значок запуска
  • Значок атрибуции

Значок запуска

Значок панели запуска представляет ваше приложение в системном интерфейсе, например, на панели запуска и в области значков. Вы можете указать, что хотите использовать значок из вашего мобильного приложения для представления автомобильного медиа-приложения, с помощью следующего объявления в манифесте:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

Чтобы использовать значок, отличный от значка вашего мобильного приложения, задайте свойство android:icon в элементе <service> службы вашего медиабраузера в манифесте:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

Значок атрибуции

Рисунок 1. Значок атрибуции на карточке носителя.

Значок атрибуции используется там, где медиаконтент имеет приоритет, например, на карточках с медиафайлами. Рассмотрите возможность повторного использования маленького значка, используемого для уведомлений. Этот значок должен быть монохромным. Вы можете указать значок, который будет представлять ваше приложение, с помощью следующего объявления манифеста:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

Создайте свой медиабраузер

Вы создаёте службу медиабраузера, расширяя класс MediaBrowserServiceCompat . И Android Auto, и Android Automotive OS смогут использовать вашу службу для следующих целей:

  • Просмотрите иерархию контента вашего приложения, чтобы предоставить пользователю меню.
  • Получите токен для объекта MediaSessionCompat вашего приложения для управления воспроизведением звука.

Вы также можете использовать свой медиабраузер, чтобы предоставить другим клиентам доступ к медиаконтенту из вашего приложения. Этими медиаклиентами могут быть другие приложения на телефоне пользователя или другие удалённые клиенты.

Рабочий процесс службы медиабраузера

В этом разделе описывается, как Android Automotive OS и Android Auto взаимодействуют с вашим медиабраузером во время типичного рабочего процесса пользователя.

  1. Пользователь запускает ваше приложение на Android Automotive OS или Android Auto.
  2. Android Automotive OS или Android Auto обращаются к службе медиабраузера вашего приложения с помощью метода onCreate() . В вашей реализации метода onCreate() необходимо создать и зарегистрировать объект MediaSessionCompat и его объект обратного вызова.
  3. Android Automotive OS или Android Auto вызывает метод onGetRoot() вашего сервиса, чтобы получить корневой медиа-элемент в иерархии контента. Корневой медиа-элемент не отображается; вместо этого он используется для получения дополнительного контента из вашего приложения.
  4. Android Automotive OS или Android Auto вызывают метод onLoadChildren() вашей службы для получения дочерних элементов корневого медиа-элемента. Android Automotive OS и Android Auto отображают эти медиа-элементы как верхний уровень элементов контента. Подробнее о том, что ожидает система на этом уровне, см. в разделе «Структура корневого меню» на этой странице.
  5. Если пользователь выбирает просматриваемый элемент мультимедиа, метод onLoadChildren() вашей службы снова вызывается для извлечения дочерних элементов выбранного элемента меню.
  6. Если пользователь выбирает воспроизводимый элемент мультимедиа, Android Automotive OS или Android Auto вызывают соответствующий метод обратного вызова сеанса мультимедиа для выполнения этого действия.
  7. Если ваше приложение поддерживает эту функцию, пользователь также может осуществлять поиск по вашему контенту. В этом случае Android Automotive OS или Android Auto вызывают метод onSearch() вашего сервиса.

Создайте иерархию своего контента

Android Auto и Android Automotive OS обращаются к службе медиабраузера вашего приложения, чтобы узнать, какой контент доступен. Для этого необходимо реализовать два метода в службе медиабраузера: onGetRoot() и onLoadChildren()

Реализовать onGetRoot

Метод onGetRoot() вашего сервиса возвращает информацию о корневом узле иерархии вашего контента. Android Auto и Android Automotive OS используют этот корневой узел для запроса остального контента с помощью метода onLoadChildren() .

Следующий фрагмент кода показывает простую реализацию метода onGetRoot() :

Котлин

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? =
    // Verify that the specified package is allowed to access your
    // content. You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        null
    } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)

Ява

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // Verify that the specified package is allowed to access your
    // content. You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        return null;
    }

    return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

Более подробный пример этого метода см. в методе onGetRoot() в примере приложения Universal Android Music Player на GitHub.

Добавить проверку пакета для onGetRoot()

При вызове метода onGetRoot() вашего сервиса вызывающий пакет передаёт идентификационную информацию вашему сервису. Ваш сервис может использовать эту информацию, чтобы решить, может ли пакет получить доступ к вашему контенту. Например, вы можете ограничить доступ к контенту своего приложения списком одобренных пакетов, сравнив clientPackageName с вашим разрешённым списком и проверив сертификат, использованный для подписи APK-файла пакета. Если пакет не удаётся проверить, верните null , чтобы запретить доступ к контенту.

Чтобы предоставить системным приложениям, таким как Android Auto и Android Automotive OS, доступ к вашему контенту, ваш сервис должен всегда возвращать ненулевой BrowserRoot при вызове этими системными приложениями метода onGetRoot() . Сигнатура системного приложения Android Automotive OS может различаться в зависимости от марки и модели автомобиля, поэтому для надёжной поддержки Android Automotive OS необходимо разрешить подключения для всех системных приложений.

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

fun isKnownCaller(
    callingPackage: String,
    callingUid: Int
): Boolean {
    ...
    val isCallerKnown = when {
       // If the system is making the call, allow it.
       callingUid == Process.SYSTEM_UID -> true
       // If the app was signed by the same certificate as the platform
       // itself, also allow it.
       callerSignature == platformSignature -> true
       // ... more cases
    }
    return isCallerKnown
}

Этот фрагмент кода — фрагмент класса PackageValidator из примера приложения Universal Android Music Player на GitHub. Более подробный пример реализации валидации пакетов для метода onGetRoot() вашего сервиса можно найти в этом классе.

Помимо разрешения системных приложений, необходимо разрешить Google Ассистенту подключаться к MediaBrowserService . Обратите внимание, что у Google Ассистента есть отдельные названия пакетов для телефона, включая Android Auto, и для Android Automotive OS.

Реализовать onLoadChildren()

Получив объект корневого узла, Android Auto и Android Automotive OS создают меню верхнего уровня, вызывая onLoadChildren() для объекта корневого узла, чтобы получить доступ к его дочерним элементам. Клиентские приложения создают подменю, вызывая этот же метод с использованием объектов дочерних элементов.

Каждый узел в иерархии контента представлен объектом MediaBrowserCompat.MediaItem . Каждый из этих медиаэлементов идентифицируется уникальной строкой идентификатора. Клиентские приложения обрабатывают эти строки идентификатора как непрозрачные токены. Когда клиентское приложение хочет перейти к подменю или воспроизвести медиаэлемент, оно передаёт токен. Ваше приложение отвечает за связывание токена с соответствующим медиаэлементом.

В следующем фрагменте кода показана простая реализация метода onLoadChildren() :

Котлин

override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaBrowserCompat.MediaItem>>
) {
    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems)
}

Ява

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaBrowserCompat.MediaItem>> result) {

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems);
}

Полный пример этого метода см. в методе onLoadChildren() в примере приложения Universal Android Music Player на GitHub.

Структура корневого меню

Рисунок 2. Корневой контент отображается в виде навигационных вкладок.

В Android Auto и Android Automotive OS существуют определённые ограничения на структуру корневого меню. Они передаются службе MediaBrowserService через корневые подсказки, которые можно прочитать через аргумент Bundle , передаваемый в onGetRoot() . Следование этим подсказкам позволяет системе оптимально отображать содержимое корневого меню в виде навигационных вкладок. Несоблюдение этих подсказок может привести к потере части содержимого корневого меню или сделать его менее заметным для системы. Передаются две подсказки:

Используйте следующий код для чтения соответствующих корневых подсказок:

Котлин

import androidx.media.utils.MediaConstants

// Later, in your MediaBrowserServiceCompat.
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

  val maximumRootChildLimit = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
      /* defaultValue= */ 4)
  val supportedRootChildFlags = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
      /* defaultValue= */ MediaItem.FLAG_BROWSABLE)

  // Rest of method...
}

Ява

import androidx.media.utils.MediaConstants;

// Later, in your MediaBrowserServiceCompat.
@Override
public BrowserRoot onGetRoot(
    String clientPackageName, int clientUid, Bundle rootHints) {

    int maximumRootChildLimit = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
        /* defaultValue= */ 4);
    int supportedRootChildFlags = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
        /* defaultValue= */ MediaItem.FLAG_BROWSABLE);

    // Rest of method...
}

Вы можете разветвлять логику структуры иерархии контента на основе значений этих подсказок, особенно если ваша иерархия различается в разных интеграциях MediaBrowser за пределами Android Auto и Android Automotive OS. Например, если вы обычно отображаете корневой воспроизводимый элемент, вам может потребоваться вложить его в корневой просматриваемый элемент из-за значения подсказки «Поддерживаемые флаги».

Помимо корневых ссылок, есть еще несколько дополнительных рекомендаций, которые помогут обеспечить оптимальное отображение вкладок:

  • Предоставьте монохромные, предпочтительно белые, значки для каждого элемента вкладки.
  • Используйте короткие, но содержательные подписи для каждого элемента вкладки. Короткие подписи снижают вероятность обрезания строк.

Демонстрация медиа-произведений искусства

Обложки для медиа-элементов должны передаваться как локальный URI с помощью метода ContentResolver.SCHEME_CONTENT или ContentResolver.SCHEME_ANDROID_RESOURCE . Этот локальный URI должен быть преобразован в растровое или векторное изображение, которое можно отрисовать в ресурсах приложения. Для объектов MediaDescriptionCompat , представляющих элементы в иерархии контента, передайте URI через setIconUri() . Для объектов MediaMetadataCompat , представляющих текущий воспроизводимый элемент, передайте URI через putString() , используя любой из следующих ключей:

Ниже описано, как загрузить изображение с веб-URI и предоставить его через локальный URI. Более полный пример см. в реализации метода openFile() и окружающих его методов в примере приложения Universal Android Music Player.

  1. Создайте URI content:// соответствующий веб-URI. Служба медиабраузера и медиасеанс передают этот URI контента в Android Auto и Android Automotive OS.

    Котлин

    fun Uri.asAlbumArtContentURI(): Uri {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(this.getPath()) // Make sure you trust the URI
        .build()
    }

    Ява

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. В вашей реализации ContentProvider.openFile() проверьте, существует ли файл для соответствующего URI. Если нет, загрузите и кэшируйте файл изображения. В следующем фрагменте кода используется Glide .

    Котлин

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }

    Ява

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }

Более подробную информацию о поставщиках контента см. в разделе Создание поставщика контента .

Применить стили содержимого

После создания иерархии контента с использованием просматриваемых или воспроизводимых элементов вы можете применить стили контента, которые определяют, как эти элементы будут отображаться в автомобиле.

Вы можете использовать следующие стили контента:

Список элементов

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

Элементы сетки

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

Установить стили содержимого по умолчанию

Вы можете задать глобальные значения по умолчанию для отображения медиа-элементов, включив определённые константы в пакет дополнительных параметров BrowserRoot метода onGetRoot() вашего сервиса. Android Auto и Android Automotive OS считывают этот пакет и ищут эти константы, чтобы определить подходящий стиль.

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

Ключи могут соответствовать следующим целочисленным константам, влияющим на представление этих элементов:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM : соответствующие элементы представлены в виде элементов списка.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM : соответствующие элементы представлены в виде элементов сетки.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM : соответствующие элементы представлены как элементы списка «категории». Они аналогичны обычным элементам списка, за исключением того, что вокруг значков элементов применяются поля, поскольку они лучше выглядят, когда они маленькие. Значки должны быть векторными и иметь возможность тонирования. Эта подсказка должна предоставляться только для элементов, доступных для просмотра.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM : соответствующие элементы представлены как элементы сетки «категории». Они аналогичны обычным элементам сетки, за исключением того, что вокруг значков элементов применяются поля, поскольку они лучше выглядят, когда они маленькие. Значки должны быть векторными, с возможностью тонирования. Эта подсказка должна предоставляться только для элементов, доступных для просмотра.

В следующем фрагменте кода показано, как задать стиль содержимого по умолчанию для просматриваемых элементов в виде сеток, а для воспроизводимых элементов — в виде списков:

Котлин

import androidx.media.utils.MediaConstants

@Nullable
override fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    return BrowserRoot(ROOT_ID, extras)
}

Ява

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    return new BrowserRoot(ROOT_ID, extras);
}

Установить стили содержимого для каждого элемента

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

Чтобы переопределить значение по умолчанию для дочерних элементов просматриваемого медиа-элемента, создайте пакет дополнительных элементов в MediaDescription медиа-элемента и добавьте те же упомянутые ранее подсказки. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE применяется к воспроизводимым дочерним элементам этого элемента, тогда как DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE применяется к просматриваемым дочерним элементам этого элемента.

Чтобы переопределить значение по умолчанию для конкретного медиа-элемента, а не для его дочерних элементов, создайте пакет дополнительных материалов в MediaDescription медиа-элемента и добавьте подсказку с ключом DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM . Используйте те же значения, что и ранее, для указания представления этого элемента.

В следующем фрагменте кода показано, как создать просматриваемый MediaItem , который переопределяет стиль содержимого по умолчанию как для себя, так и для своих дочерних элементов. Он оформляет себя как элемент списка категорий, свои просматриваемые дочерние элементы — как элементы списка, а свои воспроизводимые дочерние элементы — как элементы сетки.

Котлин

import androidx.media.utils.MediaConstants

private fun createBrowsableMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
}

Ява

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createBrowsableMediaItem(
    String mediaId,
    String folderName,
    Uri iconUri) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId(mediaId);
    mediaDescriptionBuilder.setTitle(folderName);
    mediaDescriptionBuilder.setIconUri(iconUri);
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    mediaDescriptionBuilder.setExtras(extras);
    return new MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}

Группируйте элементы, используя подсказки по заголовкам

Для группировки связанных медиаэлементов используется подсказка для каждого элемента. Для каждого медиаэлемента в группе необходимо объявить пакет дополнительных материалов в MediaDescription , который включает сопоставление с ключом DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE и идентичным строковым значением. Локализуйте эту строку, которая будет использоваться в качестве заголовка группы.

В следующем фрагменте кода показано, как создать MediaItem с заголовком подгруппы "Songs" :

Котлин

import androidx.media.utils.MediaConstants

private fun createMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putString(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
        "Songs")
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
}

Ява

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

Ваше приложение должно передавать все медиа-элементы, которые вы хотите сгруппировать в виде непрерывного блока. Например, предположим, что вы хотите отобразить две группы медиа-элементов: «Песни» и «Альбомы» — в указанном порядке, и ваше приложение передаёт пять медиа-элементов в следующем порядке:

  1. Медиа-элемент A с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Медиа-элемент B с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  3. Медиа-элемент C с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Медиа-элемент D с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  5. Медиа-элемент E с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Поскольку медиафайлы для групп «Песни» и «Альбомы» не хранятся вместе в смежных блоках, Android Auto и Android Automotive OS интерпретируют это как следующие четыре группы:

  • Группа 1 под названием «Песни», содержащая медиафайл A
  • Группа 2 под названием «Альбомы», содержащая медиафайл B
  • Группа 3 под названием «Песни», содержащая медиафайлы C и D
  • Группа 4 под названием «Альбомы», содержащая медиафайл E

Чтобы отобразить эти элементы в двух группах, ваше приложение должно передавать элементы мультимедиа в следующем порядке:

  1. Медиа-элемент A с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Медиа-элемент C с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  3. Медиа-элемент D с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Медиа-элемент B с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  5. Медиа-элемент E с extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Отображать дополнительные индикаторы метаданных

Вы можете включить дополнительные индикаторы метаданных, чтобы предоставить наглядную информацию о контенте в дереве браузера медиафайлов и во время воспроизведения. В дереве браузера Android Auto и Android Automotive OS считывают дополнительные данные, связанные с элементом, и ищут определённые константы, чтобы определить, какие индикаторы отображать. Во время воспроизведения медиафайлов Android Auto и Android Automotive OS считывают метаданные сеанса медиафайлов и ищут определённые константы, чтобы определить, какие индикаторы отображать.

Рисунок 3. Вид воспроизведения с метаданными, идентифицирующими песню и исполнителя, а также значком, указывающим на наличие ненормативной лексики.

Рисунок 4. Вид просмотра с точкой для невоспроизведенного контента на первом элементе и полосой прогресса для частично воспроизведенного контента на втором элементе.

Следующие константы можно использовать как в дополнительных описаниях MediaItem , так и в дополнительных материалах MediaMetadata :

  • EXTRA_DOWNLOAD_STATUS : указывает статус загрузки элемента. Используйте эту константу в качестве ключа; возможны следующие длинные константы:
  • METADATA_KEY_IS_EXPLICIT : указывает, содержит ли элемент явное содержимое. Чтобы указать, что элемент является явным, используйте эту константу в качестве ключа и длинное значение METADATA_VALUE_ATTRIBUTE_PRESENT в качестве значения.

Следующие константы можно использовать только в дополнительных описаниях MediaItem :

Чтобы отобразить индикаторы, которые появляются, когда пользователь просматривает дерево обзора медиафайлов, создайте пакет дополнительных данных, включающий одну или несколько из этих констант, и передайте этот пакет методу MediaDescription.Builder.setExtras() .

В следующем фрагменте кода показано, как отображать индикаторы для явного медиа-элемента, который завершен на 70%:

Котлин

import androidx.media.utils.MediaConstants

val extras = Bundle()
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED)
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

Ява

import androidx.media.utils.MediaConstants;

Bundle extras = new Bundle();
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build();
return new MediaBrowserCompat.MediaItem(description, /* flags */);

Чтобы отобразить индикаторы для воспроизводимого в данный момент медиафайла, можно объявить Long значения для METADATA_KEY_IS_EXPLICIT или EXTRA_DOWNLOAD_STATUS в MediaMetadataCompat вашего mediaSession . Индикаторы DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS или DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE не отображаются в окне воспроизведения.

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

Котлин

import androidx.media.utils.MediaConstants

mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build())

Ява

import androidx.media.utils.MediaConstants;

mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build());

Отображение пользовательских индикаторов метаданных

Чтобы заменить значки по умолчанию, обозначающие «загруженные элементы» и «контент для взрослых», и отображать до четырёх пользовательских значков, например, аудиоформатов, используйте параметр KEY_TINTABLE_INDICATOR_ICON_URI_LIST в дополнительных параметрах описания MediaItem . Если один из значков представляет аудиоформат и вы хотите, чтобы он отображался для текущего воспроизводимого медиафайла, необходимо также задать дополнительные параметры аудиоформата .

В следующем фрагменте кода показано, как добавить данные в дополнительные данные MediaDescriptionCompat .

Котлин

fun addIndicatorIcons(extras: Bundle) {
    val myPackageName = TODO // Set to application's package name like "com.android.car.media.testmediaapp"
    val uriPrefix = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + myPackageName + "/drawable/"
    val iconsUris = java.util.ArrayList(2)
    iconsUris.add((uriPrefix + "hda_logo_small").toUri())
    iconsUris.add((uriPrefix + "ic_explicit").toUri())
    extras.putParcelableArrayList(MetadataExtras.KEY_TINTABLE_INDICATOR_ICON_URI_LIST, iconsUris)
}

val bob = MediaDescriptionCompat.Builder()
val extras = Bundle()
addIndicatorIcons(extras)
bob.setExtras(extras)

Ява

static void addIndicatorIcons(Bundle extras) {
    String myPackageName = TODO; // Set to application's package name like "com.android.car.media.testmediaapp"
    String uriPrefix = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + myPackageName + "/drawable/";
    ArrayList iconsUris = new ArrayList<>(2);
    iconsUris.add(Uri.parse(uriPrefix + "ic_explicit"));
    iconsUris.add(Uri.parse(uriPrefix + "hda_logo_small"));
    extras.putParcelableArrayList(MetadataExtras.KEY_TINTABLE_INDICATOR_ICON_URI_LIST, iconsUris);
}
MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
Bundle extras = new Bundle();
addIndicatorIcons(extras);
bob.setExtras(extras);

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

Как упоминалось ранее, можно использовать дополнительный параметр DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE для отображения индикатора прогресса для частично воспроизведенного контента в режиме просмотра. Однако, если пользователь продолжает воспроизводить частично воспроизведенный контент в Android Auto или Android Automotive OS, этот индикатор со временем становится неточным.

Чтобы в Android Auto и Android Automotive OS индикатор прогресса обновлялся, можно предоставить дополнительную информацию в MediaMetadataCompat и PlaybackStateCompat , чтобы связать текущий контент с медиафайлами в представлении просмотра. Для автоматического обновления индикатора прогресса медиафайла должны быть выполнены следующие требования:

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

Котлин

import androidx.media.utils.MediaConstants

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
val mediaItemExtras = Bundle()
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build())

val playbackStateExtras = Bundle()
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id")
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build())

Ява

import androidx.media.utils.MediaConstants;

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
Bundle mediaItemExtras = new Bundle();
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build();
return MediaBrowserCompat.MediaItem(description, /* flags */);

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build());

Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id");
mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build());

Рисунок 5. Вид воспроизведения с опцией «Результаты поиска» для просмотра элементов мультимедиа, связанных с голосовым поиском пользователя.

Ваше приложение может предоставлять контекстные результаты поиска, которые отображаются пользователям при выполнении поискового запроса. Android Auto и Android Automotive OS показывают эти результаты через интерфейсы поисковых запросов или через возможности, основанные на запросах, сделанных ранее в ходе сеанса. Подробнее см. в разделе «Поддержка голосовых команд» данного руководства.

Чтобы отобразить просматриваемые результаты поиска, включите константный ключ BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED в пакет extras метода onGetRoot() вашей службы, сопоставив его с логическим значением true .

В следующем фрагменте кода показано, как включить поддержку в методе onGetRoot() :

Котлин

import androidx.media.utils.MediaConstants

@Nullable
fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
    return BrowserRoot(ROOT_ID, extras)
}

Ява

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true);
    return new BrowserRoot(ROOT_ID, extras);
}

Чтобы начать предоставлять результаты поиска, переопределите метод onSearch() в службе вашего медиабраузера. Android Auto и Android Automotive OS перенаправляют поисковые запросы пользователя в этот метод каждый раз, когда пользователь вызывает интерфейс поискового запроса или функцию «Результаты поиска».

Вы можете упорядочить результаты поиска из метода onSearch() вашего сервиса, используя заголовки, чтобы сделать их более удобными для просмотра. Например, если ваше приложение воспроизводит музыку, вы можете упорядочить результаты поиска по альбомам, исполнителям и песням.

Следующий фрагмент кода показывает простую реализацию метода onSearch() :

Котлин

fun onSearch(query: String, extras: Bundle) {
  // Detach from results to unblock the caller (if a search is expensive).
  result.detach()
  object:AsyncTask() {
    internal var searchResponse:ArrayList
    internal var succeeded = false
    protected fun doInBackground(vararg params:Void):Void {
      searchResponse = ArrayList()
      if (doSearch(query, extras, searchResponse))
      {
        succeeded = true
      }
      return null
    }
    protected fun onPostExecute(param:Void) {
      if (succeeded)
      {
        // Sending an empty List informs the caller that there were no results.
        result.sendResult(searchResponse)
      }
      else
      {
        // This invokes onError() on the search callback.
        result.sendResult(null)
      }
      return null
    }
  }.execute()
}
// Populates resultsToFill with search results. Returns true on success or false on error.
private fun doSearch(
    query: String,
    extras: Bundle,
    resultsToFill: ArrayList
): Boolean {
  // Implement this method.
}

Ява

@Override
public void onSearch(final String query, final Bundle extras,
                        Result<List<MediaItem>> result) {

  // Detach from results to unblock the caller (if a search is expensive).
  result.detach();

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback.
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error. */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method.
}

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

Одно пользовательское действие просмотра.

Рисунок 6. Отдельное пользовательское действие просмотра

Пользовательские действия в браузере позволяют добавлять пользовательские значки и метки к объектам MediaItem вашего приложения в автомобильном медиа-приложении и обрабатывать взаимодействие пользователя с этими действиями. Это позволяет расширить функциональность медиа-приложения различными способами, например, добавляя действия «Загрузить», «Добавить в очередь», «Воспроизвести радио», «Добавить в избранное» или «Удалить».

Пользовательское меню действий при просмотре.

Рисунок 7. Переполнение пользовательского действия просмотра

Если количество пользовательских действий превышает допустимое производителем оборудования, пользователю будет показано дополнительное меню.

Как они работают?

Каждое пользовательское действие просмотра определяется следующим образом:

  • Идентификатор действия (уникальный строковый идентификатор)
  • Метка действия (текст, отображаемый пользователю)
  • URI значка действия (векторный объект, который можно тонировать)

Вы определяете список пользовательских действий просмотра глобально как часть BrowseRoot . Затем вы можете прикрепить подмножество этих действий к отдельному MediaItem.

Когда пользователь взаимодействует с пользовательским действием «Обзор», ваше приложение получает обратный вызов в onCustomAction() . Затем вы можете обработать действие и при необходимости обновить список действий для MediaItem . Это полезно для действий с отслеживанием состояния, таких как «Добавить в избранное» и «Загрузить». Для действий, не требующих обновления, например, «Воспроизвести радио», обновлять список действий не нужно.

Пользовательские действия просмотра в корневом узле просмотра.

Рисунок 8. Панель инструментов «Настраиваемое действие просмотра»

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

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

Вот шаги по добавлению пользовательских действий просмотра в ваш проект:

  1. Переопределите два метода в вашей реализации MediaBrowserServiceCompat :
  2. Анализ ограничений действий во время выполнения:
    • В onGetRoot() получите максимальное количество действий, разрешенных для каждого MediaItem , используя ключевой BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT в Bundle rootHints . Предел 0 указывает, что функция не поддерживается системой.
  3. Создайте глобальный список индивидуальных просмотра действий:
    • Для каждого действия создайте объект Bundle со следующими клавишами: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID : идентификатор действия * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL : метка действия * extras_key_custom_browser_action_icon_uri: con uri * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI : con uri Bundle extras_key_custom_browser_icon_icon_uri: econ uri * extras_key_custom_action_icon_uri: econ uri * extras_key_castom
  4. Добавьте глобальный список в свой BrowseRoot :
  5. Добавьте действия в ваши объекты MediaItem :
    • Вы можете добавить действия в отдельные объекты MediaItem , включив список идентификаторов действия в дополнениях MediaDescriptionCompat с использованием ключа DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST . Этот список должен быть подмножеством глобального списка действий, которые вы определили в BrowseRoot .
  6. Обрабатывать действия и вернуть прогресс или результаты:
    • В onCustomAction обработайте действие на основе идентификатора действия и любых других данных, которые вам нужны. Вы можете получить идентификатор MediaItem , который запустил действие из дополнений, используя ключ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID . Полем
    • Вы можете обновить список действий для MediaItem , включив ключ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM в пакет прогресса или результатов.

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

Переопределить BrowserServiceCompat

Вам необходимо переопределить следующие методы в MediaBrowserServiceCompat .

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Расположение действий ограничение

Вам следует проверить, сколько поддерживается пользовательским просмотром действий.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Создайте индивидуальный просмотр

Каждое действие должно быть упаковано в отдельный Bundle .

  • Идентификатор действия
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • Метка действия
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • Значок действия URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Добавьте пользовательский просмотр действий в Parceable ArrayList

Добавьте все пользовательские объекты Browse Action Bundle в ArrayList .

private ArrayList<Bundle> createCustomActionsList(
                                        CustomBrowseAction browseActions) {
    ArrayList<Bundle> browseActionsBundle = new ArrayList<>();
    for (CustomBrowseAction browseAction : browseActions) {
        Bundle action = new Bundle();
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                browseAction.mId);
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                getString(browseAction.mLabelResId));
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                browseAction.mIcon);
        browseActionsBundle.add(action);
    }
    return browseActionsBundle;
}

Добавить собственный список действий просмотра в корень просмотра

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    Bundle browserRootExtras = new Bundle();
    browserRootExtras.putParcelableArrayList(
            BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
            createCustomActionsList()));
    mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
    return mRoot;
}

Добавить действия в MediaItem

MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                String description, Uri iconUri, Uri mediaUri,
                ArrayList<String> browseActionIds) {

    MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
    bob.setMediaId(id);
    bob.setTitle(title);
    bob.setSubtitle(subtitle);
    bob.setDescription(description);
    bob.setIconUri(iconUri);
    bob.setMediaUri(mediaUri);

    Bundle extras = new Bundle();
    extras.putStringArrayList(
          DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
          browseActionIds);

    bob.setExtras(extras);
    return bob.build();
}
MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

Построить результат onCustomAction

  • Parse MediaId из Bundle extras :
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
  • Для асинхронных результатов отсоедините результат. result.detach()
  • Построить пакет результатов
    • Сообщение пользователю
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Обновите элемент (используйте для обновления действий в элементе)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Открытый вид воспроизведения
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Обновление узел
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • Если ошибка, вызовите result.sendError(resultBundle).
  • Если обновление прогресса, вызовите result.sendProgressUpdate(resultBundle) .
  • Завершить, позвонив result.sendResult(resultBundle) .

Обновить состояние действия

Используя метод result.sendProgressUpdate(resultBundle) с помощью ключа EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM , вы можете обновить MediaItem , чтобы отразить новое состояние действия. Это позволяет вам предоставлять обратную связь с пользователем в реальном времени о прогрессе и результате их действия.

Пример: скачать действие

Вот пример того, как вы можете использовать эту функцию для реализации действия загрузки с тремя состояниями:

  1. Скачать: это начальное состояние действия. Когда пользователь выбирает это действие, вы можете поменять его с «загрузкой» и вызовы sendProgressUpdate для обновления пользовательского интерфейса.
  2. Загрузка: это состояние указывает, что загрузка находится в процессе. Вы можете использовать это состояние, чтобы показать панель прогресса или другой индикатор для пользователя.
  3. Загружено: это состояние указывает, что загрузка завершена. При завершении загрузки вы можете обмениваться «загрузкой» с «загруженным» и позвонить sendResult с помощью клавиши EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM чтобы указать, что элемент должен быть обновлен. Кроме того, вы можете использовать клавишу EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE чтобы отобразить сообщение успеха пользователю.

Этот подход позволяет предоставить пользователю четкую обратную связь о процессе загрузки и его текущего состояния. Вы можете добавить еще больше деталей со значками, чтобы показать 25%, 50%, 75%состояния загрузки.

Пример: любимое действие

Другой пример - любимое действие с двумя состояниями:

  1. Любимое: это действие отображается для элементов, которые не находятся в списке фаворитов пользователя. Когда пользователь выбирает это действие, вы можете поменять его с «любимым» и позвонить sendResult с помощью клавиши EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM для обновления пользовательского интерфейса.
  2. Благодаря: это действие отображается для элементов, которые находятся в списке фаворитов пользователя. Когда пользователь выбирает это действие, вы можете поменять его с «любимым» и позвонить sendResult с помощью клавиши EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM для обновления пользовательского интерфейса.

Этот подход обеспечивает четкий и последовательный способ для пользователей управлять своими любимыми предметами.

Эти примеры демонстрируют гибкость индивидуальных просмотра действий и то, как вы можете использовать их для реализации различных функций с обратной связью в реальном времени для улучшенного пользовательского опыта в приложении Car's Media.

Для полного примера реализации этой функции вы можете обратиться к проекту TestMediaApp .

Включить управление воспроизведением

Android Auto и Android Automotive OS Отправляйте команды управления воспроизведением через MediaSessionCompat вашей службы. Вы должны зарегистрировать сеанс и реализовать соответствующие методы обратного вызова.

Зарегистрировать сеанс медиа

В методе вашей сервиса Media Browser Service onCreate() создайте MediaSessionCompat , затем зарегистрируйте сеанс СМИ, вызовите setSessionToken() .

Следующий фрагмент кода показывает, как создать и зарегистрировать сеанс медиа:

Котлин

override fun onCreate() {
    super.onCreate()
    ...
    // Start a new MediaSession.
    val session = MediaSessionCompat(this, "session tag").apply {
        // Set a callback object that implements MediaSession.Callback
        // to handle play control requests.
        setCallback(MyMediaSessionCallback())
    }
    sessionToken = session.sessionToken
    ...
}

Ява

public void onCreate() {
    super.onCreate();
    ...
    // Start a new MediaSession.
    MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
    setSessionToken(session.getSessionToken());

    // Set a callback object that implements MediaSession.Callback
    // to handle play control requests.
    session.setCallback(new MyMediaSessionCallback());
    ...
}

Когда вы создаете объект сеанса медиа, вы устанавливаете объект обратного вызова, который используется для обработки запросов управления воспроизведением. Вы создаете этот объект обратного вызова, предоставляя внедрение класса MediaSessionCompat.Callback для вашего приложения. В следующем разделе обсуждается, как реализовать этот объект.

Реализовать команды PLAY

Когда пользователь запрашивает воспроизведение для предмета мультимедиа в вашем приложении, Android Automotive OS и Android Auto используют класс MediaSessionCompat.Callback из объекта вашего приложения в MediaSessionCompat , который они получили из службы Media Browser вашего приложения. Когда пользователь хочет контролировать воспроизведение контента, например, приостановить воспроизведение или пропуск на следующий трек, Android Auto и Android Automotive OS вызывает один из методов Callback объекта.

Чтобы справиться с воспроизведением контента, ваше приложение должно расширить Abstract MediaSessionCompat.Callback Class и реализовать методы, которые поддерживает ваше приложение.

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

onPrepare()
Призван, когда источник медиа изменяется. Android Automotive OS также вызывает этот метод сразу после загрузки. Ваше медиа -приложение должно реализовать этот метод.
onPlay()
Вызывается, если пользователь выбирает воспроизведение без выбора конкретного элемента. Ваше приложение должно воспроизводить свой контент по умолчанию или, если воспроизведение было приостановлено с помощью onPause() , ваше приложение возобновляет воспроизведение.

ПРИМЕЧАНИЕ. Ваше приложение не должно автоматически начинать воспроизведение музыки, когда Android Automotive OS или Android Auto подключаются к службу вашего браузера. Для получения дополнительной информации см. Раздел о установке начального состояния воспроизведения .

onPlayFromMediaId()
Вызывается, когда пользователь решает воспроизводить конкретный элемент. Метод проходит идентификатор , который ваш сервис браузера вашего браузера, назначенный элементу мультимедиа, в вашей иерархии контента.
onPlayFromSearch()
Вызывается, когда пользователь решает играть из поискового запроса. Приложение должно сделать соответствующий выбор на основе строки поиска, которая была передана.
onPause()
Вызывается, когда пользователь выбирает воспроизведение.
onSkipToNext()
Вызывается, когда пользователь решит пропустить следующий элемент.
onSkipToPrevious()
Вызывается, когда пользователь решает пропустить предыдущий элемент.
onStop()
Вызывается, когда пользователь решит остановить воспроизведение.

Переопределите эти методы в вашем приложении, чтобы обеспечить любую желаемую функциональность. Вам не нужно реализовать метод, если его функциональность не поддерживается вашим приложением. Например, если ваше приложение играет в прямом эфире, такой как спортивная трансляция, вам не нужно реализовать метод onSkipToNext() . Вместо этого вы можете использовать реализацию по умолчанию onSkipToNext() .

Ваше приложение не нуждается в какой -либо специальной логике, чтобы воспроизводить контент через динамики автомобиля. Когда ваше приложение получает запрос на воспроизведение контента, оно может воспроизводить звук так же, как он воспроизводит контент через телефонные динамики или наушники пользователя. Android Auto и Android Automotive OS автоматически отправляет аудиоконтент в систему автомобиля, чтобы воспроизводить динамики автомобиля.

Для получения дополнительной информации о воспроизведении звукового контента см. Обзор MediaPlayer , обзор Audio приложения и обзор Exoplayer.

Установить стандартные действия воспроизведения

Android Auto и Android Automotive OS -дисплей ОС ОСОБЕНА ОСОБЕННОСТИ ОСОБЕННОСТИ ОСОБЕННОСТЬ ИСПОЛЬЗОВАНИЯ НА ДЕЙСТВИЯХ, которые включены в объект PlaybackStateCompat .

По умолчанию ваше приложение должно поддерживать следующие действия:

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

Кроме того, у вас есть возможность создать очередь воспроизведения, которая может быть отображена для пользователя, но это не требуется. Для этого вызовите методы setQueue() и setQueueTitle() , включите действие ACTION_SKIP_TO_QUEUE_ITEM и определите обратный вызов onSkipToQueueItem() .

Кроме того, добавьте поддержку для теперь играющего значка, который является индикатором того, что в настоящее время играет. Для этого вызовите метод setActiveQueueItemId() и передайте идентификатор игрового элемента в настоящее время в очереди. Вам нужно обновить setActiveQueueItemId() всякий раз, когда происходит изменение очереди.

Android Auto и Android Automotive OS кнопки отображения для каждого включенного действия, а также очередь воспроизведения. Когда кнопки нажимают, система вызывает соответствующий обратный вызов от MediaSessionCompat.Callback .

Резервировать неиспользованное пространство

Android Auto и Android Automotive OS Space в пользовательском интерфейсе для действий ACTION_SKIP_TO_PREVIOUS и ACTION_SKIP_TO_NEXT . Если ваше приложение не поддерживает одну из этих функций, Android Auto и Android Automotive OS используйте пространство для отображения любых пользовательских действий, которые вы создаете.

Если вы не хотите заполнять эти пространства настраиваемыми действиями, вы можете зарезервировать их так, чтобы Android Auto и Android Automotive OS покидает пространство, когда ваше приложение не поддерживает соответствующую функцию. Для этого вызовите метод setExtras() с помощью пакета дополнений, который содержит константы, которые соответствуют зарезервированным функциям. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT соответствует ACTION_SKIP_TO_NEXT , и SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV соответствует ACTION_SKIP_TO_PREVIOUS . Используйте эти константы в качестве клавиш в пакете и используйте логическое true для их значений.

Установите начальный PlaybackState

Поскольку Android Auto и Android Automotive OS общаются с вашей службой Media Browser, ваша медиа -сессия передает статус воспроизведения контента, используя PlaybackStateCompat . Ваше приложение не должно автоматически начинать воспроизведение музыки, когда Android Automotive OS или Android Auto подключаются к служению вашего браузера. Вместо этого полагайтесь на Android Auto и Android Automotive OS, чтобы возобновить или начать воспроизведение на основе состояния автомобиля или действий пользователя.

Чтобы сделать это, установите первоначальный PlaybackStateCompat вашего сеанса медиа в STATE_STOPPED , STATE_PAUSED , STATE_NONE или STATE_ERROR .

Сессии медиа -сеансы в Android Auto и Android Automotive OS длится только на протяжении всего диска, поэтому пользователи часто начинают и останавливают эти сеансы. Чтобы продвигать беспрепятственный опыт между дисками, отслеживайте предыдущее состояние сеанса пользователя, чтобы, когда медиа -приложение получает запрос на резюме, пользователь может автоматически поднять, где они остановились, - например, последний воспроизводимый медиа -элемент, PlaybackStateCompat и очередь.

Добавить пользовательские действия воспроизведения

Вы можете добавить пользовательские действия воспроизведения, чтобы отобразить дополнительные действия, которые поддерживает ваше приложение для медиа. Если пространство позволяет (и не зарезервировано) , Android добавляет пользовательские действия в транспортные элементы управления. В противном случае, пользовательские действия отображаются в меню переполнения. Пользовательские действия отображаются в порядке, которые они добавлены в PlaybackStateCompat .

Используйте пользовательские действия, чтобы обеспечить поведение, отличное от стандартных действий . Не используйте их для замены или дублирования стандартных действий.

Вы можете добавить пользовательские действия, используя метод addCustomAction() в классе PlaybackStateCompat.Builder .

Следующий фрагмент кода показывает, как добавить пользовательское действие «Запустить радиоканал»:

Котлин

val customActionExtras = Bundle()
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO)

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon // or R.drawable.media3_icon_radio
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

Ява

Bundle customActionExtras = new Bundle();
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO);

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon) // or R.drawable.media3_icon_radio
    .setExtras(customActionExtras)
    .build());

Для более подробного примера этого метода см. Метод setCustomAction() в примере Universal Android Music Player на GitHub.

После создания пользовательского действия ваш сеанс медиа может ответить на действие, переопределив метод onCustomAction() .

Следующий фрагмент кода показывает, как ваше приложение может ответить на действие «Начать радионакана»:

Котлин

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

Ява

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

Для более подробного примера этого метода см. Метод onCustomAction в приложении Universal Android Music Player на GitHub.

Иконки для пользовательских действий

Каждое пользовательское действие, которое вы создаете, требует значка.

Если описание этого значка соответствует одной из констант CommandButton.ICON_ , вы должны установить это целочисленное значение для EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT КЛЮЧ НАСТОЯЩЕГО ДЕЙСТВИЯ. В поддерживаемых системах это будет переопределять ресурс значка, переданный в CustomAction.Builder , позволяя компонентам системы представлять ваши действия и другие действия воспроизведения в последовательном стиле.

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

Если настраиваемое действие является государственным - например, оно включает настройку воспроизведения или выключение - предоставляет различные значки для разных состояний, чтобы пользователи могли видеть изменение, когда они выберут действие.

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

Когда для текущего контекста недоступное действие недоступно, поменяйте значок «Пользовательский действие» на альтернативный значок, который показывает, что действие отключено.

Рисунок 6. Образцы значков из нестандартных действий.

Укажите аудиоформат

Чтобы указать, что в настоящее время воспроизводимый медиа использует специальный аудио -формат, вы можете указать значки, которые отображаются в автомобилях, которые поддерживают эту функцию. Вы можете установить KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI и KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI в комплекте Extras в данный момент воспроизводимого носителя (передача To MediaSession.setMetadata() ). Обязательно установите оба этих дополнительных данных, чтобы приспособить различные макеты.

Кроме того, вы можете установить дополнительное дополнение KEY_IMMERSIVE_AUDIO , чтобы сообщить автомобильным OEM -производителям, что это захватывающий звук, и они должны быть очень осторожны при принятии решения о том, следует ли применять звуковые эффекты, которые могут мешать захватывающему контенту.

Вы можете настроить в данный момент элемент медиа в настоящее время, чтобы его подзаголовок, описание или оба были ссылками на другие элементы медиа. Это позволяет пользователю быстро перейти к соответствующим элементам; Например, они могут перейти к другим песням того же артиста, других эпизодах этого подкаста и т. Д. Если автомобиль поддерживает эту функцию, пользователи могут нажать на ссылку на просмотр этого контента.

Чтобы добавить ссылки, настройте метаданные KEY_SUBTITLE_LINK_MEDIA_ID (для ссылки из подзаголовок) или KEY_DESCRIPTION_LINK_MEDIA_ID (для ссылки из описания). Для получения подробной информации см. Справочную документацию для этих полей метаданных.

Поддержать голосовые действия

Ваше медиа -приложение должно поддерживать голосовые действия, чтобы помочь водителям безопасного и удобного опыта, который сводит к минимуму отвлечения. Например, если ваше приложение воспроизводит один медиа -элемент, пользователь может сказать « Play [Song Tilting] », чтобы сказать вашему приложению, чтобы воспроизвести другую песню, не глядя и не касаясь дисплея автомобиля. Пользователи могут инициировать запросы, щелкнув соответствующие кнопки на своем рулевом колесе или выступая на горячие слова « ОК, Google ».

Когда Android Auto или Android Automotive OS обнаруживает и интерпретирует голосовое действие, это голосовое действие доставляется в приложение через onPlayFromSearch() . Получив этот обратный вызов, приложение находит контент, соответствующий строке query и начинает воспроизведение.

Пользователи могут указать различные категории терминов в своем запросе: жанр, исполнитель, альбом, название песни, радиостанция или плейлист, среди прочих. При создании поддержки для поиска учитывайте все категории, которые имеют смысл для вашего приложения. Если Android Auto или Android Automotive OS обнаруживает, что заданный запрос вписывается в определенные категории, он добавляет дополнения в параметр extras . Можно отправить следующие дополнения:

Учетная запись для пустой строки query , которая может быть отправлена Android Auto или Android Automotive OS, если пользователь не указывает термины поиска. Например, если пользователь говорит: « Играйте в музыку ». В этом случае ваше приложение может запустить недавно сыгранный или недавно предложенный трек.

Если поиск не может быть обработан быстро, не блокируйте в onPlayFromSearch() . Вместо этого установите состояние воспроизведения в STATE_CONNECTING и выполните поиск в асинхронизированном потоке.

Как только начинается воспроизведение, рассмотрите возможность заполнить очередь сеанса медиа с помощью связанного контента. Например, если пользователь просит воспроизвести альбом, ваше приложение может заполнить очередь трек -листом альбома. Также рассмотрите возможность реализации поддержки результатов поиска , чтобы пользователь мог выбрать другой трек, который соответствует их запросу.

В дополнение к « Воспроизведение » запросов, Android Auto и Android Automotive OS распознают голосовые запросы для управления воспроизведением, например, « Music Pause Music » и « Next Song », и сопоставьте эти команды с соответствующими обратными вызовами сеанса медиа, такими как onPause() и onSkipToNext() .

Для получения подробного примера о том, как реализовать действия воспроизведения в своем приложении, см. Google Assistant и Media Apps .

Реализовать предотвращение гарантий

Поскольку телефон пользователя подключен к динамикам их автомобиля при использовании Android Auto, вы должны принять дополнительные меры предосторожности, чтобы предотвратить отвлечение водителя.

Подавить тревоги в машине

Приложения Android Auto Media не должны начинать воспроизводить аудио через динамики автомобилей, если пользователь не начнет воспроизведение, например, нажав кнопку воспроизведения. Даже пользовательская запланированная тревога из вашего медиа-приложения не должна начинать воспроизводить музыку через динамики автомобилей.

Чтобы выполнить это требование, ваше приложение может использовать CarConnection в качестве сигнала перед воспроизведением какого -либо звука. Ваше приложение может проверить, проецируется ли телефон на экране автомобиля, наблюдая за LiveData для типа автомобильного соединения и проверив, равно ли он CONNECTION_TYPE_PROJECTION .

Если телефон пользователя проецирует, медиа -приложения, которые поддерживают тревоги, должны делать одну из следующих вещей:

  • Отключить сигнал тревоги.
  • Воспроизведите сигнал тревоги над STREAM_ALARM и предоставьте пользовательский интерфейс на экране телефона, чтобы отключить сигнал тревоги.

Обрабатывать медиа -рекламу

По умолчанию Android Auto отображает уведомление, когда метаданные мультимедиа меняются во время сеанса воспроизведения звука. Когда медиа -приложение переключается от воспроизведения музыки на запуск рекламы, он отвлекает, чтобы отобразить уведомление пользователю. Чтобы Android Auto отображала уведомление в этом случае, вы должны установить метаданные метаданные средства массовой информации METADATA_KEY_IS_ADVERTISEMENT в METADATA_VALUE_ATTRIBUTE_PRESENT , как показано в следующем фрагменте кода:

Котлин

import androidx.media.utils.MediaConstants

override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
    MediaMetadataCompat.Builder().apply {
        if (isAd(mediaId)) {
            putLong(
                MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        }
        // ...add any other properties you normally would.
        mediaSession.setMetadata(build())
    }
}

Ява

import androidx.media.utils.MediaConstants;

@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
    if (isAd(mediaId)) {
        builder.putLong(
            MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
    }
    // ...add any other properties you normally would.
    mediaSession.setMetadata(builder.build());
}

Обрабатывать общие ошибки

Когда приложение испытывает ошибку, установите состояние воспроизведения в STATE_ERROR и предоставьте сообщение об ошибке, используя метод setErrorMessage() . См. PlaybackStateCompat для списка кодов ошибок, которые вы можете использовать при настройке сообщения об ошибке. Сообщения об ошибках должны быть пользовательскими и локализованными с текущей локацией пользователя. Android Auto и Android Automotive OS может затем отобразить сообщение об ошибке пользователю.

Например, если содержимое недоступно в текущей области пользователя, вы можете использовать код ошибки ERROR_CODE_NOT_AVAILABLE_IN_REGION при настройке сообщения об ошибке.

Котлин

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build())

Ява

mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build());

Для получения дополнительной информации о состояниях ошибок см. В использовании сеанса СМИ: состояния и ошибки .

Если пользователь Android Auto должен открыть ваше телефонное приложение для разрешения ошибки, предоставьте пользователю эту информацию в вашем сообщении. Например, ваше сообщение об ошибке может сказать «Войдите в [имя вашего приложения]» вместо «Пожалуйста, войдите в систему».

Другие ресурсы