Android SharedPreferences Tutorial and Example

Playing audio files with MediaPlayer in Android

Playing sound in an Android app is usually a small MediaPlayer job: point it at an audio source, prepare it, and control playback from the interface. The same class handles a bundled alert, a user-selected song, or an internet stream, although each source introduces different permissions, latency, and failure modes.

This guide uses Kotlin and covers preparation, controls, lifecycle, and audio focus. It also accounts for Australian conditions: train listening on Sydney and Melbourne routes, patchy mobile coverage outside major cities, Australian English wording, and Google Play prices displayed in Australian dollars.

Understanding MediaPlayer playback states

MediaPlayer is a state machine, so calls must match its current state. prepareAsync() starts preparation on a background thread and invokes OnPreparedListener; only then is the media ready for start(). Calling start() too early can throw an exception or be ignored. The synchronous prepare() method should stay off the UI thread.

MediaPlayer suits short effects, spoken instructions, music clips, and uncomplicated streaming. A dedicated library such as ExoPlayer offers stronger support for adaptive media, extensive formats, and advanced playback controls, but MediaPlayer remains effective when the application needs a lightweight solution with direct access to the Android framework.

Loading local and bundled audio

Fixed sounds belong in res/raw, where filenames use lowercase letters, digits, and underscores. For example, coastal_ambience.mp3 could contain an original recording made near Sydney Harbour. Resource audio is packaged with the application, needs no storage permission, and remains available offline, although every recording adds to the installation size.

private var player: MediaPlayer? = null

private fun playBundledSound() {
    releasePlayer()

    player = MediaPlayer().apply {
        setAudioAttributes(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build()
        )

        setDataSource(this@MainActivity, R.raw.coastal_ambience)

        setOnPreparedListener {
            start()
        }

        setOnErrorListener { _, _, _ ->
            releasePlayer()
            true
        }

        prepareAsync()
    }
}

private fun releasePlayer() {
    player?.release()
    player = null
}

User-owned audio is loaded through a ContentResolver and a Content URI. The Storage Access Framework lets a person choose a track without granting access to the entire device, while MediaStore can supply media the application already owns or is authorised to read. Downloaded clips can also be stored in the application's private directory, but cached files require a deliberate cleanup policy.

Controlling playback and activity lifecycle

Audio attributes should describe the intended use before preparation begins. USAGE_MEDIA is appropriate for music and recorded sound, while alarm or notification sounds may require different attributes. Playback controls must also respect the activity lifecycle so that a backgrounded screen does not accidentally continue producing sound.

override fun onStop() {
    super.onStop()
    player?.pause()
}

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

Short cues can pause when the activity stops, but longer recordings may need a foreground service with a media notification. A MediaSession then provides lock-screen controls and supports actions such as headset buttons. Rotation and process recreation also require saved playback position, service ownership, or another mechanism that preserves state across configuration changes.

Before starting playback, request audio focus and handle focus loss, transient loss, and focus gain. Register for ACTION_AUDIO_BECOMING_NOISY so that removing headphones pauses the sound instead of broadcasting it through the phone speaker. For repeatable daily cues, an alarm or foreground service can invoke the playback code; the guide to scheduled background tasks shows how to arrange recurring triggers.

Comparing practical audio sources

Source selection affects packaging size, permissions, offline behaviour, and error handling. The audio's ownership and expected network conditions should determine the approach rather than simply choosing the shortest implementation.

Audio source Setup Best suited for Main consideration
Raw resource R.raw.coastal_ambience Fixed alerts and interface sounds Increases package size
Content URI Storage Access Framework or MediaStore A person's own music URI access and provider state
Private file filesDir or cache directory Downloads and temporary recordings Cleanup and storage limits
Remote URL HTTPS address Radio, podcasts, and streamed clips Network latency and interruptions

Raw resources remain the most predictable option for mandatory sounds. Streaming supports continuously updated content but requires the INTERNET permission and should use HTTPS because cleartext traffic is blocked by default on modern Android versions. Regional Australian radio and podcast audiences may also encounter variable mobile coverage, so an offline download is valuable when reception disappears. Error callbacks, retry states, and visible download progress prevent a stalled stream from looking like a failed application.

Recommendations for dependable Android audio

A dependable implementation follows a short set of practices throughout the activity, service, and playback callbacks.

  • Bundle mandatory cues in res/raw, and keep recorded or licensed material separate from source-control examples where redistribution rights are unclear.
  • Call prepareAsync() rather than blocking the main thread with prepare(), and begin playback only after the prepared callback runs.
  • Request audio focus before starting sound, and handle permanent, transient, and regained focus states.
  • Treat ACTION_AUDIO_BECOMING_NOISY as a request to pause rather than immediately switching to the phone speaker.
  • Release every MediaPlayer instance in onDestroy(), while allowing a foreground service to retain ownership for long-form playback.

Samsung and Pixel models commonly sold through Australian retailers can behave differently around background limits and audio focus. Busy conditions, including a noisy Melbourne tram, make volume control and headphone handling easy to overlook during development. Australian English labels, consistent duration formatting, and Google Play's Australian-dollar pricing keep the surrounding application familiar to its local audience. Correct resource cleanup then prevents stale sound and supports predictable playback when the same track is requested again.