Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Create a Music Player on Android: Song Playback

$
0
0
tag:code.tutsplus.com,2005:PostPresenter/mobile-227782014-02-07T14:32:31+00:00

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
<serviceandroid: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
classMusicService: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
privatelateinitvarmediaPlayer:MediaPlayer
2
privatelateinitvarsongs:ArrayList<Song>
3
privatevarsongPosition=0
4
5
privatevalmusicBinder: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
overridefunonCreate(){
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
privatefuninitMusicPlayer(){
2
3
mediaPlayer.setWakeMode(
4
applicationContext,
5
PowerManager.PARTIAL_WAKE_LOCK)
6
7
valaudioAttributes=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
funsetList(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
classMusicBinder(privatevalservice:MusicService):Binder(){
2
valgetService: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
privatevarmusicService:MusicService?=null
2
privatevarplayIntent:Intent?=null
3
privatevarmusicBound=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
privatevalmusicConnection:ServiceConnection=object:ServiceConnection{
2
overridefunonServiceConnected(name:ComponentName,service:IBinder){
3
valbinder=serviceasMusicBinder
4
5
musicService=binder.getService
6
musicService!!.setList(songs)
7
8
musicBound=true
9
}
10
11
overridefunonServiceDisconnected(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
importcom.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
overridefunonStart(){
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
overridefunonBind(intent:Intent?):IBinder{
2
returnmusicBinder
3
}
4
5
overridefunonUnbind(intent:Intent?):Boolean{
6
mediaPlayer.stop()
7
mediaPlayer.release()
8
returnfalse
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
funplaySong(){
2
3
mediaPlayer.reset()
4
valplaySong=songs[songPosition]
5
valcurrentSongId:Long=playSong.id
6
7
valtrackUri=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
overridefunonPrepared(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
funsetSong(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
funsongPicked(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
overridefunonDestroy(){
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.

2023-07-29 11:58:00 UTC2023-07-29 11:58:00 UTCNitish Kumar

Viewing all articles
Browse latest Browse all 1836

Trending Articles