Advertisement
  1. Code
  2. Mobile Development
  3. Android Development

Create a Music Player on Android: Song Playback

Scroll to top
Read Time: 7 min

In this series, we are creating a music player on Android using the MediaPlayer and MediaController classes. In the previous tutorial of this series, we learned how to get a list of songs stored on the user's external storage and display it to them.

In this tutorial, we will implement the functionality to play a song selected by the user. However, we want the song to keep playing even if the user isn't directly interacting with the app. We will have to implement a Service class to do this, and that's what we will do here.

Here is the final result of this series:

Android Music Player Final Result PreviewAndroid Music Player Final Result PreviewAndroid Music Player Final Result Preview

Creating a Service

You might remember from the previous tutorial that we added a service element to our AndroidManifest.xml file using the line below.

1
<service android:name="com.tutsplus.musicplayer.MusicService" />

We will now implement that service.

The first step involves the creation of a new class called MusicService in your application. This class will inherit from the base Service class. There are some additional interfaces that we have to implement. So our class declaration line should look like this:

1
class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {

Android Studio will then give you an error. Hover over the error and then click on the Implement members button to implement any unimplemented methods. Add these four variables to the MusicService class.

1
private lateinit var mediaPlayer: MediaPlayer
2
private lateinit var songs: ArrayList<Song>
3
private var songPosition = 0
4
5
private val musicBinder: IBinder = MusicBinder(this)

We are using the lateinit keyword to indicate to Kotlin that the variable might not be initialized now but we guarantee that we will initialize it later. This helps us avoid unnecessary non-null assertion whenever we use the variable later.

Now, override the onCreate() method of MusicService so that it contains the following code:

1
override fun onCreate() {
2
    super.onCreate()
3
    songPosition = 0
4
    mediaPlayer = MediaPlayer()
5
6
    initMusicPlayer()
7
    random = Random.Default
8
}

We initialize our mediaPlayer variable with a new instance of the MediaPlayer class. The call to our custom initMusicPlayer() method sets the value of different attributes and properties for this media player.

1
private fun initMusicPlayer() {
2
    
3
    mediaPlayer.setWakeMode(
4
        applicationContext,
5
        PowerManager.PARTIAL_WAKE_LOCK)
6
7
    val audioAttributes = AudioAttributes.Builder()
8
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
9
        .setUsage(AudioAttributes.USAGE_MEDIA)
10
        .build()
11
12
    mediaPlayer.setAudioAttributes(audioAttributes)
13
14
    mediaPlayer.setOnPreparedListener(this)
15
    mediaPlayer.setOnCompletionListener(this)
16
    mediaPlayer.setOnErrorListener(this)
17
}

This method does a lot of useful things such as setting the wake mode for the media player to PARTIAL_WAKE_LOCK. This way, the media player can keep running as the CPU will stay active while the screen can stay off. Then, we create a bunch of audio attributes and assign them to our mediaPlayer.

The next three lines set the value of different methods for the mediaPlayer instance to the current class instance. This means that any call to, for example, onPreparedListener() will be handled by a callback method defined in the current class.

Our songs are stored inside MainActivity, but we need to pass them to the MusicService class to play them. Let's define a method inside the MusicService class to pass the list of songs.

1
fun setList(theSongs: ArrayList<Song>) {
2
    songs = theSongs
3
}

We will now define a MusicBinder class that extends the Binder class. The purpose of this class is to give other classes access to the MusicService class and its methods from outside the MusicService class.

1
class MusicBinder(private val service: MusicService) : Binder() {
2
    val getService: MusicService
3
    get() = service
4
}

Starting the Service

We will now head back to the MainActivity class and add the following variables to it:

1
private var musicService: MusicService? = null
2
private var playIntent: Intent? = null
3
private var musicBound = false

While the MusicService class will play the music, the playback will be under the control of the MainActivity class because that's where the application's user interface is present. Therefore, we will need to bind to the MusicService class, and we can do so by adding the following code to our class.

1
private val musicConnection: ServiceConnection = object : ServiceConnection {
2
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
3
        val binder = service as MusicBinder
4
        
5
        musicService = binder.getService
6
        musicService!!.setList(songs)
7
        
8
        musicBound = true
9
    }
10
11
    override fun onServiceDisconnected(name: ComponentName) {
12
        musicBound = false
13
    }
14
}

We are using the MusicBinder class defined in the previous section to get access to the MusicService class and its methods. That's how we were able to call the setList() method after initialization within onServiceConnected().

Make sure you add the following statement to your imports to avoid any errors.

1
import com.tutsplus.musicplayer.MusicService.MusicBinder

We will now override the onStart() method of the MainActivity class to start the MusicService instance when the MainActivity instance starts.

1
override fun onStart() {
2
    super.onStart()
3
    if (playIntent == null) {
4
        playIntent = Intent(this, MusicService::class.java)
5
        bindService(playIntent, musicConnection, BIND_AUTO_CREATE)
6
        startService(playIntent)
7
    }
8
}

We begin by checking if an Intent object exists to start a MusicService component. A null value means that the service has not yet been started. If no such Intent exists, we create one here. The bindService() method binds the musicService to the MainActivity.

As you can see, we also pass a reference to the ServiceConnection object in the form of our musicConnection variable in the second parameter. This handles the connection and binding between our MainActivity and MusicService instances.

In the previous section, we created and stored an instance of our MusicBinder class at the top of the MusicService class. Add the following two methods to the MusicService class to handle the binding and unbinding.

1
override fun onBind(intent: Intent?): IBinder {
2
    return musicBinder
3
}
4
5
override fun onUnbind(intent: Intent?): Boolean {
6
    mediaPlayer.stop()
7
    mediaPlayer.release()
8
    return false
9
}

Begin the Playback

The basic setup of the MusicService and MainActivity class is now complete. So we can start adding the code that will help us play a song the user selects.

Create a method called playSong() inside the MusicService class, and add the following code to it:

1
fun playSong() {
2
    
3
    mediaPlayer.reset()
4
    val playSong = songs[songPosition]
5
    val currentSongId: Long = playSong.id
6
7
    val trackUri = ContentUris.withAppendedId(
8
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
9
        currentSongId
10
    )
11
12
    try {
13
        mediaPlayer.setDataSource(applicationContext, trackUri)
14
    } catch (e: Exception) {
15
        Log.e("MUSIC SERVICE", "Error setting data source", e)
16
    }
17
18
    mediaPlayer.prepare()
19
}

We reset our MediaPlayer instance with each call to the playSong() method because it is supposed to play multiple songs, and resetting everything takes the player back to its initial state.

Then, we get the current song being played based on the songPosition and extract its id from the corresponding Song object. We create a Uri based on this id and then set it as the data source for the mediaPlayer object.

Finally, we call the prepare() method to prepare the media player for playback. This is useful when you want to play both local media sources.

Add the following code to the onPrepared() method of the MusicService class. This starts the media player once it is ready for playback.

1
override fun onPrepared(mediaPlayer: MediaPlayer?) {
2
    mediaPlayer?.start()
3
}

Also add the following method to the MusicService class to set the current song. We will be calling this method a bit later.

1
fun setSong(songIndex: Int) {
2
    songPosition = songIndex
3
}

The song.xml file that we created in the previous tutorial to specify the layout for individual song items contains the following attribute attached to each item:

1
android:onClick="songPicked"

Open MainActivity.kt and add the following method inside it:

1
fun songPicked(view: View) {
2
    musicService!!.setSong(view.tag.toString().toInt())
3
    musicService!!.playSong()
4
}

When we created our adapter in the previous tutorial, we added the following line to the onBindViewHolder() method:

1
songViewHolder.itemView.tag = idx

This tag property set as the index of the song is what we are using inside the songPicked() method to set the song to play.

Update the onDestroy() method of the MainActivity to stop the associated MusicService() when the activity itself is destroyed.

1
override fun onDestroy() {
2
    stopService(playIntent)
3
    musicService = null
4
    
5
    super.onDestroy()
6
}

Final Thoughts

We have now implemented basic playback of music tracks selected from the user's list of music files. In the final part of this series, we will add a media controller through which the user will be able to control playback. We will add a notification to let the user return to the app after navigating away from it, and we will carry out some housekeeping to make the app cope with a variety of user actions.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.