How to build a simple audio player with Kotlin

Table of contents

No heading

No headings in the article.

Introduction

In this demonstration, we’ll build a simple audio player with a play button. The button kickstarts our audio then switches to a pause button and displays a stop button. As we will only be using a single audio file, conventional previous and next buttons are unnecessary. Below is what the project would look like

Demonstration

This is how the project looks like before we hit the play button

Before playing

After pressing play, a hidden stop button becomes visible since the audio is playing and we might need to stop it

playing

should we decide to hit pause, this is what the project would look like

paused

Now that we understand the basic functionality, let’s get started

Methodology

Most music/audio players(if not all) currently on the Google Play Store would require storage permissions to view audio files but for simplicity, we’ll have just one audio file as a resource in our project. On the project pane on the right, right-click on res and select a new Android Resource Directory. The Android Resource Directory pops up and we see a Resource type dropdown. From the dropdown menu, select raw and make sure the Directory name is also raw

New Resource Directory

hit ok and we have a ‘raw’ subfolder in our res folder.

Now copy your desired audio file and paste into our raw folder. Now, I must warn you that the raw folder, like other folders in our res directory, is very selective about file names, I suggest we use a ‘song.mp3’ as its name.

As we will be using ViewBinding, let’s update our app-level gradle file

buildFeatures{
        viewBinding = true
    }

The design in our activity_main.xml file is as follows….oops, don't forget to sync

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/background"
    android:background="#252525"
    tools:context=".MainActivity">



    <ImageView
        android:id="@+id/musicImage"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginStart="16dp"
        android:src="@drawable/background"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/linearLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.13999999" />

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal"
        android:paddingTop="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">


        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fabPlayOrPause"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="center"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:backgroundTint="#FFA500"
            android:src="@drawable/ic_play"
            app:fabCustomSize="60dp"
            app:layout_constraintBottom_toBottomOf="parent" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fabStop"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="center"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:backgroundTint="#FFA500"
            android:src="@drawable/ic_stop"
            app:fabCustomSize="60dp"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent" />


    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

The audio file and images used can be found here

Moving on to our MainActivity, we create two global variables one of Boolean type and the other of MediaPlayer? type(no, the question mark isn’t a typo).

var isPlaying: Boolean = false
var player: MediaPlayer? = null

So if we click on the play button, if the audio isn't playing, initialize the player if null, start it, switch to a pause button, and display the stop button.

if (player == null){
    player = MediaPlayer.create(this, R.raw.song)
}
player!!.start()
isPlaying = true
with(binding) {
    fabPlayOrPause.setImageResource(R.drawable.ic_pause)
    fabStop.visibility = View.VISIBLE
}

Else, if the player isn't null, pause the player and switch the pause button to a play button

if (player != null){
    player!!.pause()
    isPlaying = false
    binding.fabPlayOrPause.setImageResource(R.drawable.ic_play)
}

Therefore, the click listener for our play button would look like this

binding.fabPlayOrPause.setOnClickListener {
    if (isPlaying) {
        if (player != null){
            player!!.pause()
            isPlaying = false
            binding.fabPlayOrPause.setImageResource(R.drawable.ic_play)
         }
    } else {
         if (player == null){
             player = MediaPlayer.create(this, R.raw.song)
          }
          player!!.start()
          isPlaying = true
          with(binding) {
              fabPlayOrPause.setImageResource(R.drawable.ic_pause)
              fabStop.visibility = View.VISIBLE
          }

    }
}

Now we create a method to stop our player. We’re creating this method because we’ll be calling it in multiple places to avoid repetition.

private fun stopPlayer() {
    if (player != null) {
        player!!.release()
        player = null
     }
     isPlaying = false
     binding.fabPlayOrPause.setImageResource(R.drawable.ic_play)
}

So now let’s call our stopPlayer() in our stop button click listener, in our onStop() method, and player on the complete listener. Putting it all together, our MainActivity looks like this

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    var isPlaying: Boolean = false
    var player: MediaPlayer? = null



    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        binding.fabPlayOrPause.setOnClickListener {
            if (isPlaying) {
                if (player != null){
                    player!!.pause()
                    isPlaying = false
                    binding.fabPlayOrPause.setImageResource(R.drawable.ic_play)
                }
            } else {
                if (player == null){
                    player = MediaPlayer.create(this, R.raw.song)
                }
                player!!.start()
                isPlaying = true
                with(binding) {
                    fabPlayOrPause.setImageResource(R.drawable.ic_pause)
                    fabStop.visibility = View.VISIBLE
                }

            }
        }

        binding.fabStop.setOnClickListener {
            stopPlayer()
            binding.fabStop.visibility = View.GONE
        }

        player?.setOnCompletionListener {
            stopPlayer()
        }
    }

    private fun stopPlayer() {
        if (player != null) {
            player!!.release()
            player = null
        }
        isPlaying = false
        binding.fabPlayOrPause.setImageResource(R.drawable.ic_play)
    }

    override fun onStop() {
        super.onStop()
        stopPlayer()
    }
}

and that concludes it. The source code for this project can be found here