Create a Music Player on Android: User Controls
2023-7-30 23:40:0 Author: code.tutsplus.com(查看原文) 阅读量:6 收藏

By the end of this tutorial, our basic music player app will be complete. We learned how to display a list of songs from the user's device in the first tutorial. The second tutorial explained how we can play individual songs from the list. The only thing missing now is the code that allows users to control the playback of different songs.

In this tutorial, we will learn how to let users control the playback of different songs. This includes the ability to play, pause, or seek a particular song. They will also be able to play the next or previous tracks as well as turn shuffling on to play the songs in random order.

We will also display a notification during the playback so that the user can jump back to the music player after using other apps.

At the end of this tutorial, you will have a music player that looks like the image below:

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

Creating a Controller

Since we want to add playback controls for our media player, we need to implement the MediaPlayerControl interface first. Update your MainActivity class declaration so that it looks like the line below:

1
class MainActivity : AppCompatActivity(), MediaPlayerControl

You will now see an error message about unimplemented methods if you hover over the class name. You can get rid of the error by telling Android Studio to add the unimplemented methods.

Add a new Kotlin class to your project and name it MusicController. This will create a new file called MusicController.kt. The MusicController class extends the MediaController class and overrides one of its methods called hide().

Your MusicController.kt file should now have the following code:

1
package com.tutsplus.musicplayer
2
3
import android.content.Context
4
import android.widget.MediaController
5
6
7
class MusicController(c: Context?) : MediaController(c) {
8
    override fun hide() {}
9
}

The MediaController class presents a standard widget with play/pause, rewind, fast-forward, and skip (previous/next) buttons in it. The widget also contains a seek bar, which updates as the song plays and contains text indicating the duration of the song and the player's current position.

With this extended class, we can customize the behavior of the controller according to our requirements. In this case, we simply want to prevent the controls from hiding automatically after three seconds. We do this by overriding the hide() method.

Now, update the MainActivity class to add the following variables to it near the top:

1
private lateinit var controller: MusicController
2
3
private var paused: Boolean = false
4
private var playbackPaused: Boolean = false

Create a helper method called setController() and add the following code to it:

1
private fun setController() {
2
    controller = MusicController(this)
3
4
    controller.setPrevNextListeners({ playNext() }
5
    ) { playPrev() }
6
7
    controller.setMediaPlayer(this)
8
    controller.setAnchorView(findViewById(R.id.song_list))
9
    controller.isEnabled = true
10
}

We begin by instantiating the MusicController class and then assign the playNext() and playPrev() methods to its next and previous button click listeners respectively. The setAnchorView() method tells the app to anchor the controller UI to our specified view.

Let's define the playNext() and playPrev() methods now:

1
private fun playNext() {
2
    musicService!!.playNext()
3
    if(playbackPaused){
4
        setController()
5
        playbackPaused=false
6
    }
7
    controller.show(0)
8
}
9
10
private fun playPrev() {
11
    musicService!!.playPrev()
12
    if(playbackPaused){
13
        setController()
14
        playbackPaused=false
15
    }
16
    controller.show(0)
17
}

The call to the show() method of controller object with a value of 0 means that the media controller will be displayed on the screen until it is manually hidden by the user.

Now, update the displaySongs() method in MainAcitvity to add a call to setController() at the end.

Implement Playback Control

Remember that the media playback is happening in the MusicService class, but that the user interface comes from the MainActivity class. In the previous tutorial, we bound the MainActivity instance to the MusicService instance, so that we could control playback from the user interface.

The methods in our MainActivity class that we added to implement the MediaPlayerControl interface will be called when the user attempts to control playback. We will need the MusicService class to act on any events related to those controls. Open your MusicService class now to add a few more methods to it:

1
fun getPosition(): Int {
2
    return mediaPlayer.currentPosition
3
}
4
5
fun getDuration(): Int {
6
    return mediaPlayer.duration
7
}
8
9
fun isPlaying(): Boolean {
10
    return mediaPlayer.isPlaying
11
}
12
13
fun pausePlayer() {
14
    mediaPlayer.pause()
15
}
16
17
fun seek(position: Int) {
18
    mediaPlayer.seekTo(position)
19
}
20
21
fun go() {
22
    mediaPlayer.start()
23
}

In the previous section, we added the playNext() and playPrev() methods to our MainActivity class. Inside those methods, we call the playNext() and playPrev() methods of the MusicService class. We will now define those methods inside the MusicService class.

1
fun playPrev() {
2
    songPosition--
3
    if (songPosition < 0) songPosition = songs.size - 1
4
    playSong()
5
}
6
7
fun playNext() {
8
    songPosition++
9
    if (songPosition >= songs.size) songPosition = 0
10
    playSong()
11
}

In both methods, we check if the songPosition falls outside the list of songs and then we reset it accordingly. After that, we make a call to playSong() in order to play song at current position.

We will now update the implementation of some of the methods of the MediaPlayerControl interface. We are simply returning true for these methods to all users to pause, seek forward or seek backward while playing a song. Add the following code to your MainActivity class.

1
override fun canPause(): Boolean {
2
    return true
3
}
4
5
override fun canSeekBackward(): Boolean {
6
    return true
7
}
8
9
override fun canSeekForward(): Boolean {
10
    return true
11
}
12
13
override fun getAudioSessionId(): Int {
14
    return 1
15
}
16
17
override fun getBufferPercentage(): Int {
18
    val duration = musicService!!.getDuration()
19
    if (duration > 0) {
20
        return (musicService!!.getPosition() * 100)/(duration)
21
    }
22
    return 0
23
}
24
25
override fun getCurrentPosition(): Int {
26
    if (musicService != null && musicBound && musicService!!.isPlaying())
27
        return musicService!!.getPosition()
28
    else return 0
29
}

In the above snippet, you might have noticed that we called the getPosition() method from the musicService class to calculate the buffer percentage and to get the current position within MainActivity.

Let's update the implementation of some more methods so that they make a call to the respective methods inside the MusicService class.

1
override fun start() {
2
    musicService!!.go()
3
}
4
5
override fun pause() {
6
    playbackPaused = true
7
    musicService!!.pausePlayer()
8
}
9
10
override fun seekTo(p0: Int) {
11
    musicService!!.seek(p0)
12
}
13
14
override fun isPlaying(): Boolean {
15
    if(musicService!=null && musicBound)
16
        return musicService!!.isPlaying()
17
    return false
18
}
19
20
override fun getDuration(): Int {
21
    if(musicService!=null && musicBound && musicService!!.isPlaying())
22
        return musicService!!.getDuration()
23
    else return 0
24
}

Handling Navigation Back Into the App

When a user starts playing a song in any music app, they expect the song to keep playing even if they navigate away from the app to do something else. We can implement this feature by showing users a notification that displays the title of the current song being played. Clicking on the notification will take users to the app.

Begin by adding the following variables to the MusicService class.

1
private var songTitle: String? = ""
2
private val notifyId = 1

In our second tutorial where we implement playback, the onPrepared() method of the MusicService class was simply starting the media player. We will now update the method to create and display a notification. Here is the complete code of the method.

1
override fun onPrepared(mediaPlayer: MediaPlayer?) {
2
    mediaPlayer?.start()
3
4
    val channelId = "my_channel_id"
5
6
    fun createNotificationChannel(channelId: String, channelName: String): String {
7
        lateinit var notificationChannel: NotificationChannel
8
9
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
10
            notificationChannel = NotificationChannel(
11
                channelId,
12
                channelName, NotificationManager.IMPORTANCE_DEFAULT
13
            )
14
15
            notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
16
            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
17
            manager.createNotificationChannel(notificationChannel)
18
        }
19
        return channelId
20
    }
21
22
    val pendingIntent: PendingIntent =
23
        Intent(this, MainActivity::class.java).let { intent ->
24
            PendingIntent.getActivity(this, 0, intent,
25
                PendingIntent.FLAG_IMMUTABLE)
26
        }
27
28
29
    val notification: Notification = NotificationCompat.Builder(this, channelId)
30
        .setSmallIcon(R.drawable.ic_launcher_foreground)
31
        .setOngoing(true)
32
        .setContentTitle("Playing")
33
        .setContentText(songTitle)
34
        .setContentIntent(pendingIntent)
35
        .setTicker(songTitle)
36
        .build()
37
38
    createNotificationChannel(channelId, "My Music Player")
39
40
    startForeground(notifyId, notification)
41
}

After starting the media player, we create a string variable to store the channel ID. In the next line, we define a function called createNotificationChannel() to create a NotificationChannel with our specified ID and name.

Notification channels provide a way to group notifications. They were introduced in Android 8.0 or API level 26. We have to create a notification channel to display notifications in any android version above 8.0.

Next, we create a PendingIntent that will launch the main activity of our app once clicked. Finally, we create a new notification using the notification builder. I have passed true to the setOngoing() method to prevent users from accidently swiping away the notification.

Make sure that you update the playSong() method to include the following line to set the value of songTitle to the currently playing song.

1
songTitle = playSong.name

Finally, update the onDestory() method of the MusicService class to remove the notification once the app is destroyed.

1
override fun onDestroy() {
2
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
3
        stopForeground(STOP_FOREGROUND_REMOVE)
4
    } else {
5
        stopForeground(true)
6
    }
7
}

Shuffle Playback

So far, the Shuffle button in our app isn't doing anything. We will now write some code for this button so that the song to be played next is selected at random.

Begin by adding the following variables to the MusicService class.

1
private var shuffle = false
2
private var random: Random? = null

Now add the setShuffle() method to the MusicService class. This method simply toggles the value of the shuffle variable.

1
fun setShuffle() {
2
    shuffle = !shuffle
3
}

Update the playNext() method so that it checks for the shuffle value and selects a song at random if it is set to true.

1
fun playNext() {
2
    if (shuffle) {
3
        var newSong = songPosition
4
        while (newSong == songPosition) {
5
            newSong = random!!.nextInt(songs.size)
6
        }
7
        songPosition = newSong
8
    } else {
9
        songPosition++
10
        if (songPosition >= songs.size) songPosition = 0
11
    }
12
    playSong()
13
}

Finally, add the shuffleSongs() and stopSong() method to the MainActivity class.

1
fun shuffleSongs(view: View) {
2
    musicService?.setShuffle()
3
}
4
5
fun stopSong(view: View) {
6
    stopService(playIntent)
7
    musicService = null
8
    exitProcess(0)
9
}

Updating Lifecycle Methods

We will now make some changes to the onPause(), onResume(), and onStop() methods to do some initializations and cleanups. Add the following code to the MainAcitvity class.

1
override fun onPause() {
2
    super.onPause()
3
    paused = true
4
}
5
6
override fun onResume() {
7
    super.onResume()
8
    if (paused) {
9
        setController()
10
        paused = false
11
    }
12
}
13
14
override fun onStop() {
15
    controller.hide()
16
    super.onStop()
17
}

Also update the songPicked() method inside MainActivity to handle paused playback.

1
fun songPicked(view: View) {
2
    musicService!!.setSong(view.tag.toString().toInt())
3
    musicService!!.playSong()
4
5
    if(playbackPaused){
6
        setController()
7
        playbackPaused=false
8
    }
9
    controller.show(0)
10
}

With this update, we resume the playback after a new song is picked if it was paused earlier.

The last thing you need to do is update the onError() and onCompletion() methods of the MusicService class to reset the media player and play the next song.

1
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
2
    mp.reset()
3
    return false
4
}
5
6
override fun onCompletion(mp: MediaPlayer) {
7
    if (mediaPlayer.currentPosition > 0) {
8
        mp.reset()
9
        playNext()
10
    }
11
}

Final Thoughts

At this point, you should have a fully functioning music player app that you can install and use on your own device. Please keep in mind that the aim of this tutorial was to get you started with the basics.

There are a lot of improvements that you can make to this app to enhance its UI or implement additional features. You will also need to update some of these methods depending on how many user devices and API levels you want to support.

Did you find this post useful?

Nitish Kumar

Website Developer, India

I have been a web developer for around six years now, with experience in creating eCommerce websites by integrating different platforms. I have also used WordPress for creating client websites on multiple occasions. I spend my free time working on personal projects that make my everyday life easier or taking long evening walks with friends.


文章来源: https://code.tutsplus.com/create-a-music-player-on-android-user-controls--mobile-22787t
如有侵权请联系:admin#unsafe.sh