Transmisión de contenido multimedia con ExoPlayer

1. Antes de comenzar

526b239733391e74.png

Captura de pantalla: App de YouTube para Android

ExoPlayer es un reproductor multimedia de nivel de app integrado sobre las API multimedia de bajo nivel en Android. ExoPlayer tiene varias ventajas sobre el MediaPlayer integrado en Android. Admite muchos de los formatos multimedia que admite MediaPlayer, además de formatos adaptables, como DASH y SmoothStreaming. ExoPlayer es extensible y se puede personalizar mucho, lo que le permite manejar varios casos de uso avanzados. Es un proyecto de código abierto que utilizan las apps de Google, incluidas YouTube y Google Play Películas.

Requisitos previos

  • Conocimientos moderados del desarrollo de Android y Android Studio

Actividades

  • Crearás una instancia de SimpleExoPlayer, que prepara y reproduce contenido multimedia de una variedad de fuentes.
  • Integrarás ExoPlayer con el ciclo de vida de la app con el fin de admitir la reproducción en segundo plano y en primer plano, y la reanudación de la reproducción en un entorno de una o varias ventanas.
  • Usarás MediaItem para crear una lista de reproducción.
  • Reproducirás transmisiones de videos adaptativos por Internet que ajustan la calidad del contenido multimedia al ancho de banda disponible.
  • Registrarás objetos de escucha de eventos para supervisar el estado de la reproducción y mostrarás de qué manera se pueden usar esos objetos para medir la calidad de la reproducción.
  • Usarás los componentes estándar de la IU de ExoPlayer y, luego, los personalizarás según el estilo de tu app.

Requisitos

  • La versión estable más reciente de Android Studio
  • Un dispositivo Android con JellyBean (4.1) o versiones posteriores, idealmente, con Nougat (7.1) o una versión posterior, ya que admite varias ventanas

2. Prepárate

Obtén el código

Para comenzar, descarga el proyecto de Android Studio:

Descargar Zip

De manera alternativa, puedes clonar el repositorio de GitHub:

git clone https://github.com/googlecodelabs/exoplayer-intro.git

Estructura del directorio

Al clonar o descomprimir, verás una carpeta raíz (exoplayer-intro), que contiene un solo proyecto de Gradle con varios módulos; un módulo de app y uno por cada paso de este codelab, junto con todos los recursos que necesitarás.

Importa el proyecto

  1. Inicia Android Studio.
  2. Selecciona File > New > Import Project*.*
  3. Selecciona el archivo raíz build.gradle.

111b190903697765.png

Captura de pantalla: Estructura del proyecto al importar

Una vez finalizada la compilación, verás seis módulos: el módulo app (del tipo aplicación) y cinco módulos con nombres exoplayer-codelab-N (donde N es de 00 a 04,, cada uno un tipo de biblioteca). El módulo app está vacío, solo tiene un manifiesto. Todo o del módulo exoplayer-codelab-N especificado se combina cuando la app se compila con una dependencia de Gradle en app/build.gradle.

app/build.gradle

dependencies {
   implementation project(":exoplayer-codelab-00")
}

El elemento Activity de tu reproductor multimedia se conserva en el módulo exoplayer-codelab-N. Se conserva en un módulo de biblioteca separado para que puedas compartirlo con los APK que se orientan a diferentes plataformas, por ejemplo, un dispositivo móvil y Android TV. También puedes aprovechar funciones como Dynamic Delivery, que permite que la función del reproductor multimedia se instale solo cuando el usuario lo necesita.

  1. Implementa y ejecuta la app para comprobar que todo esté bien. La app debería completar la pantalla con un fondo negro.

2dae13fed92e6c8c.png

Captura de pantalla: App vacía en ejecución

3. Transmite

Agrega una dependencia de ExoPlayer

ExoPlayer es un proyecto de código abierto alojado en GitHub. Cada actualización se distribuye a través de Maven de Google, uno de los repositorios de paquetes predeterminados que utilizan Android Studio y Gradle. Cada actualización se identifica de forma única mediante una string con el siguiente formato:

com.google.android.exoplayer:exoplayer:X.X.X

Para agregar ExoPlayer a tu proyecto, solo importa sus clases y componentes de IU. Es bastante pequeño, con una huella reducida de alrededor de 70 a 300 kB, según las funciones incluidas y los formatos compatibles. La biblioteca de ExoPlayer se divide en módulos para permitir que los desarrolladores importen solo la funcionalidad que necesitan. Para obtener más información sobre la estructura modular de ExoPlayer, consulta Cómo agregar módulos de ExoPlayer.

  1. Abre el archivo build.gradle del módulo player-lib.
  2. Agrega las siguientes líneas a la sección dependencies y sincroniza el proyecto.

exoplayer-codelab-00/build.gradle

dependencies {
   [...]

implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'

}

Agrega PlayerView element.

  1. Abre el archivo de recurso de diseño activity_player.xml desde el módulo exoplayer-codelab-00.
  2. Coloca el cursor dentro del elemento FrameLayout.
  3. Comienza escribiendo <PlayerView y permite que Android Studio complete automáticamente el elemento PlayerView.
  4. Usa match_parent para width y height.
  5. Declara la ID como video_view.

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

De acá en más, te referirás a este elemento de la IU como la vista de video.

  1. Ahora, en el elemento PlayerActivity, puedes obtener una referencia al árbol de vistas creado a partir del archivo en formato XML que acabas de editar.

PlayerActivity.kt

    private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
        ActivityPlayerBinding.inflate(layoutInflater)
    }
  1. Configura la raíz del árbol de vista como la vista de contenido de Activity. Además, comprueba que la propiedad videoView se pueda ver en tu referencia viewBinding y que su tipo sea PlayerView.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)
    }

Crea un ExoPlayer

Para reproducir contenido multimedia, necesitas un objeto ExoPlayer. La forma más sencilla de crear uno es usar la clase SimpleExoPlayer.Builder. Como el nombre lo sugiere, usa el patrón del compilador para compilar una instancia de SimpleExoPlayer.

SimpleExoPlayer es una implementación conveniente para muchos fines de la interfaz ExoPlayer.

Agrega un método privado initializePlayer para crear tu SimpleExoPlayer.

PlayerActivity.kt

private var player: SimpleExoPlayer? = null
[...]
   private fun initializePlayer() {
        player = SimpleExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
            }
    }

Usa tu contexto para crear un elemento SimpleExoPlayer.Builder y llama a build para crear tu objeto SimpleExoPlayer. Luego, este se asigna a player, que debes declarar como un campo del miembro. Por último, usa la propiedad mutable viewBinding.videoView.player para vincular player a su vista correspondiente.

Crea un elemento multimedia

Ahora, player necesita contenido para reproducir. Para eso, crea un MediaItem. Existen muchos tipos diferentes de MediaItem, pero comienza creando uno para un archivo MP3 en Internet.

La manera más simple para crear un MediaItem es usar MediaItem.fromUri, que acepta el URI de un archivo multimedia. Agrega MediaItem a player con player.setMediaItem.

  1. Agrega el siguiente código a initializePlayer dentro del bloque also:

PlayerActivity.kt

private fun initializePlayer() {
    [...]
        .also { exoPlayer ->
            [...]
            val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
            exoPlayer.setMediaItem(mediaItem)
        }
}

Ten en cuenta que R.string.media_url_mp3 se define como https://storage.googleapis.com/exoplayer-test-media-0/play.mp3 en strings.xml.

Funciona bien con el ciclo de vida de la actividad

Nuestro player puede acaparar muchos recursos, entre los que se incluyen la memoria, la CPU, las conexiones de red y los códecs de hardware. Muchos de estos recursos escasean, en especial, los códecs de hardware donde puede haber solo uno. Es importante que liberes esos recursos para que otras apps los usen cuando no los estás usando, por ejemplo, cuando tu app se coloca en segundo plano.

En otras palabras, el ciclo de vida de tu reproductor debe estar vinculado al ciclo de vida de tu app. Para implementarlo, debes anular los cuatro métodos de PlayerActivity: onStart, onResume, onPause y onStop.

  1. Con PlayerActivity abierto, haz clic en Code menu > Override methods…
  2. Selecciona onStart, onResume, onPause y onStop.
  3. Inicializa el reproductor en la devolución de llamada onStart o onResume según el nivel de API.

PlayerActivity.kt

public override fun onStart() {
 super.onStart()
 if (Util.SDK_INT >= 24) {
   initializePlayer()
 }
}

public override fun onResume() {
 super.onResume()
 hideSystemUi()
 if ((Util.SDK_INT < 24 || player == null)) {
   initializePlayer()
 }
}

A partir del nivel de API 24 de Android, se admiten varias ventanas. Como tu app puede estar visible, pero no activa en el modo de pantalla dividida, debes inicializar el reproductor en onStart. El nivel de API 24 y versiones anteriores de Android requieren que esperes la mayor cantidad de tiempo posible hasta que obtengas recursos, por lo que debes esperar hasta onResume antes de inicializar el reproductor.

  1. Agrega el método hideSystemUi.

PlayerActivity.kt

@SuppressLint("InlinedApi")
private fun hideSystemUi() {
 viewBinding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
     or View.SYSTEM_UI_FLAG_FULLSCREEN
     or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
     or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
     or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
     or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}

hideSystemUi es un método de ayuda al que se llama en onResume, que te permite tener una experiencia en toda la pantalla.

  1. Libera los recursos con releasePlayer (que pronto crearás) en onPause y onStop.

PlayerActivity.kt

public override fun onPause() {
 super.onPause()
 if (Util.SDK_INT < 24) {
   releasePlayer()
 }
}

public override fun onStop() {
 super.onStop()
 if (Util.SDK_INT >= 24) {
   releasePlayer()
 }
}

Con el nivel de API 24 y versiones anteriores, no se garantiza que se llame a onStop, por lo que tienes que liberar al reproductor lo antes posible en onPause. A partir del nivel de API 24 (que incorporó el modo multiventana y de ventana dividida), se garantiza la llamada a onStop. En el estado pausado, tu actividad sigue siendo visible, por lo que debes esperar para liberar el reproductor hasta onStop.

Ahora, debes crear un método releasePlayer, que libera los recursos del reproductor y lo destruye.

  1. Agrega el siguiente código a la actividad:

PlayerActivity.kt

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
[...]

private fun releasePlayer() {
    player?.run {
        playbackPosition = this.currentPosition
        currentWindow = this.currentWindowIndex
        playWhenReady = this.playWhenReady
        release()
    }
    player = null
}

Antes de liberar y destruir el reproductor, almacena la siguiente información:

  • Estado de reproducción/pausa con playWhenReady
  • Posición actual de la reproducción con currentPosition
  • Índice de ventana actual con currentWindowIndex Para obtener más información sobre las ventanas, consulta Cronograma.

Esto te permite reanudar la reproducción desde donde el usuario la dejó. Lo único que debes hacer es reemplazar la información de estado cuando inicialices el reproductor.

Preparación final

Todo lo que necesitas hacer ahora es proporcionar la información de estado que guardaste en releasePlayer a tu reproductor durante la inicialización.

  1. Agrega lo siguiente a initializePlayer:

PlayerActivity.kt

private fun initializePlayer() {
    [...]
    exoPlayer.playWhenReady = playWhenReady
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.prepare()
}

Esto es lo que sucede:

  • playWhenReady indica al reproductor si debe comenzar a reproducir contenido en cuanto se hayan adquirido todos los recursos para la reproducción. Como, en un principio, playWhenReady es true, la reproducción se inicia automáticamente la primera vez que se ejecuta la app.
  • seekTo indica al reproductor que debe buscar una determinada posición dentro de una ventana específica. Tanto currentWindow como playbackPosition se inicializan en cero, por lo que la reproducción comienza desde el inicio la primera vez que se ejecuta la app.
  • prepare indica al reproductor que debe adquirir todos los recursos necesarios para la reproducción.

Reproduce audio

Ya está todo listo. Inicia la app para que reproduzca el archivo MP3 y observa el material gráfico integrado.

d92917867ee23ef8.png

Captura de pantalla: La app reproduciendo una sola pista.

Prueba el ciclo de vida de la actividad

Prueba si la app funciona en todos los estados posibles del ciclo de vida de la actividad.

  1. Inicia otra app y vuelve a poner tu app en primer plano. ¿Se reanuda en la posición correcta?
  2. Pausa la app, pásala a segundo plano y, luego, a primer plano otra vez. ¿Sigue en estado de pausa cuando pasa a segundo plano en ese estado?
  3. Rota la app. ¿Cómo se comporta si cambias la orientación de vertical a horizontal y de horizontal a vertical?

Reproduce video

Si quieres reproducir un video, es tan simple como modificar el URI del elemento multimedia a un archivo MP4.

  1. Cambia el URI de initializePlayer a R.string.media_url_mp4.
  2. Vuelve a iniciar la app y prueba el comportamiento luego de que se la coloca en segundo plano con video.

PlayerActivity.kt

private fun initializePlayer() {
  [...]
     val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
  [...]
}

El elemento PlayerView hace todo. En la pantalla completa, se puede ver el video, en lugar del material gráfico.

425c6c65f78e8d46.png

Captura de pantalla: La app reproduciendo video.

¡Así se hace! Acabas de crear una app para transmitir contenido multimedia en la pantalla completa en Android, con administración del ciclo de vida, estado de guardado y controles de IU.

4. Crea una lista de reproducción

Tu app actual reproduce un solo archivo multimedia, pero ¿qué sucede si deseas reproducir más de un archivo multimedia, uno tras otro? Para eso, necesitas una lista de reproducción.

Las lista de reproducción se pueden crear si agregas más MediaItem a player mediante addMediaItem. Esto permite una reproducción fluida, y el almacenamiento en búfer se controla en segundo plano para que el usuario no vea un ícono giratorio de almacenamiento en búfer cuando cambia los elementos multimedia.

  1. Agrega el siguiente código a initializePlayer:

PlayerActivity.kt

private void initializePlayer() {
  [...]
  exoPlayer.addMediaItem(mediaItem) // Existing code

  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
  exoPlayer.addMediaItem(secondMediaItem);
  [...]
}

Verifica cómo se comportan los controles del reproductor. Puedes usar 1f79fee4d082870f.png y 39627002c03ce320.png para navegar por la secuencia de los elementos multimedia.

7b5c034dafabe1bd.png

Captura de pantalla: Los controles de reproducción que muestran el botón Siguiente y Anterior

¡Es bastante útil! Para obtener más información, consulta la documentación para desarrolladores sobre elementos multimedia y listas de reproducción, y este artículo sobre la API de lista de reproducción.

5. Transmisión adaptable

La transmisión adaptable es una técnica para transmitir contenido multimedia mediante la variación de la calidad de la transmisión, en función del ancho de banda de red disponible. De esta manera, se le permite al usuario experimentar el contenido con la mejor calidad que permite su ancho de banda.

Por lo general, el mismo contenido multimedia se divide en varias pistas con diferentes calidades (tasas de bits y resoluciones). El reproductor elige una pista en función del ancho de banda de red disponible.

Cada pista se divide en fragmentos de una duración determinada, por lo general, entre 2 y 10 segundos. De esta manera, se le permite al reproductor cambiar con rapidez entre pistas, a medida que cambia el ancho de banda disponible. El reproductor se encarga de unir estos fragmentos para una reproducción perfecta.

Selección de pista adaptable

En el núcleo de la transmisión adaptable, se encuentra la selección de la pista más apropiada para el entorno actual. Actualiza tu app para reproducir contenido multimedia de transmisión adaptable mediante la selección de pista adaptable.

  1. Actualiza initializePlayer con el siguiente código:

PlayerActivity.kt

private fun initializePlayer() {
   val trackSelector = DefaultTrackSelector(this).apply {
        setParameters(buildUponParameters().setMaxVideoSizeSd())
    }
   player = SimpleExoPlayer.Builder(this)
        .setTrackSelector(trackSelector)
        .build()
  [...]
}

Primero, crea un DefaultTrackSelector, que se encarga de elegir pistas en el elemento multimedia. Luego, indícale a trackSelector que solo elija pistas de definición estándar o inferior (una buena manera de guardar los datos del usuario a costa de la calidad). Por último, pásale trackSelector al compilador para que se use en la compilación de la instancia de SimpleExoPlayer.

Compila un elemento MediaItem

DASH es un formato de transmisión adaptable que se usa mucho. Para transmitir contenido DASH, tendrás que crear un elemento MediaItem como lo hiciste antes. Sin embargo, esta vez, tenemos que usar un MediaItem.Builder en lugar de fromUri.

Esto se debe a que fromUri usa la extensión del archivo para determinar el formato multimedia subyacente, pero nuestro URI de DASH no tiene una extensión de archivo, por lo que debemos proporcionar un tipo de MIME de APPLICATION_MPD cuando se construye el MediaItem.

  1. Actualiza initializePlayer de la siguiente manera:

PlayerActivity.kt

private void initializePlayer() {
  [...]

  // Replace this line
  val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));

  // With this
   val mediaItem = MediaItem.Builder()
        .setUri(getString(R.string.media_url_dash))
        .setMimeType(MimeTypes.APPLICATION_MPD)
        .build()

  // Also remove the following lines
  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
    exoPlayer.addMediaItem(secondMediaItem)
}
  1. Reinicia la app y observa la transmisión de videos adaptable con DASH en funcionamiento. Con ExoPlayer, es muy sencillo.

Otros formatos de transmisión adaptables

HLS (MimeTypes.APPLICATION_M3U8) y SmoothStreaming (MimeTypes.APPLICATION_SS) son otros formatos de transmisión adaptables de uso general, los cuales son compatibles con ExoPlayer. Para obtener más información sobre la construcción de otras fuentes multimedia adaptables, consulte la app de demostración ExoPlayer.

6. Escucha eventos

En los pasos anteriores, aprendiste a realizar transmisiones multimedia progresivas y adaptables. ExoPlayer hace mucho trabajo detrás de escena para ti, lo que incluye lo siguiente:

  • Asignar memoria
  • Descargar archivos del contenedor
  • Extraer metadatos del contenedor
  • Decodificar datos
  • Renderizar video, audio y texto en la pantalla y los altoparlantes

Con frecuencia, es útil saber qué hace ExoPlayer en el tiempo de ejecución para comprender y mejorar la experiencia de reproducción de tus usuarios.

Por ejemplo, es posible que desees reflejar los cambios de estado de reproducción en la interfaz de usuario mediante lo siguiente:

  • Un ícono giratorio de carga cuando el reproductor entra en un estado de almacenamiento en búfer
  • Una superposición con opciones de "ver a continuación" cuando termina la pista

ExoPlayer ofrece varias interfaces de objetos de escucha que brindan devoluciones de llamada para eventos útiles. Utiliza un objeto de escucha para registrar el estado en el que se encuentra el reproductor.

Escucha

  1. Crea una constante TAG fuera de la clase PlayerActivity, que usarás para iniciar sesión más tarde.

PlayerActivity.kt

private const val TAG = "PlayerActivity"
  1. Implementa la interfaz Player.EventListener en una función de fábrica fuera de la clase PlayerActivity. Se usa para informarte sobre eventos importantes del reproductor, incluidos errores y cambios en el estado de reproducción.
  2. Para anular onPlaybackStateChanged, agrega el siguiente código:

PlayerActivity.kt

private fun playbackStateListener() = object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        val stateString: String = when (playbackState) {
            ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
            ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString")
    }
}
  1. Declara un miembro privado de tipo Player.EventListener en PlayerActivity.

PlayerActivity.kt

class PlayerActivity : AppCompatActivity() {
    [...]

    private val playbackStateListener: Player.EventListener = playbackStateListener()
}

onPlaybackStateChanged se llama cuando cambia el estado de reproducción. El nuevo estado que brinda el parámetro playbackState.

El reproductor puede estar en uno de los siguientes cuatro estados:

Estado

Descripción

ExoPlayer.STATE_IDLE

Se crea una instancia del reproductor, pero aún no está preparado.

ExoPlayer.STATE_BUFFERING

El reproductor no puede reproducir desde la posición actual porque no se almacenaron suficientes datos en el búfer.

ExoPlayer.STATE_READY

El reproductor puede reproducir de inmediato desde la posición actual. El reproductor comenzará a reproducir medios automáticamente si la propiedad playWhenReady del reproductor es true. Si es false, el reproductor está en pausa.

ExoPlayer.STATE_ENDED

El reproductor terminó de reproducir el contenido multimedia.

Registra tu objeto de escucha

Para que se realicen devoluciones de llamadas, debes registrar playbackStateListener con el reproductor. Puedes hacerlo en initializePlayer.

  1. Registra el objeto de escucha antes de preparar el reproductor.

PlayerActivity.kt

private void initializePlayer() {
    [...]
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.addListener(playbackStateListener)
    [...]
}

Nuevamente, debes ordenar para evitar referencias aisladas del reproductor que podrían causar una fuga de memoria.

  1. Quita el objeto de escucha en releasePlayer:

PlayerActivity.kt

private void releasePlayer() {
 player?.run {
   [...]
   removeListener(playbackStateListener)
   release()
 }
  player = null
}
  1. Abre logcat y ejecuta la app.
  2. Utiliza los controles de la UI para buscar, pausar y reanudar la reproducción. Deberías ver el cambio de estado de reproducción en los registros.

Más información

ExoPlayer ofrece una serie de otros objetos de escucha, que son útiles para comprender la experiencia de reproducción del usuario. Hay objetos de escucha para audio y video, así como un AnalyticsListener, que incluye las devoluciones de llamada de todos los objetos de escucha. Algunos de los métodos más importantes son los siguientes:

  • onRenderedFirstFrame se llama cuando se renderiza el primer fotograma de un video. Con esto, puedes calcular cuánto tiempo tuvo que esperar el usuario para ver contenido significativo en la pantalla.
  • onDroppedVideoFrames se llama cuando disminuyen los fotogramas de video. La disminución de fotogramas indica que la reproducción tenga bloqueos, y es probable que la experiencia del usuario sea deficiente.
  • onAudioUnderrun se llama cuando se produce un subdesbordamiento de audio. Los subdesbordamiento provocan errores audibles en el sonido y son más notorios que la disminución de los fotogramas de video.

AnalyticsListener se puede agregar a player con addAnalyticsListener. También existen métodos correspondientes para los objetos de escucha de audio y video.

Piensa en los eventos que son importantes para tus usuarios y app. Para obtener más información, consulta Cómo escuchar eventos del reproductor. ¡Eso es todo sobre los objetos de escucha de eventos!

7. Personaliza la interfaz de usuario

Hasta ahora, usaste PlayerControlView de ExoPlayer para mostrarle un controlador de reproducción al usuario.

bcfe17eebcad9e13.png

Captura de pantalla: Controlador de reproducción predeterminado

¿Qué sucede si deseas cambiar la funcionalidad o la apariencia de estos controles? Afortunadamente, estos controles se pueden personalizar mucho.

La primera personalización simple es no usar el controlador en absoluto. Se puede hacer con facilidad mediante el atributo use_controller en el elemento PlayerView dentro de activity_player.xml.

  1. Configura use_controller en false y el control ya no aparecerá:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   [...]
   app:use_controller="false"/>
  1. Agrega el siguiente espacio de nombres a FrameLayout:

activity_player.xml

<FrameLayout
  [...]
  xmlns:app="http://schemas.android.com/apk/res-auto">

Pruébalo ahora.

Personaliza el comportamiento

PlayerControlView tiene varios atributos que afectan su comportamiento. Por ejemplo, puedes usar show_timeout para personalizar el retraso en milisegundos antes de que el control se oculte después de que el usuario interactuó con este por última vez. Para ello, haz lo siguiente:

  1. Quita app:use_controller="false".
  2. Cambia la vista del reproductor para usar show_timeout:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:show_timeout="10000"/>

Los atributos de PlayerControlView también se pueden configurar de manera programática.

Personaliza la apariencia

Es un buen comienzo. Pero, ¿qué sucede si deseas tener el aspecto PlayerControlView diferente o cambiar los botones que se muestran? La implementación de PlayerControlView no asume que exista ningún botón, por lo que es fácil quitarlos y agregar otros nuevos.

Veamos cómo puedes personalizar PlayerControlView.

  1. Crea un nuevo archivo de diseño custom_player_control_view.xml en la carpeta player-lib/res/layout/.
  2. En el menú contextual de la carpeta de diseño, elige New - Layout resource file y asígnale el nombre custom_player_control_view.xml.

ae1e3795726d4e4e.png

Captura de pantalla: Se creó el archivo de diseño para la vista de control del reproductor.

  1. Copia el archivo de diseño original desde aquí hasta custom_player_control_view.xml.
  2. Quita los elementos ImageButton con el ID @id/exo_prev y @id/exo_next.

Para usar tu diseño personalizado, debes configurar el atributo app:controller_layout_id del elemento PlayerView en el archivo activity_player.xml.

  1. Utiliza el ID de diseño de tu archivo personalizado como en el siguiente fragmento de código:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:controller_layout_id="@layout/custom_player_control_view"/>
  1. Vuelve a iniciar la app. La vista de control del reproductor ya no tiene los botones Anterior y Siguiente.

89e6535a22c8e321.png

Captura de pantalla: Vista de control del reproductor personalizado sin botones Anterior o Siguiente

Puedes aplicar los cambios que desees en el archivo de diseño. De forma predeterminada, se eligen los colores del tema de Android. Puedes anular esto para que coincida con el diseño de tu app.

  1. Agrega un atributo android:tint a cada elemento ImageButton:

custom_player_control_view.xml

<ImageButton android:id="@id/exo_rew"
   android:tint="#FF00A6FF"
   style="@style/ExoMediaButton.Rewind"/>
  1. Cambia todos los atributos android:textColor que encuentres en tu archivo personalizado al mismo color: #FF00A6FF.

custom_player_control_view.xml

<TextView android:id="@id/exo_position"
   [...]
   android:textColor="#FF00A6FF"/>
<TextView android:id="@id/exo_duration"
   [...]
   android:textColor="#FF00A6FF"/>
  1. Ejecuta la app. Ahora tiene hermosos componentes de la IU de colores.

e9835d65d6dd0634.png

Captura de pantalla: Vista de texto y botones de colores

Anula el estilo predeterminado

Acabas de crear un archivo de diseño personalizado y le hiciste referencia con controller_layout_id en activity_player.xml.

Otro enfoque es anular el archivo de diseño predeterminado que utiliza PlayerControlView. El código fuente de PlayerControlView nos indica que usa R.layout.exo_player_control_view para el diseño. Si creas nuestro propio archivo de diseño con el mismo nombre de archivo, PlayerControlView usa tu archivo en su lugar.

  1. Quita el atributo controller_layout_id que acabas de agregar.
  2. Borra el archivo custom_player_control_view.xml.

PlayerView en activity_player.xml verse de la siguiente manera:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  1. Crea un archivo con el nombre exo_player_control_view.xml en la carpeta res/layout de tu módulo de biblioteca player-lib.
  2. Inserta el siguiente código en exo_player_control_view.xml para agregar un botón de reproducción, un botón de pausa y un ImageView con un logotipo:

exo_player**_control_view.xml**

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="bottom"
   android:layoutDirection="ltr"
   android:background="#CC000000"
   android:orientation="vertical">

 <LinearLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:paddingTop="4dp"
   android:orientation="horizontal">

   <ImageButton android:id="@id/exo_play"
      style="@style/ExoMediaButton.Play"/>

   <ImageButton android:id="@id/exo_pause"
      style="@style/ExoMediaButton.Pause"/>

 </LinearLayout>

 <ImageView
     android:contentDescription="@string/logo"
     android:src="@drawable/google_logo"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"/>

</LinearLayout>

De esta manera, se demuestra cómo puedes agregar tus propios elementos aquí y combinarlos con elementos de control estándar. ExoPlayerView ahora usa tu control personalizado, y se conserva toda la lógica para ocultar y mostrar cuando interactúa con el control.

8. Felicitaciones

¡Felicitaciones! Aprendiste mucho sobre la integración de ExoPlayer con tu app.

Más información

Para obtener más información sobre ExoPlayer, consulta la guía para desarrolladores y el código fuente, y suscríbete al blog de ExoPlayer.