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)
}
}
}
}
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 .