Search code examples
androidaudiotrack

ANDROID: How would I add a delay in a for loop which plays an audiofile?


After a particular button is clicked, I want to have it so that my audio would play fifteen times, and have the progress bar increment each time. I had also envisioned having a delay in between each time the audio plays.

What's currently happening is that all of the beeps play back to back without delay, and after that, the progress bar gets incremented right to max straight away. Using Handler somehow doesn't manage to delay the audio playing.

I'm a beginner in app development, so excuse the shoddy code:

 public void click1(View view) throws IOException {
    int i;
    //  ProgressBar1.setProgress(0);
    for (i = 1; i < 16; i = i + 1)

    {

        int secs = 2; // Delay in seconds
        Utils.delay(secs, new Utils.DelayCallback() {
            @Override
            public void afterDelay() throws IOException {
                // Do something after delay
                PlayShortAudioFileViaAudioTrack(500, 1);

                ProgressBar1.incrementProgressBy(1);
            }
        });
    }
}

Here's the delay code:

public class Utils {
public interface DelayCallback{
    void afterDelay() throws IOException;
}

public static void delay(int secs, final DelayCallback delayCallback){
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            try {
                delayCallback.afterDelay();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }, secs * 1000); // afterDelay will be executed after (secs*1000) milliseconds.
}
}

The function that plays audio is

    public void PlayShortAudioFileViaAudioTrack(int f, double duration) throws IOException
{   int sampleRate = 48000;     // Samples per second
    //double duration = 2.0;
    long numFrames = (long)(duration * sampleRate);
    long frameCounter = 0;

    int intSize = android.media.AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_FLOAT);
    AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_FLOAT, intSize, AudioTrack.MODE_STREAM);

    float[] buffer = new float[intSize];

    while (frameCounter < numFrames)
    {
        long remaining = numFrames - frameCounter;
        int toWrite = (remaining > intSize) ? intSize : (int) remaining;

        for (int s=0 ; s<toWrite ; s++, frameCounter++)
        {
            buffer[s] = (float)Math.sin(2.0 * Math.PI * f * frameCounter / sampleRate);
            // buffer[1][s] = Math.sin(2.0 * Math.PI * 500 * frameCounter / sampleRate);
        }

        if (at!=null) {
                         // Write the byte array to the track
            at.play();
            at.write(buffer, 0, intSize,  AudioTrack.WRITE_BLOCKING);

        }
        else
            Log.d("TCAudio", "audio track is not initialised ");
    }

    at.stop();
    at.release();

}

Changing the audiotrack mode to NON-BLOCKING from BLOCKING results in the audio just playing once, and the progress bar still shooting up to full immediately.


Solution

  • To solve your problem, you can use AsynkTask<> like this:

    Create a subclass of AsynkTask<> in your Activity to handle the delayed action and the updates of the progressbar.

    Then in your click1()-method you just have to create a new instance of your AsyncTask subclass and execute it. You can give it the number of cycles on the call of execute(). The following code should work:

    ...
    ProgressBar1.setMax(16); // to get 16 cycles like in your example
    ...
    
    public void click1(View view) throws IOException {
    
        int max = ProgressBar1.getMax();
        new MyTask().execute(max);
    }
    
    class MyTask extends AsyncTask<Integer, Integer, Void> {
        @Override
        protected Void doInBackground(Integer... params) {
            for (int i=0 ; i <= params[0]; i++) {
                try {
                    Thread.sleep(secs * 1000);
                    publishProgress(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    
        @Override
        protected void onPreExecute() {
            //do something before execution
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            //do your delayed stuff
            PlayShortAudioFileViaAudioTrack(500, 1);
            ProgressBar1.incrementProgressBy(1);
        }
    }