How to play audio files with MediaPlayer in Android
Playing a sound in an Android app can be as simple as starting an audio file from the res/raw directory. The MediaPlayer class provides the basic controls needed for music, spoken instructions, notification sounds, and short effects.
A useful implementation must handle more than a single start() call. Your app should prepare the player correctly, respond to taps, release resources when playback ends, and avoid continuing to use a player after an Activity has been destroyed.
This approach works well for beginner projects such as a language-learning app, a quiz with spoken answers, or a small travel guide for places around Australia. It is also suitable for longer local files when you want playback to work without relying on mobile reception.
The examples below use Kotlin and a file packaged with the application. The same lifecycle ideas apply when the source is a remote URL, a device file, or audio supplied through Android’s media framework.
Add an audio file to the project
Create a raw directory inside app/src/main/res if it does not already exist. Add an audio file such as welcome_message.mp3. Resource filenames must use lowercase letters, numbers, and underscores, so names such as Welcome Message.mp3 will cause problems.
A short file can be loaded and played from an Activity like this:
private var mediaPlayer: MediaPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val playButton = findViewById<Button>(R.id.playButton)
playButton.setOnClickListener {
mediaPlayer?.release()
mediaPlayer = MediaPlayer.create(
this,
R.raw.welcome_message
)
mediaPlayer?.start()
}
}
MediaPlayer.create() prepares a packaged resource for immediate playback. Releasing any previous instance before creating another one prevents overlapping audio and reduces the chance of resource leaks. A longer recording, such as a tour explanation for the Royal Botanic Garden in Melbourne, can be packaged in the same way.
Connect playback to the user interface
A play button is more useful when it changes to pause and resume controls. The player exposes methods such as start(), pause(), stop(), seekTo(), and isPlaying. You can connect these methods to buttons defined in an XML layout.
pauseButton.setOnClickListener {
mediaPlayer?.let { player ->
if (player.isPlaying) {
player.pause()
pauseButton.text = "Resume"
} else {
player.start()
pauseButton.text = "Pause"
}
}
}
stopButton.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayer = null
pauseButton.text = "Play"
}
A stopped MediaPlayer cannot be started again without preparing it first. For a simple local sound, releasing the object and creating a new one is usually clearer than trying to reuse a stopped instance. Always handle the nullable value because the player may not have been created yet.
If your screen contains a progress indicator, a SeekBar can display the current position. A Handler or coroutine may update it periodically with currentPosition, while duration provides the total length. This gives users familiar controls when listening during a Sydney train commute.
Choose the right audio source
The source determines how much preparation and error handling your app needs. Packaged resources are reliable offline, while a URL requires network access and buffering. Device audio may require a content URI returned by the system file picker.
| Audio source | Typical method | Internet required | Main consideration |
|---|---|---|---|
res/raw resource |
MediaPlayer.create() |
No | Easy to package and test |
| App assets | Asset file descriptor | No | Useful for organised bundled files |
| Device content URI | setDataSource(uri) |
No | Respect user-selected permissions |
| HTTPS stream | setDataSource(url) |
Usually | Prepare asynchronously and handle buffering |
| File path | setDataSource(path) |
No | Access may be restricted on modern Android |
For a remote stream, add the internet permission to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Use prepareAsync() rather than blocking the main thread:
mediaPlayer = MediaPlayer().apply {
setDataSource(audioUrl)
setOnPreparedListener { player ->
player.start()
}
setOnErrorListener { _, _, _ ->
release()
mediaPlayer = null
true
}
prepareAsync()
}
A streamed audio guide may work well in central Sydney but fail in a regional Queensland area with patchy coverage. For Australian users, provide a clear loading state and consider downloading important files for offline use instead of assuming constant connectivity.
Manage the player lifecycle
A MediaPlayer holds system resources, so release it when the screen no longer needs it. A basic Activity can clean up in onStop() or onDestroy(), depending on whether playback should continue while the screen is temporarily hidden.
override fun onStop() {
super.onStop()
mediaPlayer?.release()
mediaPlayer = null
}
Use setOnCompletionListener to restore the interface after the file reaches its end:
mediaPlayer?.setOnCompletionListener {
pauseButton.text = "Play"
mediaPlayer?.release()
mediaPlayer = null
}
For an app that plays audio in the background, an Activity-owned player is usually insufficient. A foreground service with a media notification, audio focus handling, and suitable media controls is a better architecture. This matters for long listening sessions, such as podcasts used during a drive between Brisbane and the Gold Coast.
Handle audio focus and interruptions
Other apps may be playing music, navigation instructions, or a phone call. Requesting audio focus tells Android how your app expects to share sound. For a short effect, transient focus may be appropriate; for spoken content, your app may pause or reduce its volume when another source becomes active.
You should also respond to interruptions and headset changes. A user listening through Bluetooth earbuds in a Melbourne tram should not be surprised by audio continuing at full volume through the phone speaker after the connection changes.
For newer applications, ExoPlayer from AndroidX Media3 is often a stronger choice for adaptive streaming, playlists, subtitles, and background playback. MediaPlayer remains convenient for straightforward local files and small learning projects, especially when the goal is to understand the essential playback sequence.
Improve the playback screen
A polished screen should expose a clear play action, a visible state, and an accessible label. Use content descriptions for icon buttons, ensure text has sufficient contrast, and make controls large enough to tap comfortably. If the layout has many controls or instructions, style the vertical scrollbar so the screen remains easier to navigate.
Audio should never be the only way to receive important information. Provide text captions or a transcript for spoken instructions, and let users pause or stop playback without searching through the interface. This is especially useful in noisy places such as a busy station, a weekend market, or a crowded footpath near Sydney’s Circular Quay.
Before publishing, test missing files, rapid repeated taps, screen rotation, app backgrounding, Bluetooth output, and network loss. Check that the player is released after completion and that errors produce a useful message rather than a crash. These small checks turn a demonstration into dependable Android audio functionality.