Alex Collette
Alex Collette

Reputation: 1784

Wait for sound to finish before continuing

I am creating a loop that iterates through a string, and depending on the character that is present, plays a different sound. unfortunately, it doesn't wait for the sound to finish before continuing, which throws errors with the mediaplayer. my loop looks like this:

for (char ch : morsechars) {
            if (Character.toString(ch).equals(".") ) {
                Log.d("play: ", "dot");
                try {
                    startPlayback();
                } catch (Exception e){
                    e.printStackTrace();
                }
            } else if (Character.toString(ch).equals("-")){
                Log.d("play: ", "dash");
                try {
                    startPlayback();
                } catch (Exception e){
                    e.printStackTrace();
                }
            } 
        }

and my player looks like this:

public void startPlayback() throws Exception{
        play = MediaPlayer.create(this, dotFILE);
        play.start();
        isplaying = true;
        while (isplaying == true){

        }


        play.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            public void onCompletion(MediaPlayer play) {
                isplaying = false;
                play.stop();
                play.reset();
                play.release();
            }
        });
    }

Im not sure how to wait for the completion listener before moving on to the next character in the loop.

Upvotes: 0

Views: 2059

Answers (1)

Dave
Dave

Reputation: 4291

1) MediaPlayer isn't the right tool for this job. Look at SoundPool. It is intended for short sounds played repeatedly.

2) For your case, what you want isn't really a loop. The sounds are going to be played asynchronously, so you run into the situation where the calls to play pile up on top of each other. That will cause the native code of the MediaPlayer to exhaust the available resources.

What you want is a series of callbacks. The MediaPlayer has the completion callback which you are already using, but, while that appears to be exactly what is needed, it will not suffice for your use-case. The native code for MediaPlayer will not be able to release its resources fast enough for each subsequent call to play, and the same resource exhaustion will occur. That's just an unfortunate reality for how the MediaPlayer works.

SoundPool doesn't have that convenient completion callback, but in this case you probably only have two sound samples and you know how long they are. A simple timer delay will probably suffice. You can use Handler.postDelayed to achieve this effect. In each callback event, just play the next sound, advance whatever variable you are using to keep track of the current sound, and post the same callback again to play any remaining sounds. It is a sort of asynchronous loop.

UPDATE

To help understand what's going on, here is a bit from Android's documentation on Handler:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

So, if my guess about your code structure is close to correct, you would create a Handler object as a member of your Activity class. This will bind it to the GUI thread, which should be ok since you won't be doing much work there. You will also need a Runnable to act as the task that gets posted. Make it a member of your class also. And you will need your String containing the characters to be a member. And you will need an int to keep track of what character you are currently on.

private Handler playMorseHandler = new Handler();
private String morseChars = ".--...---"; // I don't know where you set this
private int charIndex = 0;
private Runnable playMorseTask = new Runnable() {
    if (morseChars == null ||
        charIndex < 0 ||
        charIndex >= morseChars.length()) {
        Log.w("whateverTag", "Basic assumptions failed");
        return;
    }
    char ch = morseChars.charAt(charIndex);
    long lengthOfClip = 0L;
    boolean postAgain = true;
    if (ch == '.') {
        Log.d("play: ", "dot");
        lengthOfClip = 300L; // I don't know how long your clips are
        try {
            startPlayback();
        } catch (Exception e){
            e.printStackTrace();
        }
    } else if (ch == '-') {
        Log.d("play: ", "dash");
        lengthOfClip = 500L; // I don't know how long your clips are
        try {
            startPlayback();
        } catch (Exception e){
            e.printStackTrace();
        }
    } else {
        Log.d("whateverTag", "unexpected input");
        postAgain = false;
    }
    ++charIndex;
    if (postAgain && charIndex < morseChars.length()) {
        playMorseHandler.postDelayed(this, lengthOfClip);
    }
};

Wherever you want to start playing the sounds, you simply call:

charIndex = 0;
playMorseHandler.post(playMorseTask);

The only thing left would be to make your startPlayback() method do the right thing, which is to use SoundPool instead of MediaPlayer. That is because, as I mentioned earlier, MediaPlayer will inevitably cause the system to run out of resources playing these short sounds so quickly together.

Upvotes: 1

Related Questions