Devesh Rx Logo
Devesh Rx Blog

Android MediaPlayer API: Build a Robust Audio Player

July 1, 2026

Android MediaPlayer API: Build a Robust Audio Player

Android MediaPlayer API: Building a Robust Audio Player in Kotlin

Building an audio player in Android that crashes or stops the moment a user switches apps is a common frustration.

Tying your media playback directly to an Activity leads to abrupt stops, poor user experience, and potential memory leaks if hardware codecs aren’t released properly.

By leveraging the Android MediaPlayer API alongside a background Service, you can create a seamless, persistent audio experience. This guide breaks down the exact Kotlin implementation, Service architecture, and lifecycle management required to build a professional-grade audio app.

Why You Need a Service for Background Audio

In Android development, an Activity represents a single screen with a user interface. If you initialize your media playback inside an Activity, the audio will stop when the screen is destroyed—such as when the user minimizes the app or rotates their device.

To achieve true background audio android functionality, you must use a Service. A Service is an application component that performs long-running operations in the background without requiring a user interface.

Activities vs. Services

  • Activities: Tied to the screen lifecycle. Destroyed on minimize or rotation.
  • Services: Runs independently of the UI. Keeps audio playing while users check emails or browse the web.

Configuring AndroidManifest Permissions

Before writing any Kotlin code, you must declare your app’s requirements in the AndroidManifest.xml. This informs the Android system of your app’s hardware and network needs.

You need two critical configurations:

  1. INTERNET Permission: Required if you are streaming audio from a remote URL.
  2. Service Declaration: Every custom Service must be explicitly declared so the Android system knows it exists.
<uses-permission android:name="android.permission.INTERNET" />

<application ...>
    <service
        android:name=".AudioPlayerService"
        android:enabled="true"
        android:exported="false" />
</application>

The MediaPlayer Lifecycle

The MediaPlayer is strictly state-based. You cannot simply call .start() immediately upon instantiation. You must follow a specific state flow to avoid errors:

  1. Idle: The player is created.
  2. Initialized: You set the data source (URL or File).
  3. Preparing: The player buffers data.
  4. Prepared: The player is ready to play.
  5. Started: Music is audible to the user.

Understanding the android mediaplayer lifecycle is crucial for avoiding crashes and ensuring smooth playback.

Implementing the AudioPlayerService in Kotlin

Below is a complete kotlin mediaplayer example showing how to handle state transitions, manage hardware resources, and handle background playback.

class AudioPlayerService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    // Entry point for commands sent from the UI
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_PLAY -> {
                val url = intent.getStringExtra(EXTRA_URL)
                if (url != null) playAudio(url)
            }
            ACTION_STOP -> stopAudio()
        }
        // START_STICKY tells the system to recreate the service if killed for memory
        return START_STICKY
    }

    private fun playAudio(url: String) {
        // Step 1: Safety first - stop any existing playback
        stopAudio()

        mediaPlayer = MediaPlayer().apply {
            // Step 2: Set Audio Attributes for Audio Focus management
            setAudioAttributes(
                AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .build()
            )

            // Step 3: Set Data Source (Idle -> Initialized)
            setDataSource(url)

            // Step 4: Prepare Asynchronously to avoid freezing the main thread
            setOnPreparedListener(this@AudioPlayerService)
            setOnErrorListener(this@AudioPlayerService)
            prepareAsync() 
        }
    }

    // Step 5: Start playback once the 'Prepared' state is reached
    override fun onPrepared(mp: MediaPlayer?) {
        mp?.start()
    }

    private fun stopAudio() {
        mediaPlayer?.apply {
            if (isPlaying) stop()
            // CRITICAL: Always release resources to prevent memory leaks
            release()
        }
        mediaPlayer = null
    }

    override fun onDestroy() {
        super.onDestroy()
        stopAudio() // Ensure cleanup if the service is destroyed
    }

    override fun onBind(intent: Intent?): IBinder? = null
    override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean = true

    companion object {
        const val ACTION_PLAY = "com.example.mediaplayer.action.PLAY"
        const val ACTION_STOP = "com.example.mediaplayer.action.STOP"
        const val EXTRA_URL = "com.example.mediaplayer.extra.URL"
    }
}

Connecting the UI with Intent Actions

Your android audio player service needs to receive commands from the user interface. We use Intent actions as messages to communicate with the Service. Instead of calling functions directly, we send an Intent with an action like ACTION_PLAY.

Jetpack Compose MainActivity Setup

In modern Android development utilizing Jetpack Compose, your UI should be stateless and purely trigger events. Here is how you wire up the MainActivity to send commands to your Service:

class MainActivity : ComponentActivity() {
    
    private fun sendIntentAction(action: String, url: String? = null) {
        val intent = Intent(this, AudioPlayerService::class.java).apply {
            this.action = action
            if (url != null) putExtra(AudioPlayerService.EXTRA_URL, url)
        }
        startService(intent)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MediaPlayerTheme {
                PlayerScreen(
                    onPlay = { 
                        sendIntentAction(
                            AudioPlayerService.ACTION_PLAY, 
                            "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
                        ) 
                    },
                    onStop = { 
                        sendIntentAction(AudioPlayerService.ACTION_STOP) 
                    }
                )
            }
        }
    }
}

Key Concepts for Audio Playback

To ensure your implementation remains robust, keep these core concepts in mind:

  • prepareAsync(): Essential for network streaming. It runs the buffering process on a background thread, preventing UI freezes and Application Not Responding (ANR) errors.
  • release(): Frees up hardware audio codecs. Failure to call this when playback stops leads to severe battery drain and system crashes.
  • Service Lifecycle: Using a Service decouples your audio playback from the Activity’s lifecycle, ensuring persistence across configuration changes and app minimization.

Frequently Asked Questions (FAQ)

How do I stream audio in the background using the Android MediaPlayer API? To stream audio in the background, you must run the MediaPlayer inside an Android Service. This decouples the audio playback from your app’s UI, allowing the audio to continue playing even when the user minimizes the app or switches to another application.

Why is prepareAsync() important for streaming audio? The prepareAsync() method buffers the audio stream on a background thread. If you use the synchronous prepare() method for a network stream, it will block the main UI thread, causing the app to freeze and potentially trigger an ANR crash.

How do I prevent memory leaks with MediaPlayer? Always call the release() method on your MediaPlayer instance when audio playback is stopped or the Service is destroyed. This frees up the hardware audio codecs. Additionally, nullify the MediaPlayer reference afterward to ensure garbage collection can clean it up.


📌 Full Course Playlist https://www.youtube.com/playlist?list=PLO1OrQEU0vHNmD9Xqzs-qXwzzwrDvdhVu

#Tutorial
0 Introduction
1 Setting up Android Studio IDE
2 Mastering Android Studio: Navigating the IDE & Project Structure
3 Android Activity & Lifecycle Explained
4 Android Services: Background, Foreground, and Bound Services Explained
5 Android Broadcast Receivers: The Complete Guide to Listening and Sending Events
6 Android Content Provider API Tutorial: Access User Data Safely (Kotlin)
7 How to Build UI with Jetpack Compose: A Beginner’s Guide
8 Android Runtime Permissions in Kotlin and Jetpack Compose: Step-by-Step Guide
9 Android Intents Guide: Master Screen Navigation and Data Sharing
10 Android Room Database: Complete CRUD Tutorial with Kotlin
11 Android Internal Storage: File I/O Tutorial
12 Android MediaStore API Tutorial: How to Save and Read Files
13 Master Storage Access Framework in Jetpack Compose
14 How to Create Android Notifications with Jetpack Compose & Kotlin
15 How to Use SharedPreferences in Android (Kotlin & Compose)
16 OkHttp Android Tutorial: Complete Kotlin Guide
17 Android MediaPlayer API: Build a Robust Audio Player

~ ~ THANK YOU FOR READING ~ ~

Share: