Pausing and resuming sound effects in cocos2D
Published on 4/06/2012cocos2d comes with a handy OpenAL wrapper called SimpleAudioEngine that helps simplify the act of playing background music and sound effects on the iPhone. Strangely, while the SimpleAudioEngine class offers users the ability to play and stop sound effects, it offers no option to pause or resume them.
To add pause and resume support to SimpleAudioEngine we have to dig into the belly of the CDSoundEngine class, which can be found in CocosDenshion.m. Let’s start by adding the following two methods:
- (void)pauseAllSounds { for (int i=0; i < sourceTotal_; i++) { ALint state; alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); if(state == AL_PLAYING) { alSourcePause(_sources[i].sourceId); } } alGetError(); } - (void)resumeAllSounds { for (int i=0; i < sourceTotal_; i++) { ALint state; alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); if(state == AL_PAUSED) { alSourcePlay(_sources[i].sourceId); } } alGetError(); }
Next, since the SimpleAudioEngine class keeps its reference to CDSoundEngine private, we’ll need to add the following methods to SimpleAudioEngine:
- (void)pauseAllEffects { [soundEngine pauseAllSounds]; } - (void)resumeAllEffects { [soundEngine resumeAllSounds]; }
And that’s it! Well, almost…
One last gotcha
You may notice that, occasionally, while playing and resuming sound effects, sounds may begin to seemingly rewind or to play at random. This is down to CDSoundEngine’s handling of source indexes. Without going into too much detail, CDSoundEngine takes steps to ensure that only a finite number of sounds can be played concurrently on the iPhone. When a sound is played, it’s assigned a referential index. The problem is that, by default, CDSoundEngine doesn’t take the AL_PAUSED state flag into consideration when recycling indexes.
Thankfully, by making the following change to CDSoundEngine’s _getSourceIndexForSourceGroup method, we can solve this problem:
alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState); if (thisSourceGroup->nonInterruptible) { // Check if this source is playing, if so it can't be interrupted if (sourceState != AL_PLAYING && sourceState != AL_PAUSED) { //Set start index so next search starts at the next position thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; break; } else { sourceIndex = -1; //The source index was no good because the source was playing } } else { if(sourceState != AL_PAUSED) { // Set start index so next search starts at the next position thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; break; } }
And that’s how I added sound effect pause and resume support to my cocos2d title Lumicon. Hopefully it helps developers in a similar situation.