Media Player in Android Studio Using Kotlin | Media Player Source Code

Media Player in Android Studio Using Kotlin | Media Player Source Code

📅 April 29, 2025 · ✍️ Al Saeed
Android Kotlin Source Code

If you’re looking to create a simple media player in Android Studio using Kotlin, this article will guide you through the core of your project: the MainActivity. This is where permissions are handled, songs are loaded from device storage, and user interactions are managed.

Overview of Media Player MainActivity

The MainActivity is responsible for:

  • Checking and requesting runtime permissions
  • Fetching music files from the device storage
  • Binding to the MusicService for background playback
  • Handling user interactions like play, pause, next, and previous

Kotlin Code Breakdown for Media Player

The class implements AppCompatActivity and ServiceConnection to establish communication with the background music service. It uses ActivityMainBinding for UI binding and coroutines to prevent blocking the main thread while querying songs from MediaStore.

Permission Handling for Media Player

For Android 13+ (Tiramisu), it requests READ_MEDIA_AUDIO. For earlier versions, READ_EXTERNAL_STORAGE is used.

Loading Songs

The app queries MediaStore.Audio.Media using a coroutine and populates the songsList if the files exist.

Binding Media Player to MusicService

On successful service connection, the list is passed to the service and the UI is updated with the currently playing song title.

Media Player Controls and Playback

The play/pause, next, and previous buttons update the state using MusicService. The UI is automatically refreshed using updateControls().

Other Media Player App Classes

To complete the media player, create the following Kotlin classes and paste their code accordingly:

  • MusicService.kt – Manages playback in the background
  • AudioModel.kt – Data class representing a song
  • MusicAdapter.kt – Binds song data to RecyclerView
  • MusicPlayerActivity.kt – Full screen music controls

1. MainActivity.kt


  class MainActivity : AppCompatActivity(), ServiceConnection {
    private var musicService: MusicService? = null
    private var isServiceBound = false
    private lateinit var binding: ActivityMainBinding
    private val songsList: MutableList = mutableListOf()
    private lateinit var adapter: MusicAdapter
    private var isPlaying = false

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

        if (!checkPermission()) {
            requestPermission()

        } else {
            initializeApp()
        }

    }


    private fun initializeApp() {
        // Launch coroutine on Main thread
        CoroutineScope(Dispatchers.Main).launch {

            // Show progress bar
            binding.progressBar.visibility = View.VISIBLE

            val songs = withContext(Dispatchers.IO) {
                val songs = ArrayList()
                val projection = arrayOf(
                    MediaStore.Audio.Media.TITLE,
                    MediaStore.Audio.Media.DATA,
                    MediaStore.Audio.Media.DURATION
                )
                val selection = MediaStore.Audio.Media.IS_MUSIC + " !=0"

                contentResolver.query(
                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    projection,
                    selection,
                    null,
                    null
                )?.use { cursor ->
                    while (cursor.moveToNext()) {
                        val audioModel = AudioModel(
                            cursor.getString(1),
                            cursor.getString(0),
                            cursor.getString(2)
                        )
                        if (File(audioModel.path).exists()) {
                            songs.add(audioModel)
                        }
                    }
                }
                songs
            }

            // Hide progress bar
            binding.progressBar.visibility = View.GONE

            songsList.clear()
            songsList.addAll(songs)

            adapter = MusicAdapter(this@MainActivity, songsList) { position ->
                if (isPlaying) {
                    musicService?.musicPlay(songsList)
                    binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                    adapter.notifyDataSetChanged()
                } else {

                    if (songsList.isNotEmpty()) {
                        val intent = Intent(this@MainActivity, MusicPlayerActivity::class.java)
                        intent.putExtra("List", songsList as Serializable)
                        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                        startActivity(intent)
                    } else {
                        Toast.makeText(this@MainActivity, "No songs to play", Toast.LENGTH_SHORT)
                            .show()
                    }

                }
            }

            binding.recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
            binding.recyclerView.adapter = adapter
            binding.noSongFound.visibility = if (songsList.isEmpty()) View.VISIBLE else View.GONE

            setupControls()
        }
    }


    private fun setupControls() {
        val intent = Intent(this, MusicService::class.java)
        bindService(intent, this, Context.BIND_AUTO_CREATE)

        binding.pausePlay.setOnClickListener {
            musicService?.getMusicPlayer()?.let {
                if (it.isPlaying) {
                    it.pause()
                    binding.pausePlay.setImageResource(R.drawable.play_ic)
                } else {
                    it.start()
                    binding.pausePlay.setImageResource(R.drawable.pause_ic)
                }
            }
        }



        binding.next.setOnClickListener { musicNext() }
        binding.previous.setOnClickListener { musicPrevious() }

        binding.controls.setOnClickListener {
            val intent1 = Intent(this@MainActivity, MusicPlayerActivity::class.java)
            intent1.putExtra("IS", true)
            intent1.putExtra("List", songsList as Serializable)
            startActivity(intent1)
        }
    }


    private fun musicNext() {

        musicService?.next()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun musicPrevious() {
        musicService?.previous()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun checkPermission(): Boolean {

        val result = ContextCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )

        if (result == PackageManager.PERMISSION_GRANTED) {
            return true

        } else {
            return false
        }
    }


    private fun requestPermission() {
        val permission = Manifest.permission.READ_EXTERNAL_STORAGE

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // For Android 13+ (Tiramisu), use READ_MEDIA_AUDIO instead
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.READ_MEDIA_AUDIO),
                92
            )
        } else {

            ActivityCompat.requestPermissions(
                this,
                arrayOf(permission),
                92
            )

        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == 92 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
            initializeApp() // Or load your music files
        } else {
            Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            requestPermission()
        }
    }


    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as MusicService.MyBinder
        musicService = binder.currentService()
        isServiceBound = true
        musicService!!.setSongLIst(songsList)

        // Update the controls based on the current state of the media player
        updateControls()
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        musicService = null
        isServiceBound = false
    }


    private fun updateControls() {
        musicService?.getMusicPlayer()?.let { mediaPlayer ->
            if (mediaPlayer.isPlaying) {
                binding.controls.visibility = View.VISIBLE
                binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                binding.pausePlay.setImageResource(R.drawable.pause_ic)
                isPlaying = true
            } else {
                isPlaying = false
                binding.controls.visibility = View.GONE
            }

            mediaPlayer.setOnCompletionListener {
                musicNext() // Move to the next song when the current song is completed.
            }
        }

    }

    @SuppressLint("NotifyDataSetChanged")
    override fun onResume() {
        super.onResume()
        if (::adapter.isInitialized) {
            adapter.notifyDataSetChanged()
            updateControls()
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        if (isServiceBound) {
            unbindService(this)
            isServiceBound = false
        }
    }
}

2. MusicService.kt


class MusicService : Service() {

    private var myBinder = MyBinder()
    private var mediaPlayer: MediaPlayer? = MyMediaPlayer.getInstance()
    private var currentSongPath: String? = null
    private var isAlreadyPlaying = false
    private var songsList: List? = null


    fun setSongLIst(list: List) {
        songsList = list

    }

    public fun getMusicPlayer(): MediaPlayer? {
        return mediaPlayer
    }


    override fun onBind(intent: Intent?): IBinder? {
        return myBinder

    }


    fun musicPlay(songList: List) {
        currentSongPath = songList[MyMediaPlayer.currentIndex].path
        mediaPlayer!!.reset()
        mediaPlayer!!.setDataSource(currentSongPath)
        mediaPlayer!!.prepare()
        mediaPlayer!!.start()

    }


    fun pause() {
        mediaPlayer?.pause()
        Log.d("MyService", "Pausing music")
    }

    fun stop() {
        mediaPlayer?.stop()
        Log.d("MyService", "Stopping music")
    }


    fun next() {
        if (MyMediaPlayer.currentIndex == songsList!!.size - 1) {
            return
        }

        MyMediaPlayer.currentIndex += 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false


    }


    fun previous() {
        if (MyMediaPlayer.currentIndex == 0) {
            return
        }
        MyMediaPlayer.currentIndex -= 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false

    }


    fun getPlayingStatus(): Boolean {
        return isAlreadyPlaying
    }


    fun isPlaying(): Boolean {
        return mediaPlayer?.isPlaying ?: false
    }

    fun getCurrentPosition(): Int {
        return mediaPlayer?.currentPosition ?: 0
    }

    fun seekTo(position: Int) {
        mediaPlayer?.seekTo(position)
    }


    override fun onDestroy() {
        super.onDestroy()
    }


    inner class MyBinder : Binder() {
        fun currentService(): MusicService {
            return this@MusicService
        }
    }

}

3. MusicAdapter.kt


class MusicAdapter(
    private val context: Context,
    private val songList: List,
    private val onClick: (Int) -> Unit
) : RecyclerView.Adapter() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
        val view = SongItemBinding.inflate(LayoutInflater.from(context), parent, false)
        return ItemHolder(view)
    }

    override fun getItemCount(): Int {
        return songList.size
    }


    public fun getSongLIst(): List {
        return songList
    }

    override fun onBindViewHolder(holder: ItemHolder, position: Int) {
        val audioSong = songList[position]
        holder.bind(audioSong, position)
    }


    inner class ItemHolder(private val binding: SongItemBinding) :
        RecyclerView.ViewHolder(binding.root) {


        fun bind(audioSong: AudioModel, position: Int) {

            if (MyMediaPlayer.currentIndex == position) {
                binding.tvSongTitle.setTextColor(Color.RED)
            } else {
                binding.tvSongTitle.setTextColor(Color.BLACK)
            }
            binding.tvSongTitle.text = audioSong.title
            //   binding.ivMusic.setImageResource(R.drawable.music_ic)

            binding.root.setOnClickListener {
                MyMediaPlayer.getInstance().reset()
                MyMediaPlayer.currentIndex = adapterPosition
                onClick(adapterPosition)


            }

        }

    }
}


Download Source Code

Conclusion

This Android media player in Kotlin is lightweight, efficient, and follows best practices for modern development using ViewBinding, coroutines, and service components. Add more features like notifications, shuffle, or repeat to further enhance your app.

You can also Check Out the official Android Media Player Documentation.
Want to learn more? Visit our tutorials at Blog .