How to fix Bug #1303 (SimpleAudioEngine::playEffect() has not effect if it is played without preloading)

Hi, I’m trying to port very simple game for small children from iOS to Android (our company uses cocos2d-2.0-rc2-x-2.0.1 right now). I’ve hit the problem with playing sound effects and I’m trying to fix it.
I found Bug #1303 (SimpleAudioEngine::playEffect() has not effect if it is played without preloading) and I wonder what needs to be done to fix this bug.
http://www.cocos2d-x.org/issues/1303

I found several articles on the Internet about SoundPool.setOnLoadCompleteListener() but all of them seems to copy-paste the same trivial code:

soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int mySoundId, int status) {
loaded = true;
}
});

Changing the variable from false to true does not mean that sound will be played, does it?

As I understand it for cocos there should be some sort of queue where not-yet-loaded sounds wait for load to complete and inside OnLoadCompleteListene callback cocos should start playing queued sound. Is it correct assumption?

I see the comment for that bug about “playing mode is loop”. I think the “queue” I talked about above should carry such information as well?

I’m very new to cocos as an user and as a developer of cocos itself but I’d like to help fixing this issue. Can anybody lead me on this?

Hi guys,

I have first working version of the patch for this issue. See below. This is my the very first Java code so please don’t blame me too much. I’d like to hear what is correct here and what’s wrong and what should I change to be able to submit this patch to cocos2dx.

diff --git a/proj.android/src/org/cocos2dx/lib/Cocos2dxSound.java b/proj.android/src/org/cocos2dx/lib/Cocos2dxSound.java
index e7061f7..8662f45 100755
--- a/proj.android/src/org/cocos2dx/lib/Cocos2dxSound.java
+++ b/proj.android/src/org/cocos2dx/lib/Cocos2dxSound.java
@@ -39,7 +39,7 @@ import android.util.Log;
  *
  */

-public class Cocos2dxSound {
+public class Cocos2dxSound implements SoundPool.OnLoadCompleteListener {
    private Context mContext;
    private SoundPool mSoundPool;
    private float mLeftVolume;
@@ -52,6 +52,14 @@ public class Cocos2dxSound {

    private HashMap mPathSoundIdMap;

+   // map of effects scheduled to play after initial load
+   // key is SoundId, value is parameters from playEffect() call
+   class ScheduledEffect {
+       String path;
+       boolean isLoop;
+   }
+   private HashMap mScheduledSoundIdMap;
+   
    private static final String TAG = "Cocos2dxSound";
    private static final int MAX_SIMULTANEOUS_STREAMS_DEFAULT = 5;
    private static final float SOUND_RATE = 1.0f;
@@ -66,6 +74,28 @@ public class Cocos2dxSound {
        initData();
    }

+   public void onLoadComplete(SoundPool soundPool, int sampleId, int status){
+       if (soundPool != mSoundPool) {
+           // it's not our pool
+           return;
+       }
+       
+       if (status != 0) {
+           // status == 0 means success of the load operation
+           Log.e(TAG, "onLoadComplete: sample is not loaded");
+           return;
+       }
+       
+       ScheduledEffect effect = this.mScheduledSoundIdMap.get(sampleId);
+       if (effect == null) {
+           // this sound is not scheduled?
+           return;
+       }
+       
+       this.mScheduledSoundIdMap.remove(sampleId);
+       playEffect(effect.path, effect.isLoop);
+   }
+   
    public int preloadEffect(String path){
        Integer soundID = this.mPathSoundIdMap.get(path);

@@ -76,7 +106,7 @@ public class Cocos2dxSound {

        return soundID;
    }
-   
+       
    public void unloadEffect(String path){
        // stop effects
        ArrayList streamIDs = this.mPathStreamIDsMap.get(path);
@@ -92,7 +122,7 @@ public class Cocos2dxSound {
        this.mSoundPool.unload(soundID);
        this.mPathSoundIdMap.remove(path);
    }
-   
+
    public int playEffect(String path, boolean isLoop){
        Integer soundId = this.mPathSoundIdMap.get(path);
        int streamId = INVALID_STREAM_ID;
@@ -128,7 +158,15 @@ public class Cocos2dxSound {
             *    Because the method is supported from 2.2, so I can't use
             *    it here.
             */
-           playEffect(path, isLoop);
+           //playEffect(path, isLoop);
+
+           // schedule effect for playing after load
+           ScheduledEffect effect = new ScheduledEffect();
+           effect.path = path;
+           effect.isLoop = isLoop;
+           this.mScheduledSoundIdMap.put(soundId, effect);
+           
+           return INVALID_SOUND_ID;
        }

        return streamId;
@@ -237,7 +275,9 @@ public class Cocos2dxSound {
    private void initData(){
        this.mPathStreamIDsMap = new HashMap>();
        this.mPathSoundIdMap = new HashMap();
+       this.mScheduledSoundIdMap = new HashMap();
        mSoundPool = new SoundPool(MAX_SIMULTANEOUS_STREAMS_DEFAULT, AudioManager.STREAM_MUSIC, SOUND_QUALITY);
+       mSoundPool.setOnLoadCompleteListener(this);

        this.mLeftVolume = 0.5f;
        this.mRightVolume = 0.5f;

Also I have a question to cocos2dx developers.

According to documentation on SoundPool.play that method could return value 0 if sound failed to play (I suppose that’s because it’s not loaded to memory yet).
Should we check the streamId value in playEffect? It seems cocos shouldn’t add streamId equal to zero to mPathStreamIDsMap

Is it correct?

This bug has been fixed.
What’s your cocos2d-x version?

Thank you Minggo. I should have checked the latest code from github first.
Currently I’m using cocos2d-2.0-rc2-x-2.0.1.zip
I guess it’s time for me to upgrade.