Reproducción de medios en Android
La capacidad de reproducir contenido multimedia es una característica presente en la práctica totalidad de las terminales telefónicas existentes en el mercado hoy en día. Muchos usuarios prefieren utilizar las capacidades multimedia de su teléfono, en lugar de tener que depender de otro dispositivo adicional para ello.
En esta sesión vamos a aprender a añadir contenido multimedia en nuestras aplicaciones. En concreto, veremos cómo reproducir audio o video en una actividad.
La reproducción de contenido multimedia se lleva a cabo por medio de la clase MediaPlayer
. Dicha clase nos permite la reproducción de archivos multimedia almacenados como recursos de la aplicación, en ficheros locales, en proveedores de contenido, o servidos por medio de streaming a partir de una URL. En todos los casos, como desarrolladores, la clase MediaPlayer
nos permitirá abstraernos del formato así como del origen del fichero a reproducir.
Reproducción de audio
Tipos de fuentes de audio
Podemos reproducir audio proveniente de diferentes fuentes:
Un recurso de la aplicación
Un fichero en el dispositivo local
Una URL remota
Incluir un fichero de audio en los recursos de la aplicación para poder ser reproducido durante su ejecución es muy sencillo. Simplemente creamos una carpeta raw
dentro de la carpeta res
, y almacenamos en ella sin comprimir el fichero o ficheros que deseamos reproducir. A partir de ese momento el fichero se identificará dentro del código como R.raw.nombre_fichero
(obsérvese que no es necesario especificar la extensión del fichero).
Para reproducir un fichero de audio almacenado en el móvil o en un servidor remoto simplemente deberemos especificar su URL (local o remota).
Importante: En caso de necesitar reproducir un medio alojado en una URL remota, será necesario que la aplicación solicite el permiso
INTERNET
en el ficheroAndroidManifest.xml
:
Inicialización del reproductor de medios
Para reproducir un fichero de audio tendremos que seguir una secuencia de pasos. En primer lugar deberemos crear una instancia de la clase MediaPlayer
e indicar qué fichero será el que se reproducirá. Tenemos dos opciones para hacer esto.
La primera opción para inicializar la reproducción multimedia es por medio del método setDataSource
, el cual asigna una fuente multimedia a una instancia ya existente de la clase MediaPlayer
.
Al instanciar la clase MediaPlayer
se encontrará en estado idle. En este estado lo primero que debemos hacer es indicar el fichero a reproducir. Una vez hecho esto pasa a estado inicializado. En este estado ya sabe qué fichero ha de reproducir, pero todavía no se ha preparado para ello (inicializar bufferes, etc), por lo que no podrá comenzar la reproducción. Para prepararlo deberemos llamar al método prepare()
, con lo que tendremos el reproductor listo para empezar a reproducir el audio.
La segunda opción consiste en crear una instancia de la clase MediaPlayer
por medio del método create
. En este caso se deberá pasar como parámetro, además del contexto de la aplicación, el identificador del recurso, como se puede ver en el siguiente ejemplo:
En este caso el método create()
se encarga de asignar la fuente de audio y además pasar el reproductor a estado preparado. Por lo tanto, en este caso no será necesario llamar a prepare()
, sino que podremos reproducir el medio directamente. Aunque es más sencillo que la primera opción, también resulta menos flexible.
Inicialización asíncrona
La llamada a prepare()
puede resultar bastante costosa y producir un retardo considerable, especialmente cuando accedemos a medios externos a la aplicación. Por este motivo debemos evitar llamarla desde el hilo de eventos, para evitar bloquear este hilo.
Podemos crear un hilo secundario y realizar la preparación del medio desde él, pero también contamos con una variante del método anterior que nos facilitará realizar la preparación de forma asíncrona, fuera del hilo de eventos. Esta variante es prepareAsync()
.
Podemos utilizar un listener de tipo MediaPlayer.OnPreparedListener
para que se nos notifique con el método onPrepared
cuándo está preparado el reproductor, y así poder comenzar la reproducción. Este listener se deberá registrar en el MediaPlayer
con el método setOnPreparedListener()
:
Métodos del reproductor de medios
Una vez que la instancia de la clase MediaPlayer
ha sido inicializada, podemos comenzar la reproducción mediante el método start
. También es posible utilizar los métodos stop
y pause
para detener y pausar la reproducción. Si se detuvo la reproducción de audio mediante el método stop
será imprescindible invocar el método prepare
antes de poder reproducirlo de nuevo mediante una llamada a start
. Por otra parte, si se detuvo la reproducción por medio de pause
, tan sólo será necesario hacer una llamada a start
para continuar en el punto donde ésta se dejó.
Otros métodos de la clase MediaPlayer
que podríamos considerar interesante utilizar son los siguientes:
setLooping
nos permite especificar si el clip de audio deberá volver a reproducirse cada vez que finalice.
setScreenOnWhilePlaying
nos permitirá conseguir que la pantalla se encuentre activada siempre durante la reproducción. Tiene más sentido en el caso de la reproducción de video, que será tratada en la siguiente sección.
setVolume
modifica el volumen. Recibe dos parámetros que deberán ser dos números reales entre 0 y 1, indicando el volumen del canal izquierdo y del canal derecho,respectivamente. El valor 0 indica silencio total mientras que el valor 1 indica máximo volumen.
seekTo
permite avanzar o retroceder a un determinado punto del archivo de audio. Podemos obtener la duración total del clip de audio con el métodogetDuration
,mientras que
getCurrentPosition
nos dará la posición actual. En el siguiente código se puede ver un ejemplo de uso de estos tres últimos métodos.
Liberación del reproductor de medios
Una acción muy importante que deberemos llevar a cabo una vez haya finalizado definitivamente la reproducción (porque se vaya a salir de la aplicación o porque se vaya a cerrar la actividad donde se reproduce el audio) es destruir la instancia de la clase MediaPlayer
y liberar su memoria. Para ello deberemos hacer uso del método release
.
Por ejemplo, si nuestra actividad crea un reproductor en start()
, deberemos asegurarnos de destruirlo siempre en stop()
para evitar que pueda haber más de uno simultáneamente.
Streams de audio y control de volumen
Android reproduce el audio en diferentes streams según su naturaleza: música, alarmas, notificaciones, llamadas, etc. Cada uno de estos streams tiene su propio nivel de volumen, de forma que el volumen del tono de llamada puede ser distinto al volumen del reproductor de música. Cuando el usuario manipule los botones de control de volumen del dispositivo, estará modificando el volumen del stream que esté sonando actualmente.
A la hora de reproducir sonido con el MediaPlayer
podemos especificar el stream dentro del cual lo queremos reproducir con setAudioStreamType
. Siempre llamaremos a este método en el estado idle, antes de haber especificado la fuente de audio:
Es importante destacar que no podremos especificar el stream de audio si lo creamos con el atajo
create
.
Como hemos comentado, cuando usamos los botones de control de volumen del dispositivos afectamos al stream que esté sonando actualmente. Si no estuviese sonando ningún audio, entonces por defecto afectaría al stream del tono de llamada.
Si estamos dentro de una aplicación como por ejemplo un videojuego, aunque no esté sonando nada normalmente nos interesará que al manipular el control de volumen dentro del videojuego se modifique el volumen de música del juego, y no el del todo de llamada. Para indicar a qué stream debe afectar el volumen utilizaremos el método setVolumenControlStream()
:
Cuando la actividad o fragmento en el que hayamos llamado al método anterior esté visible, el control de volumen afectará al stream de música, y no al por defecto.
Reproducción de audio en segundo plano
Puede intersarnos dejar nuestro reproductor funcionando en segundo plano aunque cerremos la actividad, por ejemplo para implementar un reproductor de música o de podcasts. Para hacer esto deberemos iniciar el reproductor desde un servicio, en lugar de una actividad:
Cuando el móvil no se esté utilizando normalmente la pantalla y la CPU se apagarán automáticamente para ahorrar batería. Si esto ocurre la música se detendrá. Para evitarlo necesitamos adquirir lo que se conoce como un wake lock. Esto nos permitirá mantener el móvil activo aunque el usuario no lo esté utilizando.
Para poder obtener un wake lock lo primero que deberemos es solicitar el permiso correspondiente:
Una vez contemos con el permiso, podemos indicar al reproductor de medios que necesitamos un wake lock parcial (se puede apagar la pantalla pero no la CPU):
Si estamos accediendo a un fichero remoto deberemos indicar también que no se apague la WiFi, mediante un wifi lock:
Al parar o pausar la reproducción podemos liberar este lock para así ahorrar batería:
Aunque la reproducción se produzca en segundo plano, normalmente el usuario querrá estar al tanto de la reproducción y poder deternerla. Por ello es conveniente que el servicio funcione en modo foreground. Para ello deberemos crear una notificación de tipo ongoing (evento en curso) que se mostrará en la barra de notificaciones mientras el servicio esté activo. Llamando a startForeground()
desde nuestro servicio haremos que dicha notificación se muestre mientras el servicio esté activo:
En el código anterior podemos ver que hemos añadido a la notificación un PendingIntent
para que al pulsar sobre ella se abra la actividad del reproductor, y así el usuario podría pausar, detener, o cambiar la música que esté sonando.
Cuando queramos que nuestro servicio deje de funcionar en modo foreground llamaremos a:
También podríamos incluir en la propia notificación controles para poder parar o reanudar el audio. Para ello podríamos utilizar un objeto de tipo RemoteViews
y cargar en él el layout que queramos que tenga la notificación. Al construir la notificación podremos establecer la vista remota a aplicar con setContent
.
Al pulsar los botones de la notificación se lanzará un intent con una acción ACTION_PLAY
o ACTION_PAUSE
, definidas ambas por nuestra aplicación. Haremos que nuestro servicio se ejecute cuando se lancen dichas acciones, añadiéndolas a un<intent-filter>
:
Deberemos actualizar el código del servicio para que soporte también la acción ACTION_PAUSE
:
A partir de Android 4.0 también podemos utilizar la clase RemoteControlClient
para implementar estos controles, y a partir de Android 5.0 este mecanismo es reemplazado por la clase MediaSession
. Podemos obtener más información sobre el nuevo mecanismo en:
Gestión del foco del audio
La posibilidad de reproducir audio en un servicio en segundo plano nos trae también la problemática de poder tener varios sonidos reproduciéndose simultáneamente. Por ejemplo, podemos estar reproduciendo músia al mismo tiempo que suena la alerta por la llegada de un mensaje, lo cual puede suponer que no oigamos el aviso de mensaje.
Para evitar este problema deberemos coordinar la reproducción de los diferentes audios. La forma que tiene Android de resolverlo es mediante un sistema en el que cada aplicación podrá solitar el foco para reproducir audio. Por ejemplo, si estamos reproduciendo música y otra aplicación solicita el foco, podemos deterner la música momentáneamente o bajar su volumen (técnica conocida como ducking) mientras la otra aplicación reproduce el sonido, hasta que nos devuelva el foco.
En primer lugar deberemos solicitar el foco para reproducir audio mediante el servicio AudioManager
:
En este caso anterior vemos que hemos solicitado el foco para el stream de reproducción de música. Además, al solicitar el foco debemos proporcional un listener de tipo AudioManager.OnAudioFocusChangeListener
que tendrá siguiente estructura:
Vemos en este ejemplo que hay varias mas de perder el foco:
AUDIOFOCUS_LOSS
: Se pierde de forma definitiva. Detenemos y liberamos el reproductor.AUDIOFOCUS_LOSS_TRANSIENT
: Se pierde de forma temporal. Pausamos el reproductor.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
: Se pierde de forma temporal y se permite que simplemente reduzcamos el volumen del audio actual (ducking).
Cuando ganemos el foco de nuevo (AUDIOFOCUS_GAIN
) deberemos inicializar el reproductor si no estaba inicializado todavía, reanudar la reproducción si se había pausado, y restaurar el volumen original.
La característica audio focus sólo está disponible a partir de Android 2.2 (API 8). Si queremos tener compatibiliad con versiones anteriores deberemos comprobar la versión de Android en código y sólo utilizar audio focus si es mayor o igual que la 8.
Desconexión de auriculares
Estamos acostumbrados a ver que en las aplicaciones móviles para reproducir música, al desconectar los auriculares la reproducción se detiene automáticamente para evitar que el ruido repentino pudiera causar molestias.
Este comportamiento no es automático, sino que lo deberemos programar nosotros. Para ello deberemos capturar el intent AUDIO_BECOMING_NOISY
mediante un broadcast receiver:
Dentro del broadcast receiver podremos por ejemplo realizar la acción de detener la reproducción actual:
Reproducción de vídeo
Reproducir vídeo mediante VideoView
La reproducción de vídeo es muy similar a la reproducción de audio, salvo dos particularidades. En primer lugar, no es posible reproducir un clip de vídeo almacenado como parte de los recursos de la aplicación. En este caso no existe ningún método para reproducir un vídeo a partir de un id
de recurso. Lo que si que podemos hacer es representar un recurso raw
de tipo vídeo mediante una URL. Esta URL tendrá la siguiente forma (hay que especificar como host el paquete de nuestra aplicación, y como ruta el id del recurso):
En segundo lugar, el vídeo necesitará de una superficie para poder reproducirse. Esta superficie se corresponderá con una vista dentro del layout de la actividad.
Existen varias alternativas para la reproducción de vídeo, teniendo en cuenta lo que acabamos de comentar. La más sencilla es hacer uso de una vista de tipo VideoView
, que encapsula tanto la creación de una superficie en la que reproducir el vídeo como el control del mismo mediante una instancia de la clase MediaPlayer
. Este método será el que veamos en primer lugar.
El primer paso consistirá en añadir la vista VideoView
a la interfaz gráfica de la aplicación. Para ello añadimos el elemento en el archivo de layout correspondiente:
Dentro del código Java podremos acceder a dicho elemento de la manera habitual, es decir, mediante el método findViewById
. Una vez hecho esto, asignaremos una fuente que se corresponderá con el contenido multimedia a reproducir. El VideoView
se encargará de la inicialización del objeto MediaPlayer
. Para asignar un video a reproducir podemos utilizar cualquiera de estos dos métodos:
Una vez inicializada la vista se puede controlar la reproducción con los métodos start
, stopPlayback
, pause
y seekTo
. La clase VideoView
también incorpora el método setKeepScreenOn(boolean)
con la que se podrá controlar el comportamiento de la iluminación de la pantalla durante la reproducción del clip de vídeo. Si se pasa como parámetro el valor true
ésta permanecerá constantemente iluminada.
El siguiente código muestra un ejemplo de asignación de un vídeo a una vista VideoView
y de su posterior reproducción. Dicho código puede ser utilizado a modo de esqueleto en nuestra propia aplicación. También podemos ver un ejemplo de uso de seekTo
, en este caso para avanzar hasta la posición intermedia del video.
Reproducir vídeo con MediaPlayer
La segunda alternativa para la reproducción de vídeo consiste en la creación de una superficie en la que dicho vídeo se reproducirá y en el uso directo de la clase MediaPlayer
. La superficie deberá ser asignada manualmente a la instancia de la clase MediaPlayer
. En caso contrario el vídeo no se mostrará. Además, la clase MediaPlayer
requiere que la superficie sea un objeto de tipo SurfaceHolder
.
Un ejemplo de objeto SurfaceHolder
podría ser la vista SurfaceView
, que podremos añadir al XML del layout correspondiente:
El siguiente paso será la inicialización el objeto SurfaceView
y la asignación del mismo a la instancia de la clase MediaPlayer
encargada de reproducir el vídeo. El siguiente código muestra cómo hacer esto. Obsérvese que es necesario que la actividad implemente la interfaz SurfaceHolder.Callback
. Esto es así porque los objetos de la clase SurfaceHolder
se crean de manera asíncrona, por lo que debemos añadir un mecanismo que permita esperar a que dicho objeto haya sido creado antes de poder reproducir el vídeo.
Una vez que hemos asociado la superficie al objeto de la clase MediaPlayer
debemos asignar a dicho objeto el clip de vídeo a reproducir. Ya que habremos creado el objeto MediaPlayer
previamente, la única posibilidad que tendremos será utilizar el método setDataSource
, como se muestra en el siguiente ejemplo. Recuerda que cuando se utiliza dicho método es necesario llamar también al método prepare
.
Ejercicios
Reproducir de un clip de audio
Se te proporciona en las plantillas de la sesión la aplicación Audio. Dicha aplicación contiene un clip de audio almacenado en los recursos, cuyo nombre es zelda_nes.mp3. La aplicación contiene una única actividad con una serie de botones, a los que añadiremos funcionalidad para poder controlar la reproducción del clip de audio.
Se te pide hacer lo siguiente:
Añade el código necesario en el constructor para crear una instancia de la clase
MediaPlayer
(donde el objetomp
habrá sido declarado como un atributo de la clase):Modifica el manejador del botón Reproducir para que cada vez que se pulse éste se deshabilite, se habiliten los botones Pausa y Detener, y empiece a reproducirse el clip de audio mediante la invocación del método
start
del objetoMediaPlayer
.Para habilitar o deshabilitar botones usaremos el método
setEnabled
, que recibe como parámetro un booleano.Modifica el manejador del botón Detener para que cada vez que se pulse se deshabilite dicho botón y el botón Pausa, se habilite el botón Reproducir, y se detenga la reproducción del audio mediante el método
stop
. Invoca también el métodoprepare
del objetoMediaPlayer
para dejarlo todo preparado por si se desea reproducir de nuevo el audio.Modifica el manejador del botón Pausa. Si el audio estaba en reproducción el texto del botón pasará a ser Reanudar y se pausará la reproducción por medio del método pause. Si ya estaba en pausa el texto del botón volverá a ser Pausa y se reanudará la reproducción del audio. No olvides cambiar la etiqueta del botón a Pausa si se pulsa el botón Detener.
Observa que cuando detienes la reproducción con
stop
y la reanudas constart
el archivo de audio continúa reproduciéndose en el punto donde se detuvo. ¿Qué tendrías que hacer para que al pulsar el botónReproducir
la reproducción comenzara siempre por el principio?Libera los recursos asociados al objeto
MediaPlayer
en el métodoonDestroy
. No olvides invocar al métodoonDestroy
de la superclase.
Evento de finalización de la reproducción
El objeto MediaPlayer
no pasa automáticamente al estado de detenido una vez que se reproduce completamente el clip de audio, sino que es necesario detener la reproducción a mano. Por lo tanto, en este ejercicio vamos a añadir un manejador para el evento que se dispara cuando el clip de audio finaliza. Añade el siguiente código en el método onCreate
, tras la inicialización del objeto MediaPlayer
:
Lo que tiene que hacer este manejador es exactamente lo mismo que se debería hacer en el caso de haber pulsado el botón de Detener
, es decir, habilitar el botón Reproducir, deshabilitar el resto, e invocar al método stop
.
Reproducir un clip de vídeo usando VideoView
Para este ejercicio se te proporciona en las plantillas el proyecto Video, que contiene una única actividad para controlar la reproducción de un clip de video, que se incluye en los recursos del proyecto (fichero /res/raw/tetris.3gp
). En este caso tendremos un botón etiquetado como Reproducir, un botón etiquetado como Detener y una vista de tipo TextView que usaremos para mostrar la duración del vídeo, el cual se mostrará en una vista de tipo VideoView
.
Los pasos que debes seguir son los siguientes:
Modifica el manejador del botón Reproducir para que cuando éste se pulse se deshabilite y se habilite el botón Detener.
Modifica el manejador del botón Detener para que cuando éste se pulse se deshabilite y se habilite el botón Reproducir.
Prepara el vídeo a reproducir al pulsar el botón Reproducir con la siguiente línea de código (pero no utilices el método
start
para reproducirlo todavía):Detén la ejecución del vídeo cuando se pulse el botón Detener por medio del método
stopPlayback
.Para poder reproducir el vídeo y poder obtener su duración y mosrtrarla en el
TextView
, hemos de esperar a que la superficie donde se va a reproducir esté preparada. Para ello hemos de implementar el manejador para el eventoOnPreparedListener
de la vistaVideoView
. Añade el manejador:Añade código en el manejador anterior para comenzar la reproducción por medio del método
start
. Obtén la duración en minutos y segundos por medio del métodogetDuration
delVideoView
y muéstrala en elTextView
con el formato mm:ss.El método
getDuration
devuelve la duración del vídeo en milisegundos.
Last updated