Waiting for SoundPool to load before navigating to the app

I followed the tutorial on integrating SoundPool into my application and this is the code that was provided in the tutorial:

package com.example.soundpoolexample;

import android.app.Activity;
import android.media.AudioManager;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class SoundPoolExample extends Activity implements OnTouchListener {
    private SoundPool soundPool;
    private int soundID;
    boolean loaded = false;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        View view = findViewById(R.id.textView1);
        view.setOnTouchListener(this);
        // Set the hardware buttons to control the music
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        // Load the sound
        soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId,
                    int status) {
                loaded = true;
            }
        });
        soundID = soundPool.load(this, R.raw.sound1, 1);

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // Getting the user sound settings
            AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
            float actualVolume = (float) audioManager
                    .getStreamVolume(AudioManager.STREAM_MUSIC);
            float maxVolume = (float) audioManager
                    .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
            float volume = actualVolume / maxVolume;
            // Is the sound loaded already?
            if (loaded) {
                soundPool.play(soundID, volume, volume, 1, 0, 1f);
                Log.e("Test", "Played sound");
            }
        }
        return false;
    }
}

      

The problem is that I want to make sure the app only moves forward once the SoundPool is loaded successfully, and doesn't need to check if it's loaded before playing the sound every time. How should I do it?

I'm sure this is a cruel solution, but does something like this work after loading the sound file in onCreate ?:

...

    soundID = soundPool.load(this, R.raw.sound1, 1);
    while (!loaded) {}

...

      

I'm sure there is a better way.

+3


source to share


4 answers


You can have an elegant solution using Semaphore

, however try to avoid blocking onCreate()

as the application will not be responsive, the code here is just to illustrate the useSemaphore



public class SoundPoolExample extends Activity implements OnTouchListener {

public static final Semaphore semaphore = new Semaphore(0);

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
        @Override
        public void onLoadComplete(SoundPool soundPool, int sampleId,
                int status) {

            semaphore.release();
        }
    });
    soundID = soundPool.load(this, R.raw.sound1, 1);

    // Will block since there is not permits (the semaphore is initialized with 0)
    // then will continue once semaphore.release(); is called i.e. after loading is finished
    semaphore.acquire();
} 

// rest of your class

}

      

+1


source


I tried iTech semaphore solution and it didn't work. The app is sitting there waiting.

onCreate is called with the main thread. This thread creates a sound pool, starts the download of the sound, and then parks itself, waiting for the download to complete. It looks like it should work, right?

After some research, I found that SoundPool has undocumented behavior, always doing onLoadComplete with the main thread using the Looper / Handler mechanism. Since the main thread is parked, it cannot receive this event and the application freezes.

We can see how this works in the Android source code in SoundPool.java:



public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
{
    synchronized(mLock) {
        if (listener != null) {
            // setup message handler
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                mEventHandler = new EventHandler(mProxy, looper);
            } else if ((looper = Looper.getMainLooper()) != null) {
                mEventHandler = new EventHandler(mProxy, looper);
            } else {
                mEventHandler = null;
            }
        } else {
            mEventHandler = null;
        }
        mOnLoadCompleteListener = listener;
    }
}

      

This shows that it uses the default main looper if you don't have a looper of your own setup.

The simplest solution is to start the entire audio loading procedure on a new thread and immediately return from the main thread. Then you can use the main thread to show the loading screen if needed. When the sound is loaded, the main thread can receive the onLoadComplete call and signal the sound loading thread. The audio load thread can wait for this signal with a semaphore, or wait / notify on a shared object.

0


source


It's a little tricky to get it right. I handle a similar scenario as follows. In structure, it looks like this:

  • handler

    is created on the main thread.
  • handleMessage

    which takes care of custom notification of all downloadable sound samples.
  • A SoundPool

    is created on the main thread.
  • A new thread will start, which:

    4.1 Sets SoundPool.OnLoadCompleteListener

    which dispatches the custom notification message using the instance handler from the main thread.

    4.2 Uses a soundPool instance to emit asynchronous loading of sound samples ( load

    class method SoundPool

    )

Then there is a variable that contains the loading state of the sound samples. It can have three states: not loading, loading, loading.

The state of this variable is set to load in the stream from the 4th step above.

The state of this variable is set to load in handleMessage

when a custom notification message arrives.

This state variable is checked before playing any sound. If sound samples have not been loaded yet, a semi-full-screen dialog is displayed indicating that the sound samples are loaded and automatically disappear on load. The user cannot cancel the dialog. That is, programmatically from a method handleMessage

, calling the method of dismiss

this dialog, if shown. The dialog has a nice progress animation. This is specific to my application and it can be done differently for other applications.

Why is this so difficult, you might ask. The point is, the entire Android GUI API is an event machine, and by definition, your own code running on the main thread doesn't have to do any tedious procedure, because that would render the entire GUI inactive. In fact, when Android detects such cases, it logs them to logcat. For example, if I were using a semaphore, there would be no progress bar animation because the main GUI thread would be blocked. Using a semaphore in this way simply demonstrates the fact that you don't understand the architecture of the Android GUI, and this can lead to the application freezing if something goes wrong with the part that frees the semaphore.

Hope it helps.

0


source


I think that when using SoundPool, everyone should pay attention to two cases:

  • SoundPool is deprecated for API + 21, you should instead use SoundPool.Builder

  • If your SoundPool is running on the main thread, change to a different thread as it is blocking your UI
0


source







All Articles