Сеансы мультимедиа обеспечивают универсальный способ взаимодействия с аудио- или видеопроигрывателем. В Media3 проигрывателем по умолчанию является класс ExoPlayer, который реализует интерфейс Player. Подключение сеанса мультимедиа к проигрывателю позволяет приложению рекламировать воспроизведение мультимедиа извне и получать команды воспроизведения из внешних источников.
Команды могут поступать от физических кнопок, например кнопки воспроизведения на гарнитуре или пульте телевизора. Они также могут поступать из клиентских приложений, у которых есть контроллер мультимедиа, например при команде "Пауза" ИИ-помощникам, таким как Gemini или Google Ассистент. Сеанс мультимедиа делегирует эти команды проигрывателю мультимедийного приложения.
Когда выбирать медиасеанс
Реализовав MediaSession, вы позволите пользователям управлять воспроизведением:
- Через наушники. Часто на наушниках есть кнопки или сенсорные элементы, с помощью которых пользователь может воспроизводить или приостанавливать медиаконтент, а также переходить к следующему или предыдущему треку.
- Обратиться к ИИ-помощнику, например Gemini или 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=, то, скорее всего, сеанс был неожиданно создан до того, как был выпущен ранее созданный экземпляр с тем же идентификатором. Чтобы избежать утечки сеансов из-за ошибки программирования, такие случаи обнаруживаются и уведомляются путем создания исключения.
Предоставление управления другим клиентам
Сеанс воспроизведения – это ключ к управлению воспроизведением. Он позволяет направлять команды из внешних источников в проигрыватель, который воспроизводит медиаконтент. Это могут быть физические кнопки, например кнопка воспроизведения на гарнитуре или пульте телевизора, или косвенные команды, например "пауза" для Gemini или Google Ассистента. Аналогично, вы можете предоставить доступ к системе Android для управления уведомлениями и заблокированным экраном или к часам Wear OS, чтобы управлять воспроизведением с циферблата. Внешние клиенты могут использовать контроллер мультимедиа для отправки команд воспроизведения в ваше мультимедийное приложение. Ваш сеанс мультимедиа получает эти команды и делегирует их медиаплееру.
Когда контроллер собирается подключиться к сеансу воспроизведения, сеанс вызывает onConnectAsync(). Вы можете использовать предоставленный ControllerInfo, чтобы решить, принять или отклонить запрос. Пример принятия запроса на подключение приведен в разделе Объявление и обработка специальных команд.
После подключения контроллер может отправлять сеансу команды воспроизведения. Затем сеанс делегирует эти команды проигрывателю. Команды воспроизведения и плейлиста, определенные в интерфейсе Player, автоматически обрабатываются сеансом.
Другие методы обратного вызова позволяют обрабатывать, например, запросы на пользовательские команды и изменение плейлиста. Эти обратные вызовы также включают объект ControllerInfo, поэтому вы можете изменить способ ответа на каждый запрос для каждого контроллера.
Изменить плейлист
Сеанс мультимедиа может напрямую изменять плейлист проигрывателя, как описано в руководстве по плейлистам ExoPlayer.
Контроллеры также могут изменять плейлист, если им доступен метод COMMAND_SET_MEDIA_ITEM или COMMAND_CHANGE_MEDIA_ITEMS.
Чтобы добавить в плейлист новые элементы, проигрывателю обычно требуются экземпляры MediaItem с определенным URI. По умолчанию новые добавленные элементы автоматически перенаправляются в методы проигрывателя, такие как player.addMediaItem, если для них определен URI.
Если вы хотите настроить экземпляры MediaItem, добавленные в проигрыватель, вы можете переопределить onAddMediaItems(). Этот шаг необходим, если вы хотите поддерживать контроллеры, которые запрашивают медиаконтент без определенного URI. Вместо этого в объекте MediaItem обычно задается одно или несколько из следующих полей, описывающих запрашиваемые медиафайлы:
MediaItem.mediaId– общий идентификатор медиаконтента.MediaItem.RequestMetadata.mediaUri– URI запроса, который может использовать специальную схему и не обязательно воспроизводится проигрывателем напрямую.MediaItem.RequestMetadata.searchQuery– текстовый поисковый запрос, например из Gemini или Google Ассистента.MediaItem.mediaMetadata– структурированные метаданные, например название или исполнитель.
Чтобы настроить новые плейлисты, вы можете переопределить метод onSetMediaItems(), который позволяет задать начальный элемент и позицию в плейлисте. Например, вы можете расширить один запрошенный элемент до целого плейлиста и указать проигрывателю начать с индекса изначально запрошенного элемента. Пример реализации onSetMediaItems() с этой функцией можно найти в демонстрационном приложении.
Публикация метаданных плейлиста
Media3 предоставляет API для публикации структурированных метаданных плейлиста вместе с воспроизведением активного трека. Стандартизация того, как ваше приложение заполняет членство в плейлисте и описательные метаданные в сеансах мультимедиа, позволяет Android предоставлять точки входа системного уровня для воспроизведения в вашем приложении.
В данном контексте термин плейлист относится к любой упорядоченной группе треков, которые воспроизводятся последовательно. Это относится не только к спискам отдельных воспроизводимых элементов, определенных пользователем или приложением, но и к таким группам, как музыкальные альбомы, выпуски подкастов и главы аудиокниг.
Укажите идентификатор и метаданные плейлиста
В Media3 идентификация плейлиста и его описательные метаданные разделены между двумя областями действия MediaMetadata:
- Идентификатор плейлиста на уровне объекта (
MediaItem.mediaMetadata.playlistId):- Чтобы отметить воспроизводимый объект
MediaItemкак часть плейлиста, вызовитеMediaMetadata.Builder.setPlaylistIdдляMediaMetadataобъекта, аMediaItem.mediaIdбудет уникальным идентификатором отдельного трека. playlistId– это непрозрачная строка, определяемая приложением и уникально идентифицирующая плейлист в разных сеансах, пока его можно возобновить (например,37i9dQZF1DX76Wlfdnj7AP). Ее нельзя использовать повторно для другого плейлиста. Подключенные контроллеры не интерпретируют этот идентификатор, а сохраняют его и отправляют обратно в приложение, чтобы возобновить воспроизведение плейлиста.- У всех треков в плейлисте должен быть один и тот же идентификатор
playlistId. Для треков, которые воспроизводятся не из плейлиста, например для отдельных треков, оставьтеplaylistIdнезаданным (null).
- Чтобы отметить воспроизводимый объект
- Метаданные плейлиста на уровне проигрывателя (
Player.getPlaylistMetadata):- Заполните специальный экземпляр
MediaMetadataописательными атрибутами плейлиста (setTitle,setArtworkUriиsetMediaType) и задайте его в проигрывателе с помощьюPlayer.setPlaylistMetadata. - Когда подключенный контроллер обнаруживает ненулевое значение
currentMediaItem.mediaMetadata.playlistId, он проверяетMediaController.getPlaylistMetadata, чтобы получить название плейлиста и URI изображения. - Обновляйте метаданные плейлиста каждый раз, когда меняется активный плейлист (например, когда пользователь запускает другой плейлист), чтобы
getPlaylistMetadataвсегда описывал плейлист, на который ссылаетсяplaylistIdтекущего элемента.
- Заполните специальный экземпляр
В примере ниже показано, как опубликовать метаданные плейлиста Player и связать с ним трек MediaItem.
Kotlin
// 1. Declare the playlist descriptive metadata on the Player val playlistMetadata = MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build() player.setPlaylistMetadata(playlistMetadata) // 2. Mark the playing track(s) with the playlistId it belongs to val trackItem = MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build() ) .build() player.setMediaItem(trackItem)
Java
// 1. Declare the playlist descriptive metadata on the Player MediaMetadata playlistMetadata = new MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build(); player.setPlaylistMetadata(playlistMetadata); // 2. Mark the playing track(s) with the playlistId it belongs to MediaItem trackItem = new MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( new MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build()) .build(); player.setMediaItem(trackItem);
Как продолжить воспроизведение плейлиста
Когда пользователь выбирает плейлист, контроллер подключается к сеансу воспроизведения в приложении и отправляет плейлист MediaItem, у которого для mediaId задано сохраненное значение playlistId:
Kotlin
val playlistItem = MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build() ) .build() controller.setMediaItem(playlistItem) controller.prepare() controller.play()
Java
MediaItem playlistItem = new MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( new MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build()) .build(); controller.setMediaItem(playlistItem); controller.prepare(); controller.play();
Приложение разворачивает плейлист MediaItem в воспроизводимые треки внутри MediaSession.Callback.onSetMediaItems (или onAddMediaItems) и снова публикует playlistId и playlistMetadata, как показано в предыдущем разделе.
Как настроить кнопки управления медиаконтентом
Каждый контроллер, например системный интерфейс, Android Auto или Wear OS, может самостоятельно решать, какие кнопки показывать пользователю. Чтобы указать, какие элементы управления воспроизведением должны быть доступны пользователю, задайте настройки кнопок мультимедиа в MediaSession. Эти настройки состоят из упорядоченного списка экземпляров CommandButton, каждый из которых определяет настройку для кнопки в пользовательском интерфейсе.
Определите кнопки команд
Экземпляры CommandButton используются для определения настроек кнопок мультимедиа. Каждая кнопка определяет три аспекта нужного элемента интерфейса:
- Значок, определяющий внешний вид. При создании
CommandButton.Builderзначок должен быть задан одной из предопределенных констант. Обратите внимание, что это не фактический ресурс Bitmap или Image. Общая константа помогает контроллерам выбрать подходящий ресурс для единообразного внешнего вида в собственном пользовательском интерфейсе. Если ни одна из предопределенных констант значков не подходит для вашего случая, вы можете использоватьsetCustomIconResId. - Команда, определяющая действие, которое запускается, когда пользователь нажимает кнопку. Вы можете использовать
setPlayerCommandдляPlayer.CommandилиsetSessionCommandдля стандартного или специальногоSessionCommand. - Slot – определяет, где должна быть размещена кнопка в интерфейсе контроллера. Это поле необязательно и заполняется автоматически на основе значений полей Icon и Command. Например, можно указать, что кнопка должна отображаться в области навигации "Вперед", а не в области "Переполнение", которая используется по умолчанию.
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.onConnectAsync(), чтобы настроить параметры медиакнопок для каждого подключенного контроллера.
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.onConnectAsync() и задать доступные специальные команды для каждого подключенного контроллера.
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().
Обработка ошибок и отчеты
Сеанс может генерировать и передавать контроллерам ошибки двух типов. Критические ошибки указывают на технический сбой воспроизведения в проигрывателе сеанса, который прерывает воспроизведение. При возникновении критических ошибок контроллеру автоматически отправляется отчет. Некритические ошибки – это нетехнические ошибки или ошибки, связанные с правилами, которые не прерывают воспроизведение и отправляются контроллерам приложением вручную.
Критические ошибки воспроизведения
Проигрыватель сообщает сеансу о критической ошибке воспроизведения, а затем сообщает контроллерам о вызове через Player.Listener.onPlayerError(PlaybackException) и Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).
В этом случае состояние воспроизведения меняется на STATE_IDLE, а MediaController.getPlayerError() возвращает PlaybackException, которое вызвало переход. Контроллер может проверить PlaybackException.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).
Настройка критических ошибок
Чтобы предоставить пользователю локализованную и понятную информацию, вы можете настроить код ошибки, сообщение об ошибке и дополнительные сведения о критической ошибке воспроизведения, поступающие от фактического проигрывателя. Для этого при создании сеанса используйте ForwardingSimpleBasePlayer:
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
Подкласс ForwardingSimpleBasePlayer может переопределить getState(), чтобы перехватить ошибку и настроить ее код, сообщение или дополнительные параметры. Аналогичным образом можно генерировать новые ошибки, которых нет в исходном проигрывателе:
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. } });