Сеансы мультимедиа предоставляют универсальный способ взаимодействия с аудио- или видеопроигрывателем. В Media3 проигрывателем по умолчанию является класс ExoPlayer, который реализует интерфейс Player. Подключение сеанса мультимедиа к проигрывателю позволяет приложению рекламировать воспроизведение мультимедиа извне и получать команды воспроизведения из внешних источников.
Команды могут поступать от физических кнопок, например кнопки воспроизведения на гарнитуре или пульте телевизора. Они также могут поступать из клиентских приложений, у которых есть контроллер мультимедиа, например при команде "Пауза" в Google Ассистенте. Сеанс мультимедиа передает эти команды проигрывателю мультимедийного приложения.
Когда выбирать медиасеанс
Реализовав MediaSession, вы позволите пользователям управлять воспроизведением:
- через наушники; Часто на наушниках есть кнопки или сенсорные элементы, с помощью которых пользователь может воспроизводить или приостанавливать медиаконтент, а также переходить к следующему или предыдущему треку.
- С помощью Google Ассистента. Например, чтобы приостановить воспроизведение медиаконтента на устройстве, можно сказать "Окей, Google, пауза".
- через часы Wear OS. Это позволяет упростить доступ к наиболее распространенным элементам управления воспроизведением при воспроизведении на телефоне.
- С помощью элементов управления мультимедиа. В этой карусели показаны элементы управления для каждого запущенного сеанса воспроизведения.
- На телевизоре. Позволяет выполнять действия с помощью физических кнопок воспроизведения, элементов управления воспроизведением на платформе и управления питанием (например, если телевизор, саундбар или аудио- или видеоресивер выключается или переключается вход, воспроизведение в приложении должно остановиться).
- Через элементы управления мультимедиа в Android Auto. Это позволяет безопасно управлять воспроизведением во время вождения.
- и другие внешние процессы, которые могут повлиять на воспроизведение.
Это удобно во многих случаях. В частности, мы рекомендуем использовать MediaSession в следующих случаях:
- Вы смотрите длинный видеоконтент, например фильмы или онлайн-телевидение.
- Вы транслируете длинный аудиоконтент, например подкасты или музыкальные плейлисты.
- Вы разрабатываете приложение для телевизоров.
Однако не все сценарии использования подходят для MediaSession. Использовать только Player рекомендуется в следующих случаях:
- Вы показываете короткие видео, для которых не требуется внешнее управление или фоновое воспроизведение.
- Нет одного активного видео, например пользователь прокручивает список и на экране одновременно показывается несколько видео.
- Вы показываете одноразовое вступительное или поясняющее видео, которое пользователь должен активно смотреть, не нуждаясь во внешних элементах управления воспроизведением.
- Ваш контент конфиденциален, и вы не хотите, чтобы внешние процессы имели доступ к метаданным медиафайлов (например, в режиме инкогнито в браузере).
Если ни один из перечисленных выше вариантов вам не подходит, подумайте, допустимо ли, чтобы приложение продолжало воспроизведение, когда пользователь не взаимодействует с контентом. Если да, то вам, скорее всего, подойдет MediaSession. Если нет, то, скорее всего, вам нужен Player.
Как создать сеанс воспроизведения медиаконтента
Сеанс мультимедиа существует вместе с проигрывателем, которым он управляет. Вы можете создать сеанс воспроизведения медиаконтента с помощью объектов Context и Player. Создавать и инициализировать сеанс воспроизведения нужно тогда, когда это необходимо, например в методе жизненного цикла onStart() или onResume() класса Activity или Fragment или в методе onCreate() класса Service, которому принадлежит сеанс воспроизведения и связанный с ним проигрыватель.
Чтобы создать сеанс воспроизведения, инициализируйте Player и передайте его в MediaSession.Builder, как показано ниже.
Kotlin
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=, то, скорее всего, сеанс был неожиданно создан до того, как был выпущен ранее созданный экземпляр с тем же идентификатором. Чтобы избежать утечки сеансов из-за ошибки программирования, такие случаи обнаруживаются и уведомляются путем создания исключения.
Предоставление управления другим клиентам
Сеанс воспроизведения – это ключ к управлению воспроизведением. Он позволяет направлять команды из внешних источников в проигрыватель, который воспроизводит медиаконтент. Это могут быть физические кнопки, например кнопка воспроизведения на гарнитуре или пульте телевизора, или косвенные команды, например "Пауза" в Google Ассистенте. Аналогичным образом вы можете предоставить доступ к системе Android, чтобы упростить управление уведомлениями и заблокированным экраном, или к часам Wear OS, чтобы управлять воспроизведением с циферблата. Внешние клиенты могут использовать контроллер мультимедиа для отправки команд воспроизведения в ваше мультимедийное приложение. Эти команды принимаются сеансом мультимедиа, который в конечном итоге делегирует их медиапроигрывателю.
Когда контроллер собирается подключиться к сеансу воспроизведения медиаконтента, вызывается метод onConnect(). Вы можете использовать предоставленные ControllerInfo, чтобы решить, принять или отклонить запрос. Пример принятия запроса на подключение приведен в разделе Объявление специальных команд.
После подключения контроллер может отправлять команды воспроизведения в сеанс. Затем сеанс передает эти команды проигрывателю. Команды воспроизведения и плейлиста, определенные в интерфейсе Player, автоматически обрабатываются сеансом.
Другие методы обратного вызова позволяют обрабатывать, например, запросы на пользовательские команды и изменение плейлиста. Эти обратные вызовы также включают объект ControllerInfo, поэтому вы можете изменить способ ответа на каждый запрос для каждого контроллера.
Изменить плейлист
Сеанс мультимедиа может напрямую изменять плейлист своего проигрывателя, как описано в руководстве по плейлистам ExoPlayer.
Контроллеры также могут изменять плейлист, если им доступны COMMAND_SET_MEDIA_ITEM или COMMAND_CHANGE_MEDIA_ITEMS.
При добавлении новых элементов в плейлист проигрывателю обычно требуются экземпляры MediaItem с определенным URI, чтобы их можно было воспроизвести. По умолчанию новые элементы автоматически пересылаются в методы проигрывателя, например player.addMediaItem, если для них определен URI.
Если вы хотите настроить экземпляры 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 используются для определения настроек кнопок управления медиа. Каждая кнопка определяет три аспекта нужного элемента интерфейса:
- Значок, определяющий внешний вид. При создании
CommandButton.Builderзначок должен быть задан одной из предопределенных констант. Обратите внимание, что это не растровое изображение и не ресурс изображения. Общая константа помогает контроллерам выбрать подходящий ресурс для единообразного внешнего вида в собственном пользовательском интерфейсе. Если ни одна из предопределенных констант значков не подходит для вашего случая, вы можете использоватьsetCustomIconResId. - Команда, определяющая действие, которое запускается, когда пользователь взаимодействует с кнопкой. Вы можете использовать
setPlayerCommandдляPlayer.CommandилиsetSessionCommandдля стандартного или специальногоSessionCommand. - Слот, определяющий, где должна быть размещена кнопка в интерфейсе контроллера. Это поле необязательно и заполняется автоматически на основе значений полей Значок и Команда. Например, можно указать, что кнопка должна отображаться в области навигации "Вперед", а не в области "Ещё", которая используется по умолчанию.
Kotlin
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();
При разрешении конфликтов в настройках кнопок мультимедиа применяется следующий алгоритм:
- Для каждого значения
CommandButtonв настройках кнопок управления медиа поместите кнопку в первый доступный и разрешенный слот. - Если в центральном, переднем или заднем слоте нет кнопки, добавьте в него кнопку по умолчанию.
Вы можете использовать CommandButton.DisplayConstraints, чтобы посмотреть, как будут выглядеть кнопки в зависимости от ограничений интерфейса.
Как настроить кнопки управления медиаконтентом
Проще всего задать настройки кнопок мультимедиа при создании MediaSession. Вы также можете переопределить MediaSession.Callback.onConnect, чтобы настроить параметры медиакнопок для каждого подключенного контроллера.
Kotlin
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. Вы можете изменить настройки для всех контроллеров или для определенного контроллера.
Kotlin
// 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() и задать доступные специальные команды для каждого подключенного контроллера.
Kotlin
private class CustomMediaSessionCallback : MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnectAsync( session: MediaSession, controller: ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return Futures.immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } }
Java
private static class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return Futures.immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } }
Чтобы получать запросы на выполнение специальных команд от MediaController, переопределите метод onCustomCommand() в Callback.
Kotlin
private class CustomCallback : MediaSession.Callback { // ... override fun onCustomCommand( session: MediaSession, controller: 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)) } // ... return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } }
Java
private static class CustomCallback 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)); } // ... return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } }
Вы можете отслеживать, какой контроллер мультимедиа отправляет запрос, с помощью свойства packageName объекта MediaSession.ControllerInfo, который передается в методы Callback. Это позволяет настраивать поведение приложения в ответ на определенную команду, если она исходит от системы, вашего собственного приложения или других клиентских приложений.
Как настроить команды проигрывателя по умолчанию
Все команды по умолчанию и обработка состояния делегируются объекту Player, который находится в MediaSession. Чтобы настроить поведение команды, определенной в интерфейсе Player, например play() или seekToNext(), оберните Player в ForwardingSimpleBasePlayer, прежде чем передавать его в MediaSession:
Kotlin
val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
Подробную информацию о ForwardingSimpleBasePlayer можно найти в руководстве по ExoPlayer в разделе Настройка.
Определите контроллер, запрашивающий команду проигрывателя
Если вызов метода Player исходит от MediaController, вы можете определить источник с помощью MediaSession.controllerForCurrentRequest и получить ControllerInfo для текущего запроса:
Kotlin
private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { private lateinit var session: MediaSession 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
private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer { private MediaSession session; 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:
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
Перенаправляющий проигрыватель может использовать ForwardingSimpleBasePlayer, чтобы перехватить ошибку и настроить ее код, сообщение или дополнительные параметры. Аналогичным образом можно сгенерировать новые ошибки, которых нет в исходном проигрывателе:
Kotlin
private 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 } private 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
private static 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); } }
Некритические ошибки
Приложение может отправлять всем или определенным контроллерам некритические ошибки, которые не вызваны техническим исключением.
Kotlin
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:
Kotlin
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. } });