Streaming Music

Small sound effects fit into the limited heap memory an Android application gets from the operating system. Bigger audio files containing longer music pieces don't. For this reason we need to stream the music to the audio hardware, which means that we only read-in a small chunk at a time, enough to decode it to raw PCM data and throw that at the audio chip.

That sounds intimidating. Luckily there's the MediaPlayer class, which handles all that business for us. All we need to do is point it at the audio file and tell it to play it back.

Instantiating the MediaPlayer class is dead simple: MediaPlayer mediaPlayer = new MediaPlayer();

Next we need to tell the MediaPlayer what file to play back. That's again done via an AssetFileDescriptor:

AssetFileDescriptor descriptor = assetManager.openFd("music.ogg"); mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());

There's a little bit more going on here than in the SoundPool case. The MediaPlayer.setDataSource() method does not directly take an AssetFileDescriptor. Instead it wants a FileDescriptor, which we get via the

AssetFileDescriptor.getFileDescriptor() method. Additionally we have to specify the offset and the length of the audio file. Why the offset? Assets are all stored in a single file in reality. For the MediaPlayer to get to the start of the file we have to provide it with the offset of the file within the containing asset file.

Before we can start playing back the music file, we have to call one more method that prepares the MediaPlayer for playback:

mediaPlayer.prepare();

This will actually open the file and check whether it can be read and played back by the MediaPlayer instance. From here on we are free to play the audio file, pause it, stop it, set it to be looped, and change the volume.

To start the play back we simply call the following method: mediaPlayer.start();

Note that this can only be called after the MediaPlayer.prepare() method has been called successfully (you'll notice if it throws a runtime exception).

We can pause the playback after having started it with a call to the pause() method: mediaPlayer.pause();

Calling this method is again only valid if we have successfully prepared the MediaPlayer and started playback already. To resume a paused MediaPlayer, we can call the MediaPlayer.start() method again without any preparation.

To stop the playback we call the following method: mediaPlayer.stop();

Note that when we want to start a stopped MediaPlayer, we have to first call the MediaPlayer.prepare() method again.

We can set the MediaPlayer to loop the playback with the following method: mediaPlayer.setLooping(true);

To adjust the volume of the music playback, we can use this method: mediaPlayer.setVolume(1, 1);

This will set the volume of the left and right channels. The documentation does not specify what range these two arguments have to be in. From experimentation, the valid range seems to be 0 to 1.

Finally, we need a way to check whether the playback has finished. We can do this in two ways. For one, we can register an OnCompletionListener with the MediaPlayer that will be called when the playback has finished:

mediaPlayer.setOnCompletionListener(listener);

If we want to poll for the state of the MediaPlayer, we can use the following method instead:

boolean isPlaying = mediaPlayer.isPlaying();

Note that if the MediaPlayer is set to loop, none of the preceding methods will indicate that the MediaPlayer has stopped.

Finally, if we are done with that MediaPlayer instance, we make sure all the resources it takes up are released by calling the following method:

mediaPlayer.release();

It's considered good practice to always do this before throwing away the instance.

In case we didn't set the MediaPlayer to be looped and the playback has finished, we can restart the MediaPlayer by calling the MediaPlayer.prepare() and MediaPlayer.start() methods again.

Most of these methods work asynchronously, so even if you called MediaPlayer.stop() the MediaPlayer.isPlaying() method might return for a short period after that. It's usually nothing we worry about too much. In most games we set the MediaPlayer to be looped and stop it when the need arises (e.g., when we switch to a different screen that we want other music to be played on).

Let's write a small test activity where we play back a sound file from the assets/ directory in looping mode. This sound effect will be paused and resumed according to the activity life cycle; when our activity gets paused, so should the music, and when the activity is resumed, the music playback should pick up from where it left off. Listing 410 shows you how that's done.

Listing 4-10. MediaPlayerTest.java; Playing Back Audio Streams package com.badlogic.androidgames;

import java.io.IOException;

import android.app.Activity;

import android.content.res.AssetFileDescriptor;

import android.content.res.AssetManager;

import android.media.AudioManager;

import android.media.MediaPlayer;

import android.os.Bundle;

import android.widget.TextView;

public class MediaPlayerTest extends Activity { MediaPlayer mediaPlayer;

^Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView textView = new TextView(this); setContentView(textView);

setVolumeControlStream(AudioManager.STREAM_MUSIC); mediaPlayer = new MediaPlayer(); try {

AssetManager assetManager = getAssets();

AssetFileDescriptor descriptor = assetManager.openFd("music.ogg"); mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()); mediaPlayer.prepare(); mediaPlayer.setLooping(true); } catch (IOException e) {

textView.setText("Couldn't load music file, " + e.getMessage()); mediaPlayer = null;

^Override protected void onResume() { super.onResume(); if (mediaPlayer != null) { mediaPlayer.start();

protected void onPause() { super.onPause(); if (mediaPlayer != null) { mediaPlayer.pause(); if (isFinishing()) { mediaPlayer.stop(); mediaPlayer.release();

We keep a reference to the MediaPlayer in the form of a member of our activity. In the onCreate() method we simply create a TextView for outputting any error messages, as always.

Before we start playing around with the MediaPlayer, we make sure the volume controls actually control the music stream. Having that set up, we instantiate the MediaPlayer. We fetch the AssetFileDescriptor from the AssetManager for a file called music.ogg located in the assets/ directory, and set it as the data source of the MediaPlayer. All that's left is preparing the MediaPlayer instance and setting it to loop the stream. In case anything goes wrong, we set the mediaPlayer member to null so we can later determine whether loading was successful or not. Additionally we output some error text to the TextView.

In the onResume() method we simply start the MediaPlayer (if creating it was successful). The onResume() method is the perfect place to do this, as it is called after onCreate() and after onPause(). In the first case it will start the playback for the first time; in the second case it will simply resume the paused MediaPlayer.

The onResume() method pauses the MediaPlayer. If the activity is going to be killed, we stop the MediaPlayer and then release all its resources.

If you play around with this, make sure to also test out how it reacts to pausing and resuming the activity by either locking the screen or temporarily switching to the home screen. When resumed, the MediaPlayer will pick up from where it left when it was paused.

Here are couple of things to remember:

The methods MediaPlayer.start(), MediaPlayer.pause(), and MediaPlayer.resume() can only be called in certain states as just discussed. Never try to call them when you haven't prepared the MediaPlayer yet. Call MediaPlayer.start() only after preparing the MediaPlayer or when you want to resume it after you've explicitly paused it via a call to MediaPlayer.pause().

MediaPlayer instances are pretty heavyweight. Having many of them instanced will take up considerable resources. We should always try to have only one for music playback. Sound effects are better handled with the SoundPool class.

Remember to set the volume controls to handle the music stream, or else your players won't be able to adjust the volume of your game.

We are almost done with this chapter, but one big topic still lies ahead of us: 2D graphics.

0 0

Post a comment