Sound MediaPlayer working in APDE preview

Because we all know that the “sound” library in processing for android garbles and distorts your sounds after a while,
and that the “cassette” library stops working after a few playbacks,
the only viable option is the native android MediaPlayer.
Especially when using APDE.
Because there we have to address different loading methods of the audio files for the preview or if it’s an apk…

So here is a MediaPlayer example that works in APDE and its preview.
Wrapped in convenient helper functions that work as drop in modules to save some headache.
If anyone wants to maybe make a class for even more convenience, be my guest.

I also added a extensive amount of comments for people who are new to this.

Here ya go:


// Necessary imports
import android.media.MediaPlayer;
import android.media.AudioAttributes;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer.OnCompletionListener;
import android.content.Context;
import android.app.Activity;

// The needed initializers
Context context;
Activity act;

// Only needed when direct access is wished 
MediaPlayer bgm,sound = null;

// Make sure these or other files are in the data folder
String bgmMusic = "menu_music.mp3";
String sfxSound = "correct.mp3";

void setup()
{
  // Need to assign these 
  act = this.getActivity();
  context = act.getApplicationContext();
}

void draw ()
{
  // Not really necessary in this example...
}

// Cleanup any declared MediaPlayers when
// closing the app, if there are.
public void stop()
{
  super.stop();
  // We have those in this example
  bgm.release();
  sound.release();
}

// We use mousePressed for demonstration
// as this on almost all devices available. 
void mousePressed()
{
  //- Use this for fire&forget playback
  //- Best for small files to avoid long loading
   playSfx(sfxSound);
  
  //- For one-shoot sounds that stay around
  //- to be played again at a later time.
  // sound = playSound(sfxSound);
  //- Or just load the file without playing. 
  // sound = loadSound(sfxSound);
  
  //- Use this for playing (looping) music
  // bgm = playLoop(bgmMusic);
  //- Or just load the music file and play later
  // bgm = loadLoop(bgmMusic);
  
  //- To access its methods, assign it to a variable
  //- we can also assign a new file while playing.
  if (bgm == null)
  {
    // Load and play the file
    bgm = playLoop(bgmMusic);
  }
  else
  {
    // It's loaded, so pause or play(start)...
    // we can check if it's playing after the
    // file was loaded, and everything else
    // that you can do with the MediaPlayer
    // But don't use stop! except you know what you're doing...
    if (bgm.isPlaying())
    {
      bgm.pause();
    } else
    {
      // I know that using start instead of play
      // is confusing, but i didn't make the MediaPlayer...
      bgm.start();
    }
  }
}

// Here the actual helper/wrapper functions
// that encapsulate the MediaPlayer creation stuff.
MediaPlayer playSfx(String file)
{
  // This exists for convenience only
  // and should be removed for production.
  // This lets us set if we use the preview
  // of APDE or if this is an apk for installation testing 
  Boolean isApk = false; //set false for preview and true for akp.
  
  // First create a new MediaPlayer 
  MediaPlayer mp = new MediaPlayer();
  
  // Set looping false for one-shot
  mp.setLooping(false);
  
  // The listener is only needed to release
  // fire&forget sounds from memory.
  // For looping or play/pause this has
  // to be removed completely, otherwise 
  // we can't play the sound again.
  // because with it the MediaPlayer goes
  // into stop() mode which is basically a reset()
  // and we would need to assign a datasource again 
  mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
  {
    // Yes it has to be the com
    @Override
      public void onCompletion(MediaPlayer mp)
    {
      // Cleanup
      mp.release();
      mp = null;
    }
  }
  );
  
  // Now we try to assign the file to the
  // datasource of the MediaPlayer
  // It has to be a try because the
  // MediaPlayer wants it so.
  try
  {
    // Must first set the Attributes before loading.
    // We don't explicitly need to set them, but better coding and so...
    mp.setAudioAttributes( new AudioAttributes.Builder() 
      .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
      .setUsage(AudioAttributes.USAGE_MEDIA)
      .build()
      );
    
    // We check if its an apk or not
    // BECAUSE the preview and apk
    // load the file differently! 
    if (!isApk)
    {
      // For preview use this and simply
      // set the datasource to our preview folder
      // followed by the filename.
      mp.setDataSource(sketchPath + "/" + file);
    }
    else
    {
      // An APK needs to load the file like this
      
      // First create a new AssetFileDescriptor
      // array. Somehow needs to be an array..whatever..
      AssetFileDescriptor[] fd = new AssetFileDescriptor[1];
      // Now we load the file and its infos
      // into the descriptor
      fd[0] = context.getAssets().openFd(file);
      // And now we load that into the datasource.
      mp.setDataSource(fd[0].getFileDescriptor(), fd[0].getStartOffset(), fd[0].getLength());
    }
    
    // For music files we don't use prepareAsync
    mp.prepare();
    
    // Now we can play the sound if we want
    mp.start();
  }
  catch(IOException e) {
    // For debug messages in case of an error.
    // Has to be IOException for the MediaPlayer 
    println(e);
  }
  
  // Finally we return the MediaPlayer to a
  // variable or nothingness for fire&forget...
  return mp;
}

// The following functions are just for pure convenience...
// I know we could use a different creator method where
// we can pre-define its state but that's for you to do.
// Here's just bare metal examples...
MediaPlayer playSound(String file)
{
  Boolean isApk = false; //set false for preview
  MediaPlayer mp = new MediaPlayer();
  mp.setLooping(false);
  try
  {
    mp.setAudioAttributes( new AudioAttributes.Builder() 
      .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
      .setUsage(AudioAttributes.USAGE_MEDIA)
      .build()
      );

    if (!isApk)
    {
      mp.setDataSource(sketchPath +"/"+file);
    }
    else
    {
      AssetFileDescriptor[] fd = new AssetFileDescriptor[1];
      fd[0] = context.getAssets().openFd(file);
      mp.setDataSource(fd[0].getFileDescriptor(), fd[0].getStartOffset(), fd[0].getLength());
    }
    mp.prepare();
    mp.start();
  }
  catch(IOException e) {
    println(e);
  }
  return mp;
}

MediaPlayer playLoop(String file)
{
  Boolean isApk = false; //set false for preview
  MediaPlayer mp = new MediaPlayer();
  mp.setLooping(true);

  try
  {
    mp.setAudioAttributes( new AudioAttributes.Builder() 
      .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
      .setUsage(AudioAttributes.USAGE_MEDIA)
      .build()
      );

    if (!isApk)
    {
      mp.setDataSource(sketchPath +"/"+file);
    }
    else
    {
      AssetFileDescriptor[] fd = new AssetFileDescriptor[1];
      fd[0] = context.getAssets().openFd(file);
      mp.setDataSource(fd[0].getFileDescriptor(), fd[0].getStartOffset(), fd[0].getLength());
    }
    mp.prepare();
    mp.start();
  }
  catch(IOException e) {
    println(e);
  }
  return mp;
}

MediaPlayer loadSound(String file)
{
  Boolean isApk = false; //set false for preview
  MediaPlayer mp = new MediaPlayer();
  mp.setLooping(false);

  try
  {
    mp.setAudioAttributes( new AudioAttributes.Builder() 
      .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
      .setUsage(AudioAttributes.USAGE_MEDIA)
      .build()
      );

    if (!isApk)
    {
      mp.setDataSource(sketchPath +"/"+file);
    }
    else
    {
      AssetFileDescriptor[] fd = new AssetFileDescriptor[1];
      fd[0] = context.getAssets().openFd(file);
      mp.setDataSource(fd[0].getFileDescriptor(), fd[0].getStartOffset(), fd[0].getLength());
    }
    mp.prepare();
  }
  catch(IOException e) {
    println(e);
  }
  return mp;
}

MediaPlayer loadLoop(String file)
{
  Boolean isApk = false; //set false for preview
  MediaPlayer mp = new MediaPlayer();
  mp.setLooping(true);

  try
  {
    mp.setAudioAttributes( new AudioAttributes.Builder() 
      .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
      .setUsage(AudioAttributes.USAGE_MEDIA)
      .build()
      );

    if (!isApk)
    {
      mp.setDataSource(sketchPath +"/"+file);
    }
    else
    {
      AssetFileDescriptor[] fd = new AssetFileDescriptor[1];
      fd[0] = context.getAssets().openFd(file);
      mp.setDataSource(fd[0].getFileDescriptor(), fd[0].getStartOffset(), fd[0].getLength());
    }
    mp.prepare();
  }
  catch(IOException e) {
    println(e);
  }
  return mp;
}

This part is actually also releasing the created media players even when loosing focus of the app,
and then the app crashes when brought back to foreground because the mediaplayers are gone and we can’t just start() them again.

better option would be, to just pause the media players when the app goes into “pause” mode instead of releasing them.

like that:

public void pause()
{
  super.pause();
  // We have those in this example
  bgm.pause();
  sound.pause();
}

Although not sure if the MediaPlayers get released then when closing the app…
maybe someone else can point that out for me…

EDIT: after further testing, APDE sends a pause() AND stop() for the app even if just loosing focus.
So better keep that in mind…

Try putting your media player creation in onCreate()