Creación de un juego (3): Modulo Audio

Para la creación de nuestro juego no vamos a diseñar un proceso de audio avanzado, simplemente nos centraremos en reproducir archivos de música y efectos de sonido. Para ello crearemos tres interfaces: Sonido, Música y Audio.
Sonido: nos permitirá reproducir efectos de sonido que almacenaremos en la memoria RAM.
Musica: reproducirá archivos de gran tamaño directamente desde el disco a la tarjeta de sonido.
Audio: sera la responsable de crear sonidos y música a partir de archivos ubicados en la carpeta assets. 


1.1 Interface de Sonido
public interface Sound {
 
    public void play(float volume);

    public void dispose();
}
Simplemente necesitaremos dos métodos para los efectos de sonido, el método play para reproducirlo y el método dispose que liberara el efecto de sonido de la memoria RAM.



1.2 Interface de Música
public interface Music {
 
    public void play();

    public void stop();

    public void pause();

    public void setLooping(boolean looping);

    public void setVolume(float volume);

    public boolean isPlaying();

    public boolean isStopped();

    public boolean isLooping();

    public void dispose();
}
Aquí necesitaremos los controles típicos de un reproductor de música: play, stop y pause. Después el método setLooping lo usaremos para poner la música en modo bucle/loop. Con el método setVolume indicaremos el volumen de salida de nuestra música. Continuamos creando tres métodos (isPlaying, isStopped, isLooping) que usaremos para comprobar si la música se esta reproduciendo, esta parada o esta en modo bucle/loop. Y para terminar usaremos el método dispose para liberar los recursos de nuestro reproductor una vez que ya no lo necesitemos.



1.3 Interface de Audio
public interface Audio {
   
    public Music newMusic(String filename);

    public Sound newSound(String filename);
}
Con esta interface conseguiremos reducir en numero de instancias de nuestro código y simplemente la usaremos para crear nuevos objetos de Sonido o Audio, pasándole como parámetro el nombre del archivo.



2.1 Implementar interface de Sonido
import android.media.SoundPool;

import com.example.slange.interfaces.Sound;

public class AndroidSound implements Sound {
    
    int soundId;
    SoundPool soundPool;

    public AndroidSound(SoundPool soundPool, int soundId) {
        this.soundId = soundId;
        this.soundPool = soundPool;
    }

    public void play(float volume) {
        soundPool.play(soundId, volume, volume, 0, 0, 1);
    }

    public void dispose() {
        soundPool.unload(soundId);
    }
}
Empezamos creando una variable int, que sera donde almacenaremos la id de nuestro efecto de sonido y creamos un objeto soundPool que nos ayudara a gestionar y reproducir los efectos de sonido.

En el constructor de la clase simplemente almacenamos los parámetros del constructor en las dos variables que hemos creado para esta clase.

Para reproducir los efectos de sonido usaremos el método play de la clase SoundPool:
play(soundID, leftVolume, rightVolume, priority, loop, rate)
Nos pide como parámetro la id del sonido que la conseguiremos mas adelante con el método load de esta misma clase, el volumen del canal izquierdo y derecho (usaremos los parámetros de nuestro método en este caso, el rango va desde 0.0 a 1.0), la prioridad de reproducción (0 es la mas baja),  modo loop (0 desactivado y -1 activado) y para finalizar la tasa de reproducción (el valor normal es 1, pero su rango va desde 0.5 a 2.0).

Para liberar el recurso de la memoria en nuestro SoundPool usaremos el método unload que nos pide como parámetro la id del sonido a liberar.



2.2 Implementar interface de Música
import java.io.IOException;

import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;

import com.example.slange.interfaces.Music;

public class AndroidMusic implements Music, OnCompletionListener {
    
    MediaPlayer mediaPlayer;
    boolean isPrepared = false;
Comentar que a parte de implementar nuestra interface Música, también implementamos la interface OnCompletionListener que nos pide sobreescribir el método onCompletion. Esta interface se encarga de hacer una llamada a su método una vez que la reproducción a terminado, es decir, cuando termina de sonar la música porque a llegado a su fin.

Y el bloque sinchronized se encargara automáticamente de gestionar los estados del reproductor (reproduciendo, parado, pausado, preparado). Con ello conseguiremos no tener dos estados diferentes a la vez y por lo tanto no tener excepciones IllegalStateException.

Empezamos la interface creando un objeto MediaPlayer que sera el encargado de preparar, reproducir, pausar y parar nuestra música. También creamos un booleano para almacenar el estado preparado del reproductor de música.

    public AndroidMusic(AssetFileDescriptor assetDescriptor) {
        mediaPlayer = new MediaPlayer();
        try {
            mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(),
                    assetDescriptor.getStartOffset(),
                    assetDescriptor.getLength());
            mediaPlayer.prepare();
            isPrepared = true;
            mediaPlayer.setOnCompletionListener(this);
        } catch (Exception e) {
            throw new RuntimeException("Error al cargar la musica");
        }
    }
Ya en el constructor primero iniciamos nuestro mediaplayer y encapsulamos todo en un bloque try-catch por si acaso hay problemas al cargar el archivo de música. 
Establecemos nuestra fuente de música con el metodo setDataSource, que nos pide como parámetro un FileDescriptor (usamos el parámetro AssetFileDescriptor con su método getFileDescriptor que nos devuelve un archivo de la carpeta assets para poder leer sus datos, así como el desplazamiento y su longitud), el punto inicial de nuestro archivo de música (con el método getStartOffset establecemos el inicio) y el final del archivo de música (con el método getLength establecemos el final).
Preparamos el archivo para su reproducción con el método prepare y guardamos el estado true en nuestra variable booleana.
Para terminar establecemos la llamada al método onCompletion y creamos una nueva excepción con un mensaje personalizado.

    public void dispose() {
        if (mediaPlayer.isPlaying())
            mediaPlayer.stop();
        mediaPlayer.release();
    }
Comprobamos si se esta reproduciendo y en este caso pararemos la reproducción con el método stop y liberaremos el recurso de nuestro mediaplayer con el método release.

    public boolean isLooping() {
        return mediaPlayer.isLooping();
    }
Devuelve true en caso de que el reproductor este en modo bucle/loop.

    public boolean isPlaying() {
        return mediaPlayer.isPlaying();
    }
Devuelve true en caso de que el reproductor este reproduciendo la música.

    public boolean isStopped() {
        return !isPrepared;
    }
Comprobaremos nuestra variable booleana y nos devolverá false en caso de que se este reproduciendo.

    public void pause() {
        if (mediaPlayer.isPlaying())
            mediaPlayer.pause();
    }
Comprobaremos si se esta reproduciendo y en este caso pausamos la reproducción.

    public void play() {
        if (mediaPlayer.isPlaying())
            return;
        try {
            synchronized (this) {
                if (!isPrepared)
                    mediaPlayer.prepare();
                mediaPlayer.start();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Lo primero que hacemos es comprobar si se esta reproduciendo y en este caso devolvemos la función y ya no hace nada mas. En el caso contrario encapsulamos todo en un bloque try-catch manejando dos excepciones. Una vez dentro del bloque, sincronizamos el estado del reproductor y comprobamos si esta preparado a través de nuestra variable booleana, en caso de que no lo este lo preparamos con el método prepare y para terminar empezamos la reproducción con el método start.

    public void setLooping(boolean isLooping) {
        mediaPlayer.setLooping(isLooping);
    }
Ponemos el reproductor en modo bucle/loop a través del método mediaplayer setLooping, indicándolo con el parámetro booleano de nuestro método isLooping.

    public void setVolume(float volume) {
        mediaPlayer.setVolume(volume, volume);
    }
Indicamos el volumen derecho e izquierdo a través del método mediaplayer setVolume y usando nuestro parámetro float volume.

    public void stop() {
        mediaPlayer.stop();
        synchronized (this) {
            isPrepared = false;
        }
    }
Paramos la reproducción con el método mediaplayer stop y seguidamente sincronizamos el estado del reproductor y ponemos nuestra variable a false indicando que no esta preparada la reproducción.

    public void onCompletion(MediaPlayer player) {
        synchronized (this) {
            isPrepared = false;
        }
    }
}
Para terminar sobreescribimos el método onCompletion y simplemente sincronizamos el estado y ponemos nuestra variable a false.



2.3 Implementar interface de Audio
import java.io.IOException;

import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioManager;
import android.media.SoundPool;

import com.example.slange.interfaces.Audio;
import com.example.slange.interfaces.Music;
import com.example.slange.interfaces.Sound;

public class AndroidAudio implements Audio {
    
    AssetManager assets;
    SoundPool soundPool;
Comentar que a parte de implementar nuestra interface Audio, estamos importando las interfaces de Sonido y Música.

Empezamos creando un objeto AssetManager y SoundPool, ya conocidos en estos últimos artículos.

    public AndroidAudio(Activity activity) {
        activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        this.assets = activity.getAssets();
        this.soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
    }
Ya en el constructor le añadimos como parámetro una Activity y usamos el método setVolumeControlStream que nos permitirá cambiar el volumen con los controles de hardware del dispositivo y nos pide como parámetro una fuente de música que le indicamos que va a ser un stream de música a través de la clase AudioManager.
En nuestra variable assets almacenamos una instancia de la carpeta assets.
Y en la variable soundPool almacenamos un nuevo sonido SoundPool indicando como parámetro el numero máximo de sonidos simultáneos (20), el tipo de sonido (AudiosManager.STREAM_MUSIC) y la calidad de nuestro sonido (actualmente no tiene ningún efecto, por lo que su valor por defecto es 0).

    public Music newMusic(String filename) {
        try {
            AssetFileDescriptor assetDescriptor = assets.openFd(filename);
            return new AndroidMusic(assetDescriptor);
        } catch (IOException e) {
            throw new RuntimeException("Error al cargar: '" + filename + "'");
        }
    }
Nos devolverá un nuevo objeto AndroidMusic que cargaremos desde la carpeta assets.

    public Sound newSound(String filename) {
        try {
            AssetFileDescriptor assetDescriptor = assets.openFd(filename);
            int soundId = soundPool.load(assetDescriptor, 0);
            return new AndroidSound(soundPool, soundId);
        } catch (IOException e) {
            throw new RuntimeException("Error al cargar: '" + filename + "'");
        }
    }
}
Y para terminar el método newSound nos devolverá un nuevo objeto AndroidSound que cargaremos desde la carpeta assets, almacenando en memoria su id con el método load.

En ambos casos manejamos la excepción IOException en caso de que algo vaya mal a la hora de cargar los archivos.


CODIGO DE EJEMPLO: DESCARGAR

2 comentarios:

  1. Tonosparacelular.net es un sitio web que soalciona ringtones para teléfonosóviles en Mexico. Con mile de tonos de iPhone, tonos de Samsung o efectos de sonido únicos y atractivos que puedes buscar y descargar fácilmente.

    ResponderEliminar
  2. Dzwoneknatelefon.pl to polski dostawca dzwonków do telefonów komórkowych. Dzwonki dostępne zarówno bezpośrednio do pobrania w Dzwoneknatelefon.pl byłyby świetną propozycją na dzisiejsze urządzenia mobilne

    ResponderEliminar