Coders' Guidebook

How to play audio using the MediaPlayer class and Kotlin

This tutorial will explore how to play and pause audio files in an Android app using MediaPlayer. The first half of this tutorial will focus on how to play audio files which are included within the app itself, while the second half will discuss how to play external audio files and handle errors.

play-pause-buttons.png

Importing audio files

Sound effects and music that will be used in the app first must be imported into the Android Studio project. To do this, navigate through Project > app then right-click the res folder. Select New > Android Resource Directory

android-resource-directory.png

Set the Directory name to 'raw' then press OK.

raw-directory.png

This will create a folder called raw, which is where you will store media files. For this tutorial, we will use an MP3 recording of running water (which you can download below) but you are welcome to use another audio file of your choosing.

water.mp3

Drag and drop the audio file from your computer into the raw folder. Note, you may also be asked to confirm that you wish to move the audio file over to the project.

add-audio.png

The water.mp3 file is now ready to be used.

Adding the play and pause buttons

In this section, we will add the play and pause buttons which will control playback. Open up the layout file where you want the buttons to go such as activity_main.xml (found by navigating through Project > app > res > layout). Open the file in Design view and locate the Palette. Search in the Buttons category for the ImageButton widget then drag and drop it into the layout.

image-button.png

A 'Pick a Resouce' window will invite you to select an image for the button. Select ic_media_play then press OK to add the button to the layout.

play-button.png

Select the Play button then refer to the Attributes panel. Set the id to 'playButton' and the onClick attribute to 'playSound'. Note the playSound value may not be recognised by Android Studio yet but this will be resolved shortly.

play-button-attributes.png

Add another ImageButton like before, except this time use ic_media_pause as the image, set the id to pauseButton and set the onClick attribute to pauseSound. NB: you may have to resolve missing constraints and missing contentDescription errors.

With the play and pause buttons now added to the layout, we can write the Kotlin code which makes the buttons operational.

Making the play and pause buttons operational

To make the buttons operational, open the Kotlin file which manages the layout e.g. MainActivity.kt (found by navigating through Project > app > java > com) then add the following import statements to the top of the file:

import android.media.MediaPlayer
import android.view.View

Next, declare the following variable before the onCreate function:

var mMediaPlayer: MediaPlayer? = null

The mMediaPlayer variable allows your app to utilise the MediaPlayer class and incorporate audio and video into your app.

Moving on, add the following functions after the onCreate function. We'll discuss what each function means in turn.

// 1. Plays the water sound
fun playSound(view: View) {
    if (mMediaPlayer == null) {
        mMediaPlayer = MediaPlayer.create(this, R.raw.water)
        mMediaPlayer!!.isLooping = true
        mMediaPlayer!!.start()
    } else mMediaPlayer!!.start()
}
    
// 2. Pause playback
fun pauseSound(view: View) {
    if (mMediaPlayer != null && mMediaPlayer!!.isPlaying) mMediaPlayer!!.pause()
}
   
// 3. {optional} Stops playback
fun stopSound(view: View) {
    if (mMediaPlayer != null) {
        mMediaPlayer!!.stop()
        mMediaPlayer!!.release()
        mMediaPlayer = null
    }
}
    
// 4. Closes the MediaPlayer when the app is closed
override fun onStop() {
    super.onStop()
    if (mMediaPlayer != null) {
        mMediaPlayer!!.release()
        mMediaPlayer = null
    }
}

The first function is responsible for playing the water sound. It begins by checking whether or not the mMediaPlayer variable has been assigned an audio file. If it hasn't, then the MediaPlayer.create(this, R.raw.water) command loads the water.mp3 file and the isLooping = true and start commands begin playing the sound on repeat. On the other hand, if the mMediaPlayer is not null, then this means it has already been assigned an audio file and we can simply resume playback using the start command.

The second function, pauseSound, will only run if the mMediaPlayer variable is not null (i.e. it has been assigned an audio file) and is currently playing audio. If both criteria are met then the pauseSound function will pause playback.

The third function, stopSound, is optional and is included here to show you how you would stop playback as opposed to pausing it. The stop command halts and resets playback of the audio file, while the release() command removes the audio file from the mMediaPlayer variable. Releasing the mMediaPlayer variable when playback stops is good practise because it stops the MediaPlayer class from using the device's memory and battery unnecessarily.

Finally, the onStop function will run when the activity or app is closed. It checks whether the mMediaPlayer variable has a media file assigned to it. If it does, then the function releases the MediaPlayer class from this file to conserve the device's memory and battery life.

And that's it! If you run the app now you should find you can play and pause the water sound on command.

play-pause-buttons.png

Play external audio files

The MediaPlayer class can also be configured to accept external data sources such as audio files from the user's device and content from the internet. For example, you load a song from the user's device as follows:

fun playContentUri(uri: Uri) {
    val mMediaPlayer = MediaPlayer().apply {
        setDataSource(application, uri)
        setAudioAttributes(AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build()
        )
        prepare()
    }
    mMediaPlayer.start()
}

The above code uses a method called playContentUri to initiate the playback of an audio file based on its Uri (a Uri is a piece of data which locates a file). In addition to loading the audio file using the setDataSource method, the above code also implements a method called setAudioAttributes. The setAudioAttributes method lets you describe the type of content you will be playing. This can be useful in several situations, such as when other apps and hardware interact with your app during playback. The attributes configuration used above specifies that the MediaPlayer will be used to play music. You can explore other attributes such as alarms, speech etc. in the official Android documentation.

Handle MediaPlayer errors

Things can go wrong with the MediaPlayer class from time to time, especially when you are playing external audio files. For example, if your app attempts to play a file which no longer exists then the app could crash if the error is not handled appropriately. One way to handle specific errors is to wrap the MediaPlayer code inside a try/catch block. The try/catch block will "try" a certain task (initiating playback) and "catch" any predefined exceptions (errors). There are two main ways to identify probable exceptions. First, you can test the app yourself and find which exceptions are output in the Logcat when the app crashes (see this tutorial for more information on reading the Logcat). Alternatively, you could read the Android documentation for the methods you are using and find out which exceptions (errors) the method may throw. For example, here is the documentation for the setDataSource method. One of the exceptions it can potentially throw is the IOException. An IOException is caused by an error reading/writing a file and so is likely to be thrown if the app tries to play an audio file which no longer exists. To catch this exception, you could write the following code:

fun playContentUri(uri: Uri) {
    var mMediaPlayer: MediaPlayer? = null
    try {
        mMediaPlayer = MediaPlayer().apply {
            setDataSource(application, uri)
            setAudioAttributes(AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build()
            )
            prepare()
        }
        mMediaPlayer.start()
    } catch (e: IOException) {
        mMediaPlayer = null
        mMediaPlayer?.release()
    }
}

The above code uses a try/catch block to attempt to load an audio file based on its Uri as described in the previous section. In the event that IOException error is thrown, the media player variable is set to null, which essentially removes any data that previously been applied, and the media player is then reset, which prepares the media player to be reused in a new playback attempt.

<<< Previous

Next >>>