Playing a different song during the game would be a nice change of pace, so let’s make a similar change to GameActivity.java. While we’re there, we’ll add a sound that plays when the game is over. First we need to declare the variable for the media player and a handler we’ll need later:
ticTacToev4/src/main/java/org/example/tictactoe/GameActivity.java
import android.media.MediaPlayer;
import android.os.Handler;
public class GameActivity extends Activity {
private MediaPlayer mMediaPlayer;
private Handler mHandler = new Handler(); // ...
}
Then we start and stop the music in onResume() and onPause() as before:
ticTacToev4/src/main/java/org/example/tictactoe/GameActivity.java
@Override
protected void onResume() { super.onResume();
mMediaPlayer = MediaPlayer.create(this, R.raw.frankum_loop001e); mMediaPlayer.setLooping(true);
mMediaPlayer.start(); }
@Override
protected void onPause() { super.onPause();
mHandler.removeCallbacks(null); mMediaPlayer.stop();
mMediaPlayer.reset(); mMediaPlayer.release();
String gameData = mGameFragment.getState(); getPreferences(MODE_PRIVATE).edit()
.putString(PREF_RESTORE, gameData) .commit();
Log.d("UT3", "state = " + gameData); }
Finally let’s change the reportWinner() method to play a little jingle when the game is over, depending on who won:
ticTacToev4/src/main/java/org/example/tictactoe/GameActivity.java
public void reportWinner(final Tile.Owner winner) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { mMediaPlayer.stop(); mMediaPlayer.reset(); mMediaPlayer.release(); } builder.setMessage(getString(R.string.declare_winner, winner)); builder.setCancelable(false); builder.setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { @Override
public void onClick(DialogInterface dialogInterface, int i) { finish();
} });
final Dialog dialog = builder.create(); mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mMediaPlayer = MediaPlayer.create(GameActivity.this, winner == Tile.Owner.X ? R.raw.oldedgar_winner
: winner == Tile.Owner.O ? R.raw.notr_loser : R.raw.department64_draw ); mMediaPlayer.start(); dialog.show(); } }, 500);
mGameFragment.initGame(); // Reset the board to the initial position }
If there’s music currently playing, we stop it with the MediaPlayer.stop() method. Then we set up a handler to run a piece of code after a delay of half a second (500ms). This code creates a new player, passing it the id of the sound to play. I picked three other clips from the freesound.org site for this purpose.
It Goes Ding When There’s Stuff
When you’re watching a TV show or movie, and something happens on screen that makes a noise, often the noise isn’t recorded as it happens. To make it sound better, it’s added in later during editing by the Foley artists. They put in all the clicks, whirs, footsteps, bangs, crashes, and thousands of other sounds during the feature. If they do their job right, you’ll never know they were there. So sit through the credits next time, and look for the Foley. In this example you’re going to be the Foley artist by adding sound effects to the Tic-Tac-Toe game. In particular we want the game to make a noise when the user selects a tile on the board or presses the button to reset the game pieces. For these sounds we’re going to use a different class better suited for short sound clips called SoundPool. Unlike MediaPlayer, a SoundPool can play multiple sounds at once. It keeps track of all the sounds and mixes them together.
We create a new SoundPool with the new SoundPool() constructor. Starting in Android 5.0 (Lollipop) we could use the SoundPool.Builder class, but since we want our app to run on earlier versions of Android, we need to use the old way. If the compiler gives you a warning message about this being deprecated, just ignore it. Don’t worry; it will still work.
ticTacToev4/src/main/java/org/example/tictactoe/GameFragment.java
import android.media.AudioManager;
import android.media.SoundPool;
public class GameFragment extends Fragment {
private int mSoundX, mSoundO, mSoundMiss, mSoundRewind;
private SoundPool mSoundPool;
private float mVolume = 1f; // ...
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes. setRetainInstance(true);
initGame();
mSoundPool = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);
mSoundX = mSoundPool.load(getActivity(), R.raw.sergenious_movex, 1); mSoundO = mSoundPool.load(getActivity(), R.raw.sergenious_moveo, 1);
mSoundMiss = mSoundPool.load(getActivity(), R.raw.erkanozan_miss, 1); mSoundRewind = mSoundPool.load(getActivity(), R.raw.joanne_rewind, 1); }
The SoundPool.load() method takes three parameters and returns the sound id of the loaded sound. The parameters are the current activity, the resource id of the raw sound file, and the priority of the sound.
This doesn’t actually play anything—for that we call the SoundPool.play() method. Modify the click listener for each small tile (the code that’s run when the tile is pressed) as follows:
ticTacToev4/src/main/java/org/example/tictactoe/GameFragment.java
private void initViews(View rootView) { // ...
inner.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View view) {
if (isAvailable(smallTile)) {
mSoundPool.play(mSoundX, mVolume, mVolume, 1, 0, 1f); makeMove(fLarge, fSmall);
think(); } else {
mSoundPool.play(mSoundMiss, mVolume, mVolume, 1, 0, 1f); }
} }); // ... }
private void think() { // ...
switchTurns();
mSoundPool.play(mSoundO, mVolume, mVolume, 1, 0, 1f); makeMove(move[0], move[1]);
switchTurns(); // ...
}
First we play the X sound to indicate the user has moved. Then a second later we play the O sound to indicate the computer has moved. If the user selected a tile that wasn’t a valid move, we play a different sound to indicate the user missed pressing the right tile.
After making these changes, be sure to test them out by playing a game and making all the different sounds happen.
By the way, SoundPool.play() is quite a versatile method. It takes the following six parameters:
soundID
a soundID returned by the load() function
leftVolume
left volume value (range = 0.0 to 1.0)
rightVolume
volume value (range = 0.0 to 1.0)
priority
stream priority (0 = lowest priority)
loop
loop mode (0 = no loop, -1 = loop forever)
rate
playback rate (1.0 = normal playback, range 0.5 to 2.0) Try playing around with the parameters to get different effects.
Fast-Forward >>
In this chapter, we covered playing audio clips using the Android SDK. We didn’t discuss recording because most programs won’t need to do that, but if you happen to be the exception, then look up the MediaRecorder class in the online documentation.5
In Chapter 7, Adding Animation, on page 101, we’ll close out the Tic-Tac-Toe example by learning some simple animation techniques that can make a big difference in the enjoyment of your Android programs. If you don’t need to do that right now, then you can skip ahead to Chapter 8, Write Once, Test Everywhere, on page 113 and learn about making your apps run on a variety of devices.