Управляйте воспроизведением и рекламируйте его с помощью MediaSession.

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

Команды могут поступать от физических кнопок, таких как кнопка воспроизведения на гарнитуре или пульте дистанционного управления телевизором. Они также могут поступать от клиентских приложений, имеющих медиаконтроллер, например, команда «пауза» для Google Ассистента. Медиа-сессия делегирует эти команды проигрывателю медиаприложения.

Когда следует выбирать медиасессию

Внедрение MediaSession позволяет пользователям управлять воспроизведением:

  • Через наушники . Часто на наушниках есть кнопки или сенсорные элементы, с помощью которых пользователь может воспроизводить или приостанавливать воспроизведение медиафайлов, а также переходить к следующему или предыдущему треку.
  • Обратившись к Google Ассистенту , можно использовать следующий способ: сказать «Окей, Google, пауза» , чтобы приостановить воспроизведение любого медиафайла на устройстве.
  • Через свои часы Wear OS . Это обеспечивает более легкий доступ к наиболее распространенным элементам управления воспроизведением во время игры на телефоне.
  • Через элементы управления мультимедиа . В этой карусели отображаются элементы управления для каждой запущенной сессии воспроизведения мультимедиа.
  • Приложение для телевизора позволяет выполнять действия с помощью физических кнопок воспроизведения, управлять воспроизведением на платформе и управлять питанием (например, если телевизор, саундбар или AV-ресивер выключаются или переключается вход, воспроизведение должно остановиться в приложении).
  • С помощью элементов управления мультимедиа Android Auto . Это позволяет безопасно управлять воспроизведением во время вождения.
  • А также любые другие внешние процессы, которые могут повлиять на воспроизведение.

Это отлично подходит для многих сценариев использования. В частности, вам следует всерьез рассмотреть возможность использования MediaSession в следующих случаях:

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

Однако не все сценарии использования хорошо подходят для MediaSession . В следующих случаях вам может понадобиться использовать только Player :

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

Если ваш сценарий использования не соответствует ни одному из перечисленных выше, подумайте, устраивает ли вас, если ваше приложение продолжит воспроизведение, когда пользователь не взаимодействует с контентом. Если ответ «да», вам, вероятно, следует выбрать MediaSession . Если ответ «нет», вам, вероятно, следует использовать Player .

Создать медиасессию

Медиа-сессия существует параллельно с управляемым ею проигрывателем. Вы можете создать медиа-сессию, используя объекты Context и Player . Создавать и инициализировать медиа-сессию следует по мере необходимости, например, в методах жизненного цикла onStart() или onResume() объекта Activity или Fragment , или в методе onCreate() объекта Service , которому принадлежит медиа-сессия и связанный с ней проигрыватель.

Для создания медиасессии инициализируйте Player и передайте его в MediaSession.Builder следующим образом:

Котлин

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Автоматическая обработка состояний

Библиотека Media3 автоматически обновляет медиасессию, используя состояние проигрывателя. Таким образом, вам не нужно вручную обрабатывать сопоставление проигрывателя с сессией.

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

Уникальный идентификатор сессии

По умолчанию MediaSession.Builder создает сессию с пустой строкой в ​​качестве идентификатора сессии. Этого достаточно, если приложение планирует создать только один экземпляр сессии, что является наиболее распространенным случаем.

Если приложение хочет управлять несколькими экземплярами сессий одновременно, оно должно гарантировать уникальность идентификатора каждой сессии. Идентификатор сессии можно установить при создании сессии с помощью MediaSession.Builder.setId(String id) .

Если вы видите исключение IllegalStateException приводящее к сбою приложения с сообщением об ошибке IllegalStateException: Session ID must be unique. ID= то, вероятно, сессия была неожиданно создана до того, как был освобожден ранее созданный экземпляр с тем же ID. Чтобы избежать утечки сессий из-за программной ошибки, такие случаи обнаруживаются и уведомляются путем генерации исключения.

Предоставить контроль другим клиентам

Медиа-сессия — это ключ к управлению воспроизведением. Она позволяет направлять команды от внешних источников к плееру, который воспроизводит ваши медиафайлы. Этими источниками могут быть физические кнопки, такие как кнопка воспроизведения на гарнитуре или пульте дистанционного управления телевизора, или косвенные команды, например, команда «пауза» для Google Ассистента. Аналогично, вы можете предоставить доступ системе Android для управления уведомлениями и экраном блокировки, или часам Wear OS, чтобы управлять воспроизведением с циферблата. Внешние клиенты могут использовать медиаконтроллер для отправки команд воспроизведения вашему медиаприложению. Эти команды принимаются вашей медиа-сессией, которая в конечном итоге передает их медиаплееру.

Диаграмма, демонстрирующая взаимодействие между MediaSession и MediaController.
Рисунок 1 : Медиаконтроллер обеспечивает передачу команд от внешних источников в медиасессию.

Когда контроллер собирается подключиться к вашей медиа-сессии, вызывается метод onConnect() . Вы можете использовать предоставленный ControllerInfo , чтобы решить, принимать или отклонять запрос. Пример принятия запроса на подключение см. в разделе «Объявление пользовательских команд» .

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

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

Измените плейлист

Медиасеанс может напрямую изменять плейлист своего проигрывателя, как описано в руководстве ExoPlayer по плейлистам . Контроллеры также могут изменять плейлист, если для них доступны команды COMMAND_SET_MEDIA_ITEM или COMMAND_CHANGE_MEDIA_ITEMS .

При добавлении новых элементов в плейлист плееру обычно требуются экземпляры MediaItem с заданным URI , чтобы сделать их воспроизводимыми. По умолчанию, если у вновь добавленных элементов задан URI, они автоматически перенаправляются в методы плеера, такие как player.addMediaItem .

Если вы хотите настроить экземпляры MediaItem , добавляемые в плеер, вы можете переопределить onAddMediaItems() . Этот шаг необходим, если вы хотите поддерживать контроллеры, запрашивающие медиафайлы без определенного URI. Вместо этого MediaItem обычно имеет одно или несколько из следующих полей, описывающих запрашиваемые медиафайлы:

  • MediaItem.id : Универсальный идентификатор, определяющий медиафайл.
  • MediaItem.RequestMetadata.mediaUri : URI запроса, который может использовать пользовательскую схему и не обязательно воспроизводится непосредственно проигрывателем.
  • MediaItem.RequestMetadata.searchQuery : Текстовый поисковый запрос, например, из Google Ассистента.
  • MediaItem.MediaMetadata : Структурированные метаданные, такие как «название» или «исполнитель».

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

Управление настройками кнопок мультимедиа

Каждый контроллер, например, системный интерфейс, Android Auto или Wear OS, может самостоятельно определять, какие кнопки отображать пользователю. Чтобы указать, какие элементы управления воспроизведением вы хотите показать пользователю, вы можете задать параметры кнопок мультимедиа в MediaSession . Эти параметры представляют собой упорядоченный список экземпляров CommandButton , каждый из которых определяет параметр для кнопки в пользовательском интерфейсе.

Определите кнопки управления

Экземпляры CommandButton используются для определения настроек кнопок управления мультимедиа. Каждая кнопка определяет три аспекта желаемого элемента пользовательского интерфейса:

  1. Значок , определяющий визуальное оформление. При создании CommandButton.Builder значок должен быть установлен на одну из предопределенных констант. Обратите внимание, что это не фактический ресурс Bitmap или изображения. Универсальная константа помогает контроллерам выбрать подходящий ресурс для обеспечения единообразного внешнего вида в собственном пользовательском интерфейсе. Если ни одна из предопределенных констант значка не подходит для вашего случая, вы можете использовать setCustomIconResId вместо этого.
  2. Параметр Command определяет действие, запускаемое при взаимодействии пользователя с кнопкой. Для Player.Command можно использовать setPlayerCommand , а для SessionCommand setSessionCommand .
  3. Поле « Слот» определяет местоположение кнопки в пользовательском интерфейсе контроллера. Это поле необязательно и устанавливается автоматически на основе значка и команды . Например, оно позволяет указать, что кнопка должна отображаться в области навигации «вперед» вместо области «переполнения» по умолчанию.

Котлин

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

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

  1. Для каждой CommandButton в настройках кнопок мультимедиа поместите кнопку в первое доступное и разрешенное место .
  2. Если какой-либо из центральных, передних или задних слотов не заполнен кнопкой, добавьте кнопки по умолчанию для этого слота .

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

Настройка параметров кнопок мультимедиа

Простейший способ задать параметры кнопок управления мультимедиа — определить список при создании объекта MediaSession . В качестве альтернативы можно переопределить MediaSession.Callback.onConnect , чтобы настроить параметры кнопок управления мультимедиа для каждого подключенного контроллера.

Котлин

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

Java

MediaSession mediaSession =
  new MediaSession.Builder(context, player)
      .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
      .build();

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

После взаимодействия с вашим плеером вам может потребоваться обновить кнопки, отображаемые в пользовательском интерфейсе контроллера. Типичный пример — кнопка-переключатель, которая меняет свой значок и действие после срабатывания связанного с ней действия. Чтобы обновить настройки кнопок мультимедиа, вы можете использовать MediaSession.setMediaButtonPreferences , чтобы обновить настройки для всех контроллеров или для конкретного контроллера:

Котлин

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
  ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
    ImmutableList.of(likeButton, removeFromFavoritesButton));

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

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

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

В медиаприложениях можно определять пользовательские команды, которые, например, могут использоваться в настройках кнопок медиафайлов . Например, вы можете захотеть реализовать кнопки, позволяющие пользователю сохранить медиафайл в список избранных. MediaController отправляет пользовательские команды, а MediaSession.Callback их принимает.

Для определения пользовательских команд необходимо переопределить метод MediaSession.Callback.onConnect() , чтобы установить доступные пользовательские команды для каждого подключенного контроллера.

Котлин

private class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

Чтобы получать запросы на пользовательские команды от MediaController , переопределите метод onCustomCommand() в Callback .

Котлин

private class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

Отследить, какой медиаконтроллер отправляет запрос, можно с помощью свойства packageName объекта MediaSession.ControllerInfo , передаваемого в методы Callback . Это позволяет адаптировать поведение вашего приложения в ответ на заданную команду, независимо от того, исходит ли она от системы, вашего собственного приложения или других клиентских приложений.

Настройка команд игрока по умолчанию

Все команды по умолчанию и обработка состояния делегируются Player , находящемуся в MediaSession . Чтобы настроить поведение команды, определенной в интерфейсе Player , например play() или seekToNext() , оберните ваш Player в ForwardingSimpleBasePlayer перед передачей его в MediaSession :

Котлин

val player = (logic to build a Player instance)

val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) {
  // Customizations
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = (logic to build a Player instance)

ForwardingSimpleBasePlayer forwardingPlayer =
    new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

Для получения дополнительной информации о ForwardingSimpleBasePlayer см. руководство по настройке ExoPlayer.

Определите контроллер, запрашивающий команду игрока.

Когда вызов метода Player инициируется объектом MediaController , вы можете определить источник вызова с помощью MediaSession.controllerForCurrentRequest и получить информацию ControllerInfo для текущего запроса:

Котлин

class CallerAwarePlayer(player: Player) :
  ForwardingSimpleBasePlayer(player) {

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

Java

public class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(
        int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

Настройка обработки кнопок мультимедиа

Медиа-кнопки — это аппаратные кнопки, которые можно найти на устройствах Android и других периферийных устройствах, например, кнопка воспроизведения/паузы на Bluetooth-гарнитуре. Media3 обрабатывает события нажатия медиа-кнопок, когда они поступают в сессию, и вызывает соответствующий метод Player в проигрывателе сессии .

Рекомендуется обрабатывать все входящие события нажатия кнопок мультимедиа в соответствующем методе Player . Для более сложных сценариев использования события нажатия кнопок мультимедиа можно перехватывать в MediaSession.Callback.onMediaButtonEvent(Intent) .

Обработка и сообщение об ошибках

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

Критические ошибки воспроизведения

При возникновении критической ошибки воспроизведения проигрыватель сообщает об этом в сессию, а затем контроллеры передают эту информацию для вызова методов Player.Listener.onPlayerError(PlaybackException) и Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException) .

В таком случае состояние воспроизведения переходит в STATE_IDLE , и MediaController.getPlaybackError() возвращает исключение PlaybackException , вызвавшее этот переход. Контроллер может проверить PlayerException.errorCode , чтобы получить информацию о причине ошибки.

Ошибка при настройке пользовательского проигрывателя

В дополнение к фатальным ошибкам, сообщаемым плеером, приложение может установить пользовательское исключение PlaybackException на уровне MediaSession, используя MediaSession.setPlaybackException(PlaybackException) . Это позволяет приложению сигнализировать о состоянии ошибки подключенным контроллерам. Исключение может быть установлено для всех подключенных контроллеров или для конкретного ControllerInfo .

Когда приложение устанавливает исключение PlaybackException , используя этот API:

  • Подключенные экземпляры MediaController будут уведомлены. Будут вызваны колбэки Listener.onPlayerError(PlaybackException) и Listener.onPlayerErrorChanged(@Nullable PlaybackException) контроллера с указанным исключением.

  • Метод MediaController.getPlayerError() вернет исключение PlaybackException установленное приложением.

  • Состояние воспроизведения для затронутых контроллеров изменится на Player.STATE_IDLE .

  • Доступные команды будут удалены, и останутся только команды чтения, такие как COMMAND_GET_TIMELINE , на случай, если они уже предоставлены. Например, состояние Timeline фиксируется в состоянии, в котором было применено исключение к контроллеру. Команды, пытающиеся изменить состояние проигрывателя, такие как COMMAND_PLAY , удаляются до тех пор, пока приложение не устранит исключение воспроизведения для данного контроллера.

Чтобы очистить ранее установленное пользовательское PlaybackException и восстановить обычную отчетность о состоянии проигрывателя, приложение может вызвать MediaSession.setPlaybackException(/* playbackException= */ null) или MediaSession.setPlaybackException(ControllerInfo, /* playbackException= */ null) .

Настройка критических ошибок

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

Котлин

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

Java

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

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

Котлин

class ErrorForwardingPlayer (private val context: Context, player: Player) :
    ForwardingSimpleBasePlayer(player) {

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon()
          .setPlayerError(customizePlaybackException(state.playerError!!))
          .build()
    }
    return state
  }

  fun customizePlaybackException(error: PlaybackException): PlaybackException {
    val buttonLabel: String
    val errorMessage: String
    when (error.errorCode) {
      PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
        buttonLabel = context.getString(R.string.err_button_label_restart_stream)
        errorMessage = context.getString(R.string.err_msg_behind_live_window)
      }
      else -> {
        buttonLabel = context.getString(R.string.err_button_label_ok)
        errorMessage = context.getString(R.string.err_message_default)
      }
    }
    val extras = Bundle()
    extras.putString("button_label", buttonLabel)
    return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
  }
}

Java

class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon()
              .setPlayerError(customizePlaybackException(state.playerError))
              .build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    String buttonLabel;
    String errorMessage;
    switch (error.errorCode) {
      case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
        buttonLabel = context.getString(R.string.err_button_label_restart_stream);
        errorMessage = context.getString(R.string.err_msg_behind_live_window);
        break;
      default:
        buttonLabel = context.getString(R.string.err_button_label_ok);
        errorMessage = context.getString(R.string.err_message_default);
        break;
    }
    Bundle extras = new Bundle();
    extras.putString("button_label", buttonLabel);
    return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
  }
}

Нефатальные ошибки

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

Котлин

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
mediaSession.mediaNotificationControllerInfo?.let {
  mediaSession.sendError(it, sessionError)
}

Java

SessionError sessionError = new SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired));

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

Когда в контроллер уведомлений мультимедиа отправляется некритическая ошибка, код ошибки и сообщение об ошибке реплицируются в сеанс мультимедиа платформы, при этом PlaybackState.state не изменяется на STATE_ERROR .

Получение нефатальных ошибок

Для получения некритической ошибки MediaController реализует интерфейс MediaController.Listener.onError :

Котлин

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

Java

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });