MediaPlayer to play audios on Android

- Andrés Cruz

En español
MediaPlayer to play audios on Android

As its name indicates, MediaPlayers are used to play and control the playback of audio and/or video on Android; although their use may seem complex, they are quite simple and configurable. This entry is not intended to be a guide to use MediaPlayer on Android, nor to create a player (although a structure is given from which you can start), the idea is to exemplify its more basic use, give an outline of how multimedia resources can be used through services and explain the purpose of two very important components that are the prepare and prepareAsync method that although their names are similar, the purpose of each one is different and the use of one or the other can have very different results in our application.

To play an audio, we must first create an object of the MediaPlayer class:

player = new MediaPlayer();

Now we must indicate the source of the data, which can be internal, that is, located on the same Android device or on a website and we access it via a URL like the following:

player.setDataSource("http://example.net/uploads/fuente/"
+ cancion.getFuente());

Android takes care of downloading the audio; once the source is established, we prepare the audio and start playback:

player.prepare();
player.start();

Finally our code is as follows:

try {
    player.setDataSource("http://example.net/uploads/fuente/"
    + cancion.getFuente());
    player.prepare();
    player.start();
} catch (Exception e) {
    e.printStackTrace();
}

Audios loaded asynchronously

Depending on the purpose of your application, the code shown above may give you some problems, the reason is that the audios are not fully prepared until they are fully prepared for playback, the application may "hang" depending on which thread in where we invoke said method, which is generally in the main thread and it is possible that the famous "Not responding" window appears:

No responde app android

In which it tells us that the application does not respond and if you want to close it; something that is not positive in the experience that the user has with our application.

prepareAsync to prepare audio asynchronously

The problem becomes even greater if, for example, we bring all the audios from the Internet and the application consists of a list of audios, and if the user begins to select several of them, the crash of our application becomes imminent...

The solution is really simple and we don't have to create services, threads, etc and put all the processing inside them or anything like that; We just have to use the prepareAsync method and change a few things in the above code and add a few more.

First, the prepareAsync method allows the audio to be prepared in the same way as the prepare method, but it does so asynchronously, therefore the audio must be played (depending on the action we want to do on it) once the audio is ready or prepared; For that we use an onPrepared listener event and inside it we move our play() method:

try {
    player.setDataSource("http://example.net/uploads/fuente/"
    + cancion.getFuente());
    player.setOnPreparedListener(this);
    player.prepareAsync();
} catch (Exception e) {
    e.printStackTrace();
}

And we define our listener event as follows:

@Override
public void onPrepared(MediaPlayer mediaPlayer) {
    if (mediaPlayer.isPlaying()) {
        mediaPlayer.pause();
    }
    mediaPlayer.start();
}

And with this, goodbye hangs (at least those caused by the prepare method); As we can see, with the prepareAsync method which does not need to be defined in a separate thread or process and it is Android that is in charge of handling the necessary background processes.

Practical case: Making a small player

An approach that allows us to use the MediaPlayer for the parallel reproduction of multimedia resources such as audio and/or video is to define a separate class that extends the services, in this way we can have a mother class that will be our activity that invokes (and controls according to needs) to the service, in addition to this, the service will allow a series of functionalities to be carried out through predefined or already defined methods in other classes that make up the API, and this is done by implementing these classes (OnPreparedListener, OnErrorListener and OnCompletionListener).

We will create a class that for this example we will call MusicService:

public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { }

Now we define some global variables that will allow us to perform the operation of a list of songs; we also override the corresponding methods according to the inherited and implemented classes:

public class MusicService  extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {

    //media player
    private MediaPlayer player;
    //song list
    private ArrayList<Cancion> canciones;
    private Cancion cancion;
    //current position
    private int songPosn;

    private final IBinder musicBind = new MusicBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        initMusicPlayer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return musicBind;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Bundle extras = intent.getExtras();
        int accion = 0;
        if (extras != NULL) {
            accion = extras.getInt("accion");
        }

        return START_NOT_STICKY;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        try {
            player.stop();
            player.release();
        } catch (Exception e) {
            Log.e("ERROR", e.toString());
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp.reset();
        return false;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mp.reset();
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        if(mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }

        mediaPlayer.start();
    }


}

As we can see, we override several methods that are executed in the life cycle of a video or audio, such as if an error occurs in playback using the onError method, when the music playback is completed using the onCompletion method, when the video or music is ready for playback using the onPrepared method.

We also have some service methods like the onUnbind that is called when the client connects to the service or the onStartCommand method that is called when the activity starts the service; with this we define other methods that will allow us to automate the reproduction of our music that we use in the methods overridden by the previous implemented classes:

public class MusicService  extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {

    private MediaPlayer player;

    private ArrayList<Cancion> canciones;
    private Cancion cancion;

    private int songPosn;

    private final IBinder musicBind = new MusicBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        initMusicPlayer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return musicBind;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Bundle extras = intent.getExtras();
        int accion = 0;
        if (extras != NULL) {
            accion = extras.getInt("accion");

            switch (accion) {
                case 1:
                    if (isPng()) {
                    pausePlayer();
                } else {
                    go();
                }
                break;
                case 3:
                    playNext();
                break;
                case 4:
                    playPrev();
                break;
            }
        }

        return START_NOT_STICKY;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        try {
            player.stop();
            player.release();
        } catch (Exception e) {
            Log.e("ERROR", e.toString());
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp.reset();
        return false;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mp.reset();
        playNext();
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        if(mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }

        mediaPlayer.start();
    }

    public void initMusicPlayer() {
        songPosn = 0;
        player = new MediaPlayer();
        player.setWakeMode(getApplicationContext(),
        PowerManager.PARTIAL_WAKE_LOCK);
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setOnPreparedListener(this);
        player.setOnCompletionListener(this);
        player.setOnErrorListener(this);
    }

    public void setList(ArrayList<Cancion> canciones) {
        this.canciones = canciones;
    }

    public void playSong() {

        if (songPosn >= canciones.size())
        return;

        try {
            player.reset();
        } catch (Exception e) {
            Log.i("Error", e.toString());
        }

        cancion = canciones.get(songPosn);

        try {
            player.setDataSource("http://url/"
            + cancion.getFuente());
            player.setOnPreparedListener(this);
            player.prepareAsync();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e("MUSIC SERVICE", "Error setting data source", e);
        }
    }

    public class MusicBinder extends Binder {
        public MusicBinder() {
        }

        public MusicService  getService() {
            return MusicService .this;
        }
    }

    public int getPosn() {
        return player.getCurrentPosition();
    }

    public int getDur() {
        return player.getDuration();
    }

    public boolean isPng() {
        return player.isPlaying();
    }

    public void pausePlayer() {
        player.pause();
    }

    public void seek(int posn) {
        player.seekTo(posn);
    }

    public void go() {
        player.start();
    }

    public void playPrev() {
        songPosn--;
        if (songPosn < 0) songPosn = canciones.size() - 1;
        playSong();
    }

    //skip to next
    public void playNext() {

        songPosn++;
        if (songPosn >= canciones.size()) songPosn = 0;

        playSong();
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
    }
}

With this we would have the complete service, some methods were created that allow us to verify the status of the audio playback, go to the next music, start playback, pause, etc.

Now, as we indicated at the beginning, the service is started from another component that in this case represents an activity; to start the previous service from an activity we have:

public MusicService musicSrv;
public boolean musicBound = false;

private ServiceConnection musicConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
        //get service
        musicSrv = binder.getService();
        //pass list
        musicSrv.setList(canciones);
        musicBound = true;

        current = 0;
        try {
            songPicked(NULL);
        } catch (Exception e) {
            Log.e("ERROR SERVICE", e.toString());
        }

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        musicBound = false;
        Log.i("onServiceDisconnected", "Desconectado");
    }
};

To send if we want to play, pause, go to the next audio from the activity that invoked the service or any other communication that must occur between the activity and the service we have:

private void playNext() {
    musicSrv.playNext();
    current++;
    songPicked();
}

private void playPrev() {
    musicSrv.playPrev();
    current--;
    songPicked();
}

It would remain is to associate these functions to the buttons or another element for its action.

Songs is a model class with the following structure:

public class Cancion {

    int cancion_id;
    String fuente;
    String titulo;
    String anombre;
    String imagen_url;

    public String getAnombre() {
        return anombre;
    }

    public void setAnombre(String anombre) {
        this.anombre = anombre;
    }
    public int getCancion_id() {
        return cancion_id;
    }

    public void setCancion_id(int cancion_id) {
        this.cancion_id = cancion_id;
    }

    public String getFuente() {
        return fuente;
    }

    public void setFuente(String fuente) {
        this.fuente = fuente;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public String getImagen_url() {
        return imagen_url;
    }

    public void setImagen_url(String imagen_url) {
        this.imagen_url = imagen_url;
    }
}
Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.