Las sesiones multimedia proporcionan una forma universal de interactuar con un reproductor de audio o video. En Media3, el reproductor predeterminado es la clase ExoPlayer, que implementa la interfaz Player. Conectar la sesión multimedia al reproductor permite que una app anuncie la reproducción de contenido multimedia de forma externa y reciba comandos de reproducción de fuentes externas.
Los comandos pueden originarse en botones físicos, como el botón de reproducción de un auricular o un control remoto de TV. También pueden provenir de apps cliente que tienen un controlador de contenido multimedia, como indicar "pausar" a asistentes de IA, incluidos Gemini o el Asistente de Google. La sesión multimedia delega estos comandos al reproductor de la app de contenido multimedia.
Cuándo elegir una sesión multimedia
Cuando implementas MediaSession, permites que los usuarios controlen la reproducción:
- A través de sus auriculares A menudo, hay botones o interacciones táctiles que un usuario puede realizar en sus auriculares para reproducir o pausar contenido multimedia, o bien ir a la pista siguiente o anterior.
- Hablando con asistentes de IA, como Gemini o Asistente de Google Un patrón común es decir "Hey Google, pausa" para pausar cualquier contenido multimedia que se esté reproduciendo en el dispositivo.
- A través de su reloj Wear OS Esto permite acceder con más facilidad a los controles de reproducción más comunes mientras se reproduce contenido en el teléfono.
- A través de los controles multimedia Este carrusel muestra los controles de cada sesión multimedia en curso.
- En la TV Permite acciones con botones de reproducción físicos, control de reproducción de la plataforma y administración de energía (por ejemplo, si la TV, la barra de sonido o el receptor de audio y video se apagan o se cambia la entrada, la reproducción debería detenerse en la app).
- A través de los controles multimedia de Android Auto Esto permite controlar la reproducción de forma segura mientras se conduce.
- Cualquier otro proceso externo que deba influir en la reproducción
Esto es excelente para muchos casos de uso. En particular, deberías considerar seriamente
usar MediaSession en los siguientes casos:
- Transmitirás contenido de video de formato largo, como películas o TV en vivo.
- Estás transmitiendo contenido de audio de formato largo, como podcasts o playlists de música.
- Estás creando una app para TVs.
Sin embargo, no todos los casos de uso se adaptan bien a la MediaSession. Es posible que quieras
usar solo el Player en los siguientes casos:
- Muestras contenido de formato corto, en el que no se necesita control externo ni reproducción en segundo plano.
- No hay un solo video activo, por ejemplo, el usuario se desplaza por una lista y se muestran varios videos en la pantalla al mismo tiempo.
- Estás reproduciendo un video de introducción o video explicativo único, que esperas que el usuario mire de forma activa sin necesidad de controles de reproducción externos.
- Tu contenido es sensible a la privacidad y no quieres que los procesos externos accedan a los metadatos de contenido multimedia (por ejemplo, el modo Incógnito en un navegador).
Si tu caso de uso no se ajusta a ninguno de los que se mencionan más arriba, considera si te parece bien que la app continúe con la reproducción cuando el usuario no interactúe de forma activa con el contenido. Si la respuesta es afirmativa, es probable que quieras elegir
MediaSession. Si la respuesta es no, es probable que quieras usar Player en su lugar.
Crea una sesión multimedia
Una sesión multimedia existe junto al reproductor que administra. Puedes construir una sesión multimedia con un objeto Context y un objeto Player. Debes crear e inicializar una sesión multimedia cuando sea necesario, como el método de ciclo de vida onStart() o onResume() de Activity o Fragment, o el método onCreate() de Service que posee la sesión multimedia y su reproductor asociado.
Para crear una sesión multimedia, inicializa un Player y proporciónalo a MediaSession.Builder de la siguiente manera:
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();
Manejo automático de estados
La biblioteca de Media3 actualiza automáticamente la sesión multimedia con el estado del reproductor. Por lo tanto, no es necesario que manejes manualmente la asignación del reproductor a la sesión.
Esto es diferente de la sesión multimedia de la plataforma, en la que necesitabas crear y mantener un PlaybackState de forma independiente del reproductor, por ejemplo, para indicar cualquier error.
ID de sesión único
De forma predeterminada, MediaSession.Builder crea una sesión con una cadena vacía como
ID de sesión. Esto es suficiente si una app solo pretende crear una instancia de sesión, que es el caso más común.
Si una app quiere administrar varias instancias de sesión al mismo tiempo, debe asegurarse de que el ID de cada sesión sea único. El ID de sesión se puede
establecer cuando se compila la sesión con MediaSession.Builder.setId(String id).
Si ves que un IllegalStateException provoca que tu app falle con el mensaje de error IllegalStateException: Session ID must be unique. ID=, es probable que se haya creado una sesión de forma inesperada antes de que se haya lanzado una instancia creada anteriormente con el mismo ID. Para evitar que se filtren sesiones debido a un
error de programación, estos casos se detectan y se notifican arrojando una
excepción.
Cómo otorgar control a otros clientes
La sesión multimedia es la clave para controlar la reproducción. Te permite enrutar comandos de fuentes externas al reproductor que se encarga de reproducir tu contenido multimedia. Estas fuentes pueden ser botones físicos, como el botón de reproducción de unos auriculares o un control remoto de TV, o comandos indirectos, como indicarle a Gemini o al Asistente de Google que "pause" la reproducción. Del mismo modo, es posible que quieras otorgar acceso al sistema Android para los controles de notificaciones y de la pantalla de bloqueo, o a un reloj Wear OS para que puedas controlar la reproducción desde la cara de reloj. Los clientes externos pueden usar un controlador multimedia para enviar comandos de reproducción a tu app de contenido multimedia. Tu sesión multimedia recibe estos comandos y los delega al reproductor multimedia.
Cuando un controlador está a punto de conectarse a tu sesión multimedia, la sesión llama a onConnectAsync(). Puedes usar el ControllerInfo proporcionado para decidir si aceptas o rechazas la solicitud. Para ver un
ejemplo de cómo aceptar una solicitud de conexión, consulta
Declara y controla comandos personalizados.
Después de conectarse, un controlador puede enviar comandos de reproducción a la sesión. Luego, la sesión delega esos comandos al reproductor. La sesión controla automáticamente los comandos de reproducción y de playlist definidos en la interfaz Player.
Otros métodos de devolución de llamada te permiten controlar, por ejemplo, las solicitudes de comandos personalizados y la modificación de la lista de reproducción. Estas devoluciones de llamada también incluyen un objeto ControllerInfo para que puedas modificar la forma en que respondes a cada solicitud por controlador.
Modificar la playlist
Una sesión multimedia puede modificar directamente la lista de reproducción de su reproductor, como se explica en la guía de ExoPlayer para listas de reproducción.
Los controladores también pueden modificar la playlist si COMMAND_SET_MEDIA_ITEM o COMMAND_CHANGE_MEDIA_ITEMS están disponibles para el controlador.
Cuando se agregan elementos nuevos a la playlist, el reproductor suele requerir instancias de MediaItem con un URI definido para que se puedan reproducir. De forma predeterminada, los elementos recién agregados se reenvían automáticamente a los métodos del reproductor, como player.addMediaItem, si tienen un URI definido.
Si quieres personalizar las instancias de MediaItem que se agregan al reproductor, puedes anular onAddMediaItems(). Este paso es necesario cuando quieres admitir controladores que solicitan contenido multimedia sin un URI definido. En cambio,
el MediaItem suele tener uno o más de los siguientes campos configurados para
describir el contenido multimedia solicitado:
MediaItem.mediaId: Es un ID genérico que identifica el contenido multimedia.MediaItem.RequestMetadata.mediaUri: Es un URI de solicitud que puede usar un esquema personalizado y que el reproductor no necesariamente puede reproducir de forma directa.MediaItem.RequestMetadata.searchQuery: Es una búsqueda de texto, por ejemplo, de Gemini o el Asistente de Google.MediaItem.mediaMetadata: Son metadatos estructurados, como "título" o "artista".
Para obtener más opciones de personalización de playlists completamente nuevas, también puedes anular onSetMediaItems(), que te permite definir el elemento de inicio y la posición en la playlist. Por ejemplo, puedes expandir
un solo elemento solicitado a una playlist completa y, luego, indicarle al reproductor que comience
en el índice del elemento solicitado originalmente. Para ver una
implementación de muestra de onSetMediaItems() con
esta función, consulta la app de demostración de sesiones.
Publicar metadatos de playlists
Media3 proporciona APIs para publicar metadatos estructurados de listas de reproducción junto con la reproducción de pistas activas. Estandarizar la forma en que tu app propaga la membresía de la playlist y los metadatos descriptivos en las sesiones multimedia permite que Android exponga puntos de entrada a nivel del sistema para la reproducción en tu app.
En este contexto, el término playlist se refiere a cualquier agrupación ordenada de pistas que se reproducirán en secuencia. Esto se aplica no solo a las listas de elementos reproducibles individuales definidas por el usuario o la app, sino también a las agrupaciones, como álbumes de música, episodios de podcasts y capítulos de audiolibros.
Declara el ID y los metadatos de la playlist
En Media3, la identificación de la playlist y los metadatos descriptivos de la playlist se dividen en dos ámbitos de MediaMetadata:
- ID de la playlist a nivel del elemento (
MediaItem.mediaMetadata.playlistId):- Marca un
MediaItemque se esté reproduciendo como parte de una playlist llamando aMediaMetadata.Builder.setPlaylistIden elMediaMetadatadel elemento, mientras queMediaItem.mediaIdidentifica de forma única la pista individual. - El
playlistIdes una cadena opaca definida por la app que identifica de forma única la playlist en todas las sesiones durante el tiempo que se pueda reanudar (por ejemplo,37i9dQZF1DX76Wlfdnj7AP) y no se debe volver a usar para una playlist diferente. Los controladores conectados no interpretan este ID, sino que lo almacenan y lo envían de vuelta a tu app para reanudar la playlist. - Cada pista que pertenece a la playlist tiene el mismo
playlistId. DejaplaylistIdsin configurar (null) para las pistas que no se reproducen desde un contexto de playlist, como una sola pista independiente.
- Marca un
- Metadatos de la playlist a nivel del reproductor (
Player.getPlaylistMetadata):- Completa una instancia de
MediaMetadatadedicada con los atributos descriptivos de la playlist (setTitle,setArtworkUriysetMediaType) y configúrala en el reproductor conPlayer.setPlaylistMetadata. - Cuando un controlador conectado detecta un
currentMediaItem.mediaMetadata.playlistIdno nulo, inspeccionaMediaController.getPlaylistMetadatapara recuperar el título y el URI de la imagen de la playlist. - Actualiza los metadatos de la playlist cada vez que cambie la playlist activa (por ejemplo, cuando el usuario inicie una playlist diferente) para que
getPlaylistMetadatasiempre describa la playlist a la que hace referencia elplaylistIddel elemento actual.
- Completa una instancia de
En el siguiente ejemplo, se muestra cómo publicar metadatos de la playlist en Player
y asociar una pista MediaItem con esa playlist:
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);
Cómo reanudar una playlist
Cuando un usuario selecciona una playlist para reanudar, el controlador se conecta a la sesión multimedia de tu app y envía un MediaItem de playlist cuyo mediaId se establece en el playlistId almacenado:
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();
Tu app expande la playlist MediaItem en sus pistas reproducibles dentro de MediaSession.Callback.onSetMediaItems (o onAddMediaItems) y vuelve a publicar playlistId y playlistMetadata, como se muestra en la sección anterior.
Administra las preferencias de los botones de contenido multimedia
Cada controlador, por ejemplo, la IU del sistema, Android Auto o Wear OS, puede tomar sus propias decisiones sobre qué botones mostrar al usuario. Para indicar qué controles de reproducción quieres exponer al usuario, puedes especificar las preferencias de los botones multimedia en MediaSession. Estas preferencias consisten en una lista ordenada de instancias de CommandButton, cada una de las cuales define una preferencia para un botón en la interfaz de usuario.
Define botones de comando
Las instancias de CommandButton se usan para definir las preferencias de los botones de contenido multimedia. Cada botón define tres aspectos del elemento de la IU deseado:
- El ícono, que define la apariencia visual El ícono se debe establecer en una de las constantes predefinidas cuando se crea un
CommandButton.Builder. Ten en cuenta que no se trata de un recurso de imagen o de mapa de bits real. Una constante genérica ayuda a los controladores a elegir un recurso adecuado para lograr un aspecto coherente dentro de su propia IU. Si ninguna de las constantes de íconos predefinidas se ajusta a tu caso de uso, puedes usarsetCustomIconResIden su lugar. - El comando, que define la acción que se activa cuando el usuario interactúa con el botón Puedes usar
setPlayerCommandpara unPlayer.CommandosetSessionCommandpara unSessionCommandpredefinido o personalizado. - La ranura, que define dónde se debe colocar el botón en la IU del controlador Este campo es opcional y se establece automáticamente según el Ícono y el Comando. Por ejemplo, permite especificar que se debe mostrar un botón en el área de navegación "adelante" de la IU en lugar del área predeterminada de "ampliación".
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();
Cuando se resuelven las preferencias de los botones de contenido multimedia, se aplica el siguiente algoritmo:
- Para cada
CommandButtonen las preferencias de botones multimedia, coloca el botón en el primer espacio disponible y permitido. - Si alguno de los espacios central, de avance y de retroceso no se completa con un botón, agrega botones predeterminados para este espacio.
Puedes usar CommandButton.DisplayConstraints para generar una vista previa de cómo
se resolverán las preferencias de los botones de contenido multimedia según las restricciones de visualización
de la IU.
Cómo configurar las preferencias de los botones de contenido multimedia
La forma más fácil de establecer las preferencias de los botones de contenido multimedia es definir la lista cuando
creas el MediaSession. Como alternativa, puedes anular MediaSession.Callback.onConnectAsync() para personalizar las preferencias de los botones multimedia de cada control conectado.
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();
Actualiza las preferencias de los botones multimedia después de una interacción del usuario
Después de controlar una interacción con el reproductor, es posible que quieras actualizar los botones que se muestran en la IU del controlador. Un ejemplo típico es un botón de activación que cambia su ícono y acción después de activar la acción asociada con este botón. Para actualizar las preferencias de los botones de contenido multimedia, puedes usar MediaSession.setMediaButtonPreferences para actualizar las preferencias de todos los controladores o de un controlador específico:
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));
Agrega comandos personalizados y personaliza el comportamiento predeterminado
Los comandos disponibles del reproductor se pueden extender con comandos personalizados, y también es posible interceptar los comandos entrantes del reproductor y los botones de contenido multimedia para cambiar el comportamiento predeterminado.
Declara y controla comandos personalizados
Las aplicaciones de música pueden definir comandos personalizados que, por ejemplo, se pueden usar en las preferencias de los botones de contenido multimedia. Por ejemplo, puedes implementar botones que permitan al usuario guardar un elemento multimedia en una lista de elementos favoritos. El MediaController envía comandos personalizados y el
MediaSession.Callback los recibe.
Para definir comandos personalizados, debes anular MediaSession.Callback.onConnectAsync() y establecer los comandos personalizados disponibles para cada controlador conectado.
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()); } }
Para recibir solicitudes de comandos personalizados de un MediaController, anula el método onCustomCommand() en el 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)); } }
Puedes hacer un seguimiento de qué controlador de contenido multimedia realiza una solicitud con la propiedad packageName del objeto MediaSession.ControllerInfo que se pasa a los métodos Callback. Esto te permite adaptar el comportamiento de tu app en respuesta a un comando determinado si se origina en el sistema, en tu propia app o en otras apps cliente.
Personaliza los comandos predeterminados del reproductor
Todos los comandos predeterminados y el control de estado se delegan al Player que se encuentra en el MediaSession. Para personalizar el comportamiento de un comando definido en la interfaz Player, como play() o seekToNext(), une tu Player en un ForwardingSimpleBasePlayer antes de pasarlo a 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();
Para obtener más información sobre ForwardingSimpleBasePlayer, consulta la guía de ExoPlayer sobre personalización.
Identifica el controlador que solicita un comando del reproductor
Cuando una llamada a un método Player se origina en un MediaController, puedes
identificar la fuente de origen con MediaSession.controllerForCurrentRequest
y adquirir el ControllerInfo para la solicitud actual:
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); } }
Personaliza el control de los botones de contenido multimedia
Los botones de contenido multimedia son botones de hardware que se encuentran en dispositivos Android y otros dispositivos periféricos, como el botón de reproducción/pausa de unos auriculares Bluetooth. Media3 controla los eventos de botones multimedia por ti cuando llegan a la sesión y llama al método Player adecuado en el reproductor de la sesión.
Se recomienda controlar todos los eventos de botones de contenido multimedia entrantes en el método Player correspondiente. Para casos de uso más avanzados, los eventos de botones de contenido multimedia se pueden interceptar en MediaSession.Callback.onMediaButtonEvent().
Manejo de errores y generación de informes
Existen dos tipos de errores que una sesión emite y, luego, informa a los controladores. Los errores graves informan una falla técnica de reproducción del reproductor de la sesión que interrumpe la reproducción. Los errores graves se informan al controlador automáticamente cuando ocurren. Los errores recuperables son errores no técnicos o de política que no interrumpen la reproducción y que la aplicación envía a los controladores de forma manual.
Errores de reproducción graves
El reproductor informa un error de reproducción irrecuperable a la sesión y, luego,
a los controladores para que llamen a través de
Player.Listener.onPlayerError(PlaybackException) y
Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).
En ese caso, el estado de reproducción cambia a STATE_IDLE y MediaController.getPlayerError() devuelve el PlaybackException que causó la transición. Un controlador puede inspeccionar el PlaybackException.errorCode para
obtener información sobre el motivo del error.
Cómo configurar un error de reproductor personalizado
Además de los errores fatales que informa el reproductor, una aplicación puede establecer un PlaybackException personalizado en el nivel de MediaSession con MediaSession.setPlaybackException(PlaybackException). Esto permite que la aplicación indique un estado de error a los controles conectados. La excepción se puede
establecer para todos los controles conectados o para un ControllerInfo específico.
Cuando una app establece un PlaybackException con esta API, ocurre lo siguiente:
Se notificará a las instancias de
MediaControllerconectadas. Se invocarán las devoluciones de llamadaListener.onPlayerError(PlaybackException)yListener.onPlayerErrorChanged(@Nullable PlaybackException)en el controlador con la excepción proporcionada.El método
MediaController.getPlayerError()devolverá elPlaybackExceptionque estableció la aplicación.El estado de reproducción de los controladores afectados cambiará a
Player.STATE_IDLE.Se quitarán los comandos disponibles y solo se dejarán los comandos de lectura, como
COMMAND_GET_TIMELINE, en caso de que ya se hayan otorgado. Por ejemplo, el estado deTimelinese congela al estado en el que se aplicó la excepción al controlador. Los comandos que intentan cambiar el estado del reproductor, comoCOMMAND_PLAY, se quitan hasta que la app quita la excepción de reproducción para el controlador determinado.
Para borrar un PlaybackException personalizado establecido anteriormente y restablecer los informes de estado normales del reproductor, una app puede llamar a MediaSession.setPlaybackException(/* playbackException= */ null) o MediaSession.setPlaybackException(ControllerInfo,
/* playbackException= */ null).
Personalización de errores irrecuperables
Para proporcionar información localizada y significativa al usuario, puedes personalizar el código de error, el mensaje de error y los extras de error de un error de reproducción irrecuperable que provenga del reproductor real. Para ello, usa un ForwardingSimpleBasePlayer cuando compiles la sesión:
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
Tu subclase ForwardingSimpleBasePlayer puede anular getState() para interceptar el error y personalizar el código de error, el mensaje o los extras. De la misma manera, también puedes generar errores nuevos que no existen en el reproductor original:
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); } }
Errores recuperables
Una app puede enviar errores recuperables que no se originan en una excepción técnica a todos los controladores o a uno específico:
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); }
Cuando se envía un error recuperable al controlador de notificaciones de contenido multimedia, el código y el mensaje de error se replican en la sesión multimedia de la plataforma, mientras que PlaybackState.state no se cambia a STATE_ERROR.
Recibe errores recuperables
Un MediaController recibe un error recuperable implementando 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. } });