Update: This code is using an outdated version of the Cocos2D port. It’ll still work if you use the sample download linked at the end – but it’s using outdated API calls. Unfortunately I don’t have time to update the tutorial to the new release of Cocos2D. Sorry guys 🙁
Cocos2D for Android provides a very rudimentary sound system for playing background music, and simple sound effects. For the vast majority of games, this is plenty. However, if you have more advanced requirements you may need to look into either rolling out your own sound engine or sourcing something more complete elsewhere.
Overview
The Cocos2D ‘SoundEngine’ class provides all of the functionality you’ll need. It groups audio into two main groups: ‘Sound’ and ‘Effect’. Effects are the explosions, jump sounds, and other general effects you have within games. Sound is the background music and is the only audio type that can be paused and resumed. Sound effects should be kept under 5 seconds long, and ideally they should be under 3 seconds.
Android supports a wide range of different audio formats, you can view a complete list here. However, I’ve found that the SoundEngine doesn’t necessarily support all of these formats – so please do test your audio on a real device, preferably a few.
Let’s start coding!
Create a new Android project in your IDE of choice, IntelliJ Idea is my favourite, but Eclipse is very good also. You also need to download the latest source code for cocos2d-android-1, the downloadable jar doesn’t include the SoundEngine code. Include the source into your project – this also has the added advantage that you can poke around the code to see how everything works.
Also you need to add the two audio files in this zip, and put them in the ‘raw’ folder within the ‘resources’ folder. If you don’t have a raw folder, create it.
Before we get into any real coding, we’ll need to create a very rudimentary GUI to allow us to interact with the sound system. We’ll add two buttons to the main layout, one to play some background music and another to play a single sound effect:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:text="BG Music Toggle"
android:onClick="bgMusicClicked" />
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:text="Play Sound Effect"
android:onClick="sfxClicked" />
</LinearLayout>
Using SoundEngine
The SoundEngine offers functionality to pre-load audio, this is critical to ensure sound effects are played instantly. It also means we can use compressed audio formats such as mp3, without slowing down our game. Add the following field to the top of the main activity:
private Context _cocos2dContext;
Next add the following code to the onCreate() method:
_cocos2dContext = this;
// Preload background music
SoundEngine.sharedEngine().preloadSound(_cocos2dContext, R.raw.background_music_aac);
// Preload sound effect
SoundEngine.sharedEngine().preloadEffect(_cocos2dContext, R.raw.pew_pew_lei);
This code sets the context we’ll use for playing audio, and preloads the background audio, and the sound effect. We set the context to the current activity; this is because we’re not actually using Cocos2D in this demo. In your actual games you would use CCDirector.sharedDirector().getActivity() instead of this.
Now, create the event handlers for the two buttons we created:
public void bgMusicClicked(View button)
{
// Play the background music
SoundEngine.sharedEngine().playSound(_cocos2dContext, R.raw.background_music_aac, true);
}
public void sfxClicked(View button)
{
// Play the sound effect
SoundEngine.sharedEngine().playEffect(_cocos2dContext, R.raw.pew_pew_lei);
}
Give it a test!
It won’t stop playing!
OK I may have let you walk into this one – the background music plays even when the activity is ‘closed’ or otherwise not being used by the user. This is very handy if you’re making an mp3 player, but not so useful when making a game. Fortunately we can fix this problem by hooking into a few events Android offers. As a refresher, here is the Activity Lifecycle in Android:
We need to cater for the activity being obscured by another activity, being hidden, and being closed down. The onResume, onPause, and onDestroy methods should be plenty for our needs. In onResume we’ll need to resume playback of the background sound if it was playing. onPause will pause the sound (if any) ready for resuming. When onDestroy is called we’ll close everything down and free up any applicable resources.
First, add the following fields to the activity class:
private boolean _soundPlaying = false;
private boolean _soundPaused = false;
private boolean _resumeSound = false;
We need to populate these fields as necessary when the user wants to play / pause the background music. Replace the bgMusicClicked method with:
public void bgMusicClicked(View button)
{
// If we haven't started playing the sound - play it!
if (!_soundPlaying)
{
SoundEngine.sharedEngine().playSound(_cocos2dContext, R.raw.background_music_aac, true);
_soundPlaying = true;
}
else
{
// We've loaded the sound, now it's just a case of pausing / resuming
if (!_soundPaused)
{
SoundEngine.sharedEngine().pauseSound();
_soundPaused = true;
}
else
{
SoundEngine.sharedEngine().resumeSound();
_soundPaused = false;
}
}
}
Now we only play the sound once, rather than starting fresh each time. We pause / resume the sound depending on whether it’s currently playing or not – your typical toggle pattern.
Now we’ll create the onPause handler:
@Override
public void onPause()
{
super.onPause();
// If the sound is loaded and not paused, pause it - but flag that we want it resumed
if (_soundPlaying && !_soundPaused)
{
SoundEngine.sharedEngine().pauseSound();
_soundPaused = true;
_resumeSound = true;
}
else
_resumeSound = false; // No sound playing, don't resume
}
In this method we check if the sound is playing, and if it is – whether it’s paused or not. If the sound is playing but it’s not paused, we pause it and request that it’s resumed at the earliest opportunity. If the sound isn’t playing or it’s paused, then we request that the sound isn’t resumed when the activity is.
Now it’s time for the onResume method:
@Override
public void onResume()
{
super.onResume();
// Resume playing sound only if it's loaded, paused and we want to resume it
if (_soundPlaying && _soundPaused && _resumeSound)
{
SoundEngine.sharedEngine().resumeSound();
_soundPaused = false;
}
}
We check if the sound is playing, it’s paused, and a request is pending to resume the sound. If all of this is true (phew, that’s a lot of checking!) we resume the sound.
At this point we can run the app, and the audio will be handled properly. The user won’t be irritated by music playing when it shouldn’t, and our game can resume seamlessly when the user comes back. However, we haven’t yet implemented the onDestroy method – we never clean everything up.
Cleaning up our mess
Strictly speaking, cleaning up isn’t necessary in this app. Android will automatically reclaim the consumed resources when the app is destroyed. However, it’s always good to clean everything up anyway, if only so you know how to do it when it really is important. Add the following destroy method:
@Override
public void onDestroy()
{
super.onDestroy();
// Clean everything up
SoundEngine.sharedEngine().realesAllSounds();
SoundEngine.sharedEngine().realesAllEffects();
// Completely shut down the sound system
SoundEngine.purgeSharedEngine();
}
First we release all of the preloaded sounds and effects. You can selectively release individual sounds and effects if you need to. If your game is made up of multiple scenes with different audio in each, it’s a very good idea to release the audio you’re not using to save on memory.
Finally we shut down the sound engine itself. This isn’t really necessary, but it’s here for you to see how it can be done. If you have completely different areas in your game, you could feasibly want to have no trace of the SoundEngine between those scenes since the SoundEngine is a singleton by design.
And we’re done!
And that’s it; you now know everything you need to add basic audio to your Android Cocos2D games.
You can download the sample project here.
If you want more Cocos2D, don’t forget to check out the basic tutorial series starting here.