Computer scienceMobileAndroidAndroid CoreData structures

Playing audio with MediaPlayer

8 minutes read

We all love to listening music and watching videos with our phones, but how does it work? The answer is multimedia players. There are a few of them, they differ in complexity and provide different possibilities for customization. The most popular one that is not part of the Android framework is ExoPlayer, but it's too complex for us now, so it will be better to take a look at the MediaPlayer API first, which is part of the Android framework.

In this topic, we will discuss only the audio capabilities of MediaPlayer.

How to play a song

MediaPlayer supports different data sources such as:

  • App resources

  • Local URIs using a Content Resolver

  • External URLs

Here is the easiest way to obtain media from app resources:

var mediaPlayer = MediaPlayer.create(applicationContext, R.raw.song)

mediaPlayer.setAudioAttributes(
    AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
)

mediaPlayer.start()


We create an instance with the MediaPlayer.create() method and start playback with the start() method. We get our file from R.raw, which is located in src/main/res/raw and can contain files without specifying any particular type. However, the file should be properly encoded and formatted in one of the supported formats.

You can use setAudioAttributes() to set attributes that provide information about the audio stream. The two attributes we used in the snippet are:

  • Usage, which describes why you are playing a sound, for example, USAGE_MEDIA and USAGE_ALARM.

  • ContentType, which describes the type of content you're playing, for example CONTENT_TYPE_MOVIE and CONTENT_TYPE_MUSIC.

You can explore other types of AudioAttributes if you are interested. While MediaPlayer can work without specifying the content type, it's better to include them. The platform or audio framework can use this information to adjust volume levels or apply post-processing to improve audio quality.

To play media from an internal URI, you can use this snippet:

val myUri: Uri = .... // initialize Uri here

val mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(applicationContext, myUri)
    prepare()
    start()
}

Here we have two new methods: setDataSource() and prepare(). We'll discuss prepare() later when we cover MediaPlayer states. Let's look at setDataSource() first:

We use setDataSource() to specify which content to play. When using an internal URI, we need to include the Context.

To play media from a remote URL, the code looks similar, but you don't need to include Context:

val url = "http://........" // your URL here

val mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(url)
    prepare()
    start()
}

When using this method to obtain media, the prepare() call might take a long time. Consider using prepareAsync(), which we'll discuss later in this topic.

This method requires an internet connection, so add this permission to your manifest file:

<uses-permission android:name="android.permission.INTERNET" />

Managing the state

An important aspect of MediaPlayer that you should keep in mind is that it is state-based. MediaPlayer has an internal state that you should always be aware of when writing code. Some operations are valid only when the player is in a specific state. There is a diagram of possible MediaPlayer states and the methods that can move from one state to another:

MediaPlayer state diagram

However, this scheme is complex and contains a lot of information, so let's examine each of these states.

  • Idle. MediaPlayer is in this state when you first create it or call reset().

  • Initialized. MediaPlayer reaches this state when you set a data source. If you try to set a data source while not in the idle state, an IllegalStateException occurs.

  • Prepared. Before you can play any media, you need to prepare MediaPlayer by calling prepare(). You can also call prepareAsync(), which we will discuss later in the topic. However, if you use MediaPlayer.create() for creation as we did in the first example, your MediaPlayer will start in the prepared state.

  • Started. Once your MediaPlayer is prepared, you can call start() to reach this state. While the media plays, the MediaPlayer remains in the started state.

  • Paused. You can pause your media by calling pause().

  • Stopped. MediaPlayer enters this state when the media is stopped. To play it again, you need to prepare it first.

  • PlaybackCompleted. When the media finishes playing, MediaPlayer enters this state. You can play the media again by calling start() and set a callback via setOnCompletionListener().

  • End. MediaPlayer moves to this state after you call release(), which we will discuss later. In this state, you cannot play or pause media.

  • Error. If you try to perform a playback control operation in an illegal state, MediaPlayer enters this state. You can catch the error by setting a callback via setOnErrorListener()

As you can see, there are many different states and possible transitions between them. However, some methods won't change the state. For example, seekTo(), which searches the media to the given time code, doesn't affect the state.

Using MediaPlayer efficiently

Using MediaPlayer can be straightforward, but you need to follow some important guidelines to use it correctly. Since calling prepare() can take a long time to fetch and decode data, you should never call it from the UI thread. This could cause the UI to hang and even trigger an ANR. Instead, use prepareAsync() to start preparing your data in the background. When the preparation finishes, it will trigger a callback that you can configure with setOnPreparedListener()

This example shows how to start asynchronous preparation and play the media as soon as it's ready:

val url = ...   
                       
val mediaPlayer = MediaPlayer().apply {
    setDataSource(url)     

    setOnPreparedListener {            
        start()                        
    }            
     
    prepareAsync()                           
}                                      

As you remember, when we create MediaPlayer with MediaPlayer.create(), we should not call prepare() because the builder does it for us. However, this means we cannot use prepareAsync with it!

Keep in mind that MediaPlayer uses valuable system resources. You should never create more instances than necessary. When you finish using MediaPlayer, call release() to free all allocated system resources. Do the same when your Activity receives an onStop() call since there's no reason to keep it running while your activity isn't interacting with the user (unless you want to play media in the background, which we won't cover here). When the activity resumes or restarts, you can create and prepare a new MediaPlayer and restore its state.

Here's how to release and nullify your MediaPlayer when you're done with it:

mediaPlayer?.release()
mediaPlayer = null

We didn't use a nullable MediaPlayer in the previous examples for brevity, but nullifying it is good practice.

For example, if you don't release MediaPlayer in onStop() and the user rotates their device between portrait and landscape, you might quickly use up all system resources because each orientation change creates a new MediaPlayer that never gets released.

Conclusion

To play media on Android, you can use the MediaPlayer API. MediaPlayer can obtain files from app resources, local URIs, or download them from an external URL. Although using MediaPlayer might seem straightforward, always remember these important points:

  • Correctly manage MediaPlayer states.

  • MediaPlayer can take a long time to download resources, so avoid preparing it in the UI thread.

  • Remember to release resources when you no longer need them.

10 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo