0

I start by loading a Media player into a composition class:

public class MediaPlayerWURI {

    private final MediaPlayer mediaPlayer;

    final Uri uri;

    final ActivityMain activityMain;

    boolean isPrepared = true;

    MediaPlayerWURI(ActivityMain activityMain, MediaPlayer mediaPlayer, Uri uri){
        this.activityMain = activityMain;
        this.mediaPlayer = mediaPlayer;
        this.uri= uri;
        mediaPlayer.setOnPreparedListener(null);
        mediaPlayer.setOnErrorListener(null);
        mediaPlayer.setOnPreparedListener(new MOnPreparedListener(this));
        mediaPlayer.setOnErrorListener(new MOnErrorListener());
    }

    public void prepareAsync(){
        isPrepared = false;
        mediaPlayer.prepareAsync();
    }

    public void start(){
        mediaPlayer.start();
    }

    public void stop(){
        isPrepared = false;
        mediaPlayer.stop();
    }

    class MOnPreparedListener implements MediaPlayer.OnPreparedListener{

        final MediaPlayerWURI mediaPlayerWURI;

        public  MOnPreparedListener(MediaPlayerWURI mediaPlayerWURI){
            this.mediaPlayerWURI = mediaPlayerWURI;
        }

        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            mediaPlayerWURI.isPrepared = true;
        }
    }

    class MOnErrorListener implements MediaPlayer.OnErrorListener {

        public MOnErrorListener(){
        }

        @Override
        public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
                activityMain.releaseMediaPlayers();
                return false;
        }

    }

}

The media player passed in is created with MediaPlayer.create(getApplicationContext()) and is started successfully.

The following code do not trigger onPrepared() and gets stuck in a loop.

 mediaPlayerWURI.stop();
 mediaPlayerWURI.prepareAsync();
 while (!mediaPlayerWURI.isPrepared) { }
 mediaPlayerWURI.start();

I have tried prepareAsync() on another thread:

executorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            mediaPlayerWURI.prepareAsync();
                        }
                    });

My guess is it is a threading issue, but I am not sure how to handle this, or if it even is a threading issue. My understanding is that the MediaPlayer is preparing in another thread and that the loop shouldn't prevent it from calling on prepared. I am not sure what thread onPrepare() is ran on, but from the above, I think it means the main thread is supposed to run onPrepare() and is waiting for the loop to end.

Also, I am getting weird behavior where onPrepared() is being called after the construction of the MediaPlayer. Is that normal? My assumption is that onPrepared() is called when setOnPrepared() is called on a prepared MediaPlayer. This means the listener is attached.

John Glen
  • 771
  • 7
  • 24
  • Just double checking that you've seen: https://developer.android.com/reference/android/media/MediaPlayer#state-diagram and know the difference between the number of arrow heads (single synchronous, double asynchronous). – Morrison Chang Oct 01 '20 at 03:13
  • Yea. I know that the single arrows are the client calling a method and the MediaPlayer switching states and the double arrows are the MediaPlayer calling the methods when it transitions to a new state. That's why I'm confused onPrepared() got called after create(). I thought I read somewhere that create() makes an already prepared MediaPlayer. – John Glen Oct 01 '20 at 03:29
  • 1
    In my mind, the asynchronous calls are messages that arrive at some point after the state is reached. Also `while (!mediaPlayerWURI.isPrepared) { }` is code smell as you shouldn't have a dumb wait loop blocking any thread even if it was [a volatile boolean](https://stackoverflow.com/q/106591/295004). Additionally see: [Is Android MediaPlayer multithreaded?](https://stackoverflow.com/q/48207214/295004) – Morrison Chang Oct 01 '20 at 04:15
  • The loop is a band aid until I set a boolean in the MediaPlayer wrapper and check it in onPrepared(). Thank you for the links. Is Android MediaPlayer multithreaded answered my question. – John Glen Oct 01 '20 at 14:23

1 Answers1

0

The problem was that while waiting for the MediaPlayer to be prepared while (!mediaPlayerWURI.isPrepared) { }, I was hogging the UI thread, which is the same thread that onPrepared() uses.

To fix this, I had to stop hogging the UI thread. I added a boolean to my MediaPlayerWURI wrapper class that indicates to play the MediaPlayer on prepared.

private final MediaPlayer mediaPlayer;
volatile boolean isPrepared;
volatile boolean shouldPlay;

synchronized public void shouldStart(boolean shouldPlay){
    if(shouldPlay && isPrepared){
        mediaPlayer.start();
    } else {
        this.shouldPlay = shouldPlay;
    }
}

@Override
public void onPrepared(MediaPlayer mediaPlayer) {
    synchronized (mediaPlayerWURI) {
        mediaPlayerWURI.isPrepared = true;
        if (shouldPlay) {
            mediaPlayer.start();
            shouldPlay = false;
        }
    }
}
John Glen
  • 771
  • 7
  • 24