Hace sólo un par de días que hemos publicado Adivinados que es el primer juego para Android que hago. Los últimos detalles han sido lo habitual, poner música de fondo, efectos, corregir alguna animación y demás, pero en este articulo me centraré en como he hecho para mantener la música de fondo a lo largo de todas las actividades, porque me ha parecido un tema curioso y que estoy seguro de que a alguno puede interesar y venir bien.
1. Planteamiento
Se utiliza MediaPlayer para gestionar el sonido. Si pensáis en usar SoundPool mejor dejadlo para otro tipo de sonidos, porque tiene un limite de 5 segundos para cada sonido que se reproduce.
El sonido se gestionará a través de un servicio (sé que hay gente que lo ha hecho con una clase instanciada normal). Y cada actividad que necesite interactuar con el sonido lo hará a través de este servicio, lo llamo AudioService.
Determinadas actividades tienen que poder cambiar el volumen de la musica.
2. El servicio
Creamos un archivo AudioService.java y en el empezamos escribiendo esto:
public class AudioService extends Service {
static final int DECREASE = 1, INCREASE = 2, START = 3, PAUSE = 4;
Boolean shouldPause = false;
MediaPlayer loop;
DECREASE
, INCREASE
, START
y PAUSE
són las acciones que podremos hacer con el sonido. shouldPause
hará la mágia del punto 4 y loop
será el sonido en si.
Estos son los métodos «públicos» para gestionar el sonido:
private void startLoop(){
if(loop == null){
loop = MediaPlayer.create(this, R.raw.loop);
}
if(!loop.isPlaying()){
loop.setLooping(true);
loop.start();
}
}
private void decrease(){
loop.setVolume(0.2f, 0.2f);
}
private void increase(){
loop.setVolume(1.0f, 1.0f);
}
private void start(){
startLoop();
shouldPause = false;
}
private void pause(){
shouldPause = true;
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
if(shouldPause) {
loop.pause();
}
}
}, 100);
}
Y estos son los métodos necesarios para que el servicio funcione. Lo único a destacar es que onStartCommand
se encargará de recibir las ordenes que le demos desde otras actividades, y hará lo que toque.
@Override
public void onCreate() {
super.onCreate();
Log.i(getClass().getSimpleName(), "Creating service");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Log.i(getClass().getSimpleName(), "Intent received");
try {
int actionDefault = 0;
int action = actionDefault;
if(intent != null){
if(intent.hasExtra("action")){
action = intent.getIntExtra("action", actionDefault);
}
}
switch (action) {
case INCREASE:
increase();
break;
case DECREASE:
decrease();
break;
case START:
start();
break;
case PAUSE:
pause();
break;
}
}catch (Exception e){
e.printStackTrace();
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (loop != null) loop.release();
}
No os olvidéis de declarar el servicio en el AndroidManifest.xml
:
<service
android:name=".AudioService"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />
3. Uso del servicio y problemas
Aquí empieza lo interesante. Cada vez que una actividad se abre o se cierra, se iniciará o pausará la música. De esta forma se puede diferenciar entre cerrar una actividad para salir del juego o cerrar una actividad porque cambias a otra. Más o menos quedaría así.
- De actividad A a Actividad B, se pausa al salir de A y se vuelve a arrancar al iniciar B.
- Finalizando actividad B, se pausa al salir de B y se vuelve a arrancar al iniciar A.
- Finalizando actividad A, se pausa al salir de A y como no se inicia ninguna otra, no se vuelve a arrancar.
Y aquí el código que hay que poner en cada actividad:
@Override
public void onPause() {
super.onPause();
pausar();
Intent i = new Intent(this, AudioService.class);
i.putExtra("action", AudioService.PAUSE);
startService(i);
}
@Override
public void onResume() {
super.onResume();
Intent i = new Intent(this, AudioService.class);
i.putExtra("action", AudioService.START);
startService(i);
}
El problema de esto es que provoca unas pausas de unos pocos milisegundos en cada cambio de actividad, un microcorte que a veces no se aprecia siquiera, pero si la nueva actividad es pesada a nivel de interfaz y tarda un poco en cargar, sí se notará. Incluso se notaría en dispositivos de gama baja o antiguos, para los que cualquer actividad por ligera que sea, ya es pesada.
4. Solución a los cortes entre actividades.
La forma de evitar esos cortes es sencilla, el truco está en la variable shouldPause y los métodos start()
y pause()
. Es más dificil de explicar que de entender, pero bueno:
Cada vez que se le dice que pause()
, se esperará 100ms (o el tiempo que se le diga) y después se preguntará si de verdad tiene que pausar. Si no ha pasado nada más, pausará. Si por el contrario ha llegado otra llamada a start()
durante esos 100ms, se habrá marcado shouldPause
como false
y la llamada a pause()
se omitirá.
Espero que le sirva a alguien, y que si alguien tiene una forma de hacerlo más sencilla me lo diga, por que la verdad es que no me gusta mucho, sobre todo por tener que marcar todas las actividades con los intents del servicio en el onPause
y onResume
.