To complete this brief example, you will need three sound effects saved in the .ogg
format. So if you don't have them to hand, go back to the Creating sound FX section to make some. Alternatively, you can use the sounds provided in the Chapter5/ PlayingSounds/assets folder of the code bundle. As usual, you can view or use the already completed code at Chapter5/PlayingSounds/java/MainActivity.java and Chapter5/PlayingSounds/layout/activity_main.xml. Now perform the following steps:
1. Create a project with a blank activity, just as we did in Chapter 2, Getting Started with Android. Also, clean up the code by deleting the unnecessary
parts, although this isn't essential.
2. Create three sound files and save them as sample1.ogg, sample2.ogg, and sample3.ogg.
3. In the main folder in the Project Explorer window, we need to add a folder called assets. So in the Project Explorer window, right-click on the main folder and navigate to New | Directory. Type assets in the New Directory dialog box.
4. Now copy and paste the three sound files to the newly created assets folder.
Alternatively, select the three files, right-click on them, and click on Copy. Then click on the assets folder in the Android Studio Project Explorer. Now right-click on the assets folder and click on Paste.
5. Open activity_main.xml in the editor window and drag three button
widgets onto your UI. It doesn't matter where they are or how they are
aligned. When you look at the id property in the Properties window for any of our three new buttons, you will notice that they have automatically been assigned id properties. They are button, button2, and button3. As we will see, this is just what we need.
6. Let's enable our activity to listen to the buttons being clicked by implementing
onClickListener as we have done in all our other examples with buttons. Open MainActivity.java in the editor window. Replace the public class MainActivity extends Activity { line with the following line of code: public class MainActivity extends Activity implements View. OnClickListener {
7. As before, we get an unsightly red underline on our new line of code. The last time this happened, we typed in the empty body of the onClick method that we must implement and all was well. This time, because we already know what is going on here, we will learn a shortcut. Hover your mouse cursor over the error and right-click on it. Now click on Generate... and then select Implement methods.... In the Select Methods To Implement dialog box, onClick(View):void will already be selected:
8. Select this option by clicking on OK. Now scroll to the bottom of your code and see that Android Studio has very kindly implemented the onClick method for you and the error is also gone.
9. Type this code after the MainActivity declaration to declare some variables for our sound effects:
private SoundPool soundPool; int sample1 = -1;
int sample2 = -1; int sample3 = -1;
10. Type this code in the onCreate method to load our sounds into memory: soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC,0); try{
//Create objects of the 2 required classes AssetManager assetManager = getAssets(); AssetFileDescriptor descriptor;
//create our three fx in memory ready for use descriptor = assetManager.openFd("sample1.ogg"); sample1 = soundPool.load(descriptor, 0); descriptor = assetManager.openFd("sample2.ogg"); sample2 = soundPool.load(descriptor, 0); descriptor = assetManager.openFd("sample3.ogg"); sample3 = soundPool.load(descriptor, 0); }catch(IOException e){ //catch exceptions here }
11. Now add the code to grab a reference to the buttons in our UI and listen to clicks on them:
//Make a button from each of the buttons in our layout Button button1 =(Button) findViewById(R.id.button); Button button2 =(Button) findViewById(R.id.button2); Button button3 =(Button) findViewById(R.id.button3); //Make each of them listen for clicks
button1.setOnClickListener(this); button2.setOnClickListener(this); button3.setOnClickListener(this);
12. Finally, type this code in the onClick method that we autogenerated: switch (view.getId()) {
case R.id.button://when the first button is pressed //Play sample 1
soundPool.play(sample1, 1, 1, 0, 0, 1); break;
//Now the other buttons case R.id.button2: soundPool.play(sample2, 1, 1, 0, 0, 1); break; case R.id.button3: soundPool.play(sample3, 1, 1, 0, 0, 1); break; }
Run the example on an emulator or on a real Android device. Notice that by clicking on a button, you can play any of your three sound samples at will. Of course, sounds can be played at almost any time, not just on button presses. Perhaps they can be played from a thread as well. We will see more sound samples when we implement the memory game later in the chapter.
This is how the code works. We started off by setting up a new project in the usual way. In steps 2 to 5, however, we created some sounds with Bfxr, created an assets
folder, and placed the files within it. This is the folder where Android expects to find sound files. So when we write the code in the next steps that refers to the sound files, the Android system will be able to find them.
In steps 6 to 8, we enabled our activity to listen to button clicks as we have done several times before. Only this time, we got Android Studio to autogenerate the onClick method.
Then we saw this code:
private SoundPool soundPool;
First, we create an object of the SoundPool type, called soundPool. This object will be the key to making noises with our Android device. Next, we have this code:
int sample1 = -1; int sample2 = -1; int sample3 = -1;
The preceding code is very simple; we declared three int variables. However, they serve a slightly deeper purpose than a regular int variable. As we will see in the next block of code we analyze, they will be used to hold a reference to a sound
file that is loaded into memory. In other words, the Android system will assign a number to each variable that will refer to a place in memory where our sound file
will reside.
We can think of this as a location in our variable warehouse. So we know the name of the int variable, and contained within it is what Android needs to find our sound. Here is how we load our sounds into memory and use the references
we've just been discussing.
Let's break the code in step 10 into a few parts. Take a close look and then we will
examine what is going on:
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC,0);
Here, we initialize our soundPool object and request up to 10 simultaneous streams of sound. We should be able to really mash the app buttons and get a sound every time. AudioManager.STREAM_MUSIC describes the type of stream. This is typical for applications of this type. Finally, the 0 argument indicates we would like default quality sound.
Now we see something new. Notice that the next chunk of code is wrapped into two blocks, try and catch. This means that if the code in the try block fails, we want the code in the catch block to run. As you can see, there is nothing but a comment in the catch block.
We must do this because of the way the SoundPool class is designed. If you try to write the code without the try and catch blocks, it won't work. This is typical of
Java classes involved in reading from files. It is a fail-safe process to check whether the file is readable or even whether it exists. You could put a line of code to output to the console that an error has occurred.
If you want to experiment with try/catch, then put a line of code to output a message in the catch block and remove one of the sound
files from the assets folder. When you run the app, the loading will fail
We will throw caution to the wind because we are quite sure that the files will be there and will work . Let's examine what is inside the try block. Take a close look at the following code and then we will dissect it:
try{
//Create objects of the 2 required classes AssetManager assetManager = getAssets(); AssetFileDescriptor descriptor;
//create our three fx in memory ready for use descriptor = assetManager.openFd("sample1.ogg"); sample1 = soundPool.load(descriptor, 0); descriptor = assetManager.openFd("sample2.ogg"); sample2 = soundPool.load(descriptor, 0); descriptor = assetManager.openFd("sample3.ogg"); sample3 = soundPool.load(descriptor, 0); }catch(IOException e){ //catch exceptions here }
First, we create an object called assetManager of the AssetManager type and an AssetFileDescriptor object called descriptor. We then use these two objects
combined to load our first sound sample like this:
descriptor = assetManager.openFd("sample1.ogg"); sample1 = soundPool.load(descriptor, 0);
We now have a sound sample loaded in memory and its location saved in our int variable called sample1. The first sound file, sample1.ogg, is now ready to use. We perform the same procedure for sample2 and sample3 and we are ready to make some noise!
In step 11, we set up our buttons, which we have seen several times before. In step 12, we have our switch block ready to perform a different action depending upon
which button is pressed. You can probably see that the single action each button
takes is the playing of a sound. For example, Button1 does this:
This line of code plays the sound that is loaded in memory at the location referred to by int sample1.
The arguments of the method from left to right define the
following: the sample to play, left volume, right volume, priority
over other playing sounds, loop or not, rate of playback. You
can have some fun with these if you like. Try setting the loop argument to 3 and the rate argument to perhaps 1.5.
We handle each button in the same way. Now let's learn something serious.