Kahibaro
Discord Login Register

16.3 Audio and Video Playback

Understanding Audio and Video Playback on Android

Android gives you several APIs to play audio and video, from very simple one line helpers to fully customizable players. In this chapter you focus on what makes audio and video playback work in a typical beginner level app, and how to choose and use the right tools for common tasks.

You will see how to play local media files, how to stream from the internet, and how to handle basic controls like play and pause, without covering advanced topics such as background services or notifications, which appear in later chapters.

Core Media Playback Options

For most Android apps you will use one of three main options to play audio or video.

The simplest is MediaPlayer, which is part of the Android framework and can play many common formats. It is good for basic audio playback and simple video playback that you do not need to customize a lot.

The second option is VideoView, which internally uses MediaPlayer but wraps it with a simple view that can display video content on the screen with minimal setup. VideoView is useful when you want a quick way to show a video in your layout, with minimal control.

The third option is ExoPlayer, which is an external library from Google. ExoPlayer is more powerful and flexible, especially for streaming, but it requires more setup and is usually introduced after you understand the basics of MediaPlayer. In this chapter the focus stays on MediaPlayer and VideoView to keep things clear.

Important rule: For basic playback inside your app, start with MediaPlayer for audio and VideoView for simple video. Move to ExoPlayer only when you need advanced streaming features, custom controls, or support for many formats.

Basic Audio Playback with MediaPlayer

To play audio in Android with MediaPlayer, you usually follow a simple sequence. You create an instance, connect it to an audio source, prepare it, and then start playback. Finally you release it when you are done.

A typical example for playing a short audio file stored in the res/raw directory looks like this:

class MainActivity : AppCompatActivity() {
    private var mediaPlayer: MediaPlayer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val playButton: Button = findViewById(R.id.playButton)
        val pauseButton: Button = findViewById(R.id.pauseButton)
        val stopButton: Button = findViewById(R.id.stopButton)
        playButton.setOnClickListener { playAudio() }
        pauseButton.setOnClickListener { pauseAudio() }
        stopButton.setOnClickListener { stopAudio() }
    }
    private fun playAudio() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.sample_audio)
        }
        mediaPlayer?.start()
    }
    private fun pauseAudio() {
        mediaPlayer?.let { player ->
            if (player.isPlaying) {
                player.pause()
            }
        }
    }
    private fun stopAudio() {
        mediaPlayer?.let { player ->
            if (player.isPlaying) {
                player.stop()
                player.reset()
                player.release()
                mediaPlayer = null
            }
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
        mediaPlayer = null
    }
}

The method MediaPlayer.create(context, resId) is a shortcut that prepares the player for a resource inside res/raw. After that you just call start() to play. Remember that stop() moves the player into a state where you must call reset() and then set a new data source if you want to use it again.

Always release the MediaPlayer when you no longer need it, typically in onDestroy. Failing to call release() can cause memory leaks and can keep audio resources such as the speaker or decoder locked.

Playing Audio from a File Path or URL

When the audio file is not in res/raw and you have a file path or a URL, you cannot use the create shortcut. Instead you create an empty MediaPlayer, set the data source, prepare it, and then start playback.

Here is a simple example that shows how to play from a URL. In a real app you need to think about network and threading, but this demonstrates the basic flow:

private var mediaPlayer: MediaPlayer? = null
private fun playFromUrl(url: String) {
    if (mediaPlayer == null) {
        mediaPlayer = MediaPlayer()
        mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
        mediaPlayer?.setOnPreparedListener { player ->
            player.start()
        }
        mediaPlayer?.setOnErrorListener { player, what, extra ->
            // Handle error here
            true
        }
        try {
            mediaPlayer?.setDataSource(url)
            mediaPlayer?.prepareAsync()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    } else {
        mediaPlayer?.start()
    }
}

The call prepareAsync() runs preparation in the background and triggers the OnPreparedListener when the media is ready to play. For network streams you should prefer prepareAsync() over prepare() because prepare() blocks the current thread.

For local file paths you can still use the same pattern, but the string you pass to setDataSource is a path on the device rather than a URL.

Tracking Playback State and Completion

For a responsive app you need to keep track of what is happening with the media player. For example, if the audio finishes you may want to reset the button text, release resources, or automatically play the next track.

You can listen for completion events by registering setOnCompletionListener on the MediaPlayer instance. This callback runs when playback reaches the end of the media:

private fun initMediaPlayer() {
    mediaPlayer = MediaPlayer.create(this, R.raw.sample_audio)
    mediaPlayer?.setOnCompletionListener { player ->
        // Playback finished
        player.seekTo(0)
        // Update UI here, for example reset play button appearance
    }
}

You can also query the current position and the total duration of the track with currentPosition and duration. These values are in milliseconds. This is helpful if you want to show a progress bar or a timer, but the full progress implementation is usually combined with background tasks and is left for more advanced topics.

Volume and Audio Focus Basics

Android devices can play different types of audio at the same time, such as music and notification sounds. To integrate nicely with the system your app should respect the user volume and ask for audio focus before playing long sounds like music or a podcast.

The simplest way to adjust volume for your app is to use the device volume keys. In an Activity you can associate the volume keys with a particular audio stream:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    volumeControlStream = AudioManager.STREAM_MUSIC
}

This tells Android to control the music stream volume when your activity is visible.

Handling audio focus uses the AudioManager and AudioFocusRequest classes. That topic becomes more important when you play audio in the background or handle interruptions by phone calls or other media apps, and is covered in more detail when you learn about services and background tasks.

Simple Video Playback with VideoView

To play video in your layout using VideoView, you first place a VideoView widget in your XML layout. Then you set the video URI and call start().

Here is a basic layout that includes a VideoView and two buttons:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="200dp" />
    <Button
        android:id="@+id/playButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Play" />
    <Button
        android:id="@+id/pauseButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pause" />
</LinearLayout>

Then in your activity you initialize the VideoView:

class MainActivity : AppCompatActivity() {
    private lateinit var videoView: VideoView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        videoView = findViewById(R.id.videoView)
        val playButton: Button = findViewById(R.id.playButton)
        val pauseButton: Button = findViewById(R.id.pauseButton)
        val videoUri = Uri.parse("android.resource://$packageName/${R.raw.sample_video}")
        videoView.setVideoURI(videoUri)
        playButton.setOnClickListener {
            videoView.start()
        }
        pauseButton.setOnClickListener {
            if (videoView.isPlaying) {
                videoView.pause()
            }
        }
    }
    override fun onPause() {
        super.onPause()
        if (videoView.isPlaying) {
            videoView.pause()
        }
    }
}

The URI uses the android.resource scheme for a video inside the res/raw directory. You can also pass a file URI or a network URL, but remember that streaming over the network can take time and may need buffering and error handling.

When playing video with VideoView, always pause or stop playback in lifecycle callbacks such as onPause. If you do not, the video can keep playing when the user leaves the screen, which leads to a poor user experience.

Adding Basic Media Controls to VideoView

Users expect common controls like play, pause, seek, and a progress bar. For quick results you can attach a MediaController to a VideoView. MediaController is a built in control bar that appears over the video.

Here is how to connect it:

private fun setupVideoView() {
    val videoUri = Uri.parse("android.resource://$packageName/${R.raw.sample_video}")
    videoView.setVideoURI(videoUri)
    val mediaController = MediaController(this)
    mediaController.setAnchorView(videoView)
    videoView.setMediaController(mediaController)
}

The MediaController will show play and pause buttons and a seek bar, and it will hide itself automatically after a short time. This is a convenient way to give your video basic controls without writing your own custom UI.

If you need a fully custom player interface, you can hide the default controller and build controls with your own buttons that call start(), pause(), seekTo(), and other VideoView methods. Building such an interface is very similar to the audio control example, but with the additional ability to control video position.

Handling Different Video Sources

In many apps you do not own the video file on the device. Instead you stream it from a URL. VideoView supports URLs the same way it supports local URIs. You simply pass a URI built from the video link.

For example:

val videoUrl = "https://www.example.com/sample.mp4"
val uri = Uri.parse(videoUrl)
videoView.setVideoURI(uri)
videoView.setOnPreparedListener { player ->
    // Video is ready, you can start or show controls
    videoView.start()
}
videoView.setOnErrorListener { player, what, extra ->
    // Show an error message or retry
    true
}

Streaming video depends on the user network quality. You should be prepared for buffering delay and errors, and in a real app consider showing a loading indicator while the video prepares.

For more advanced streaming features such as adaptive bitrate or DRM, VideoView and plain MediaPlayer are not always enough. This is where ExoPlayer becomes useful, but that is beyond the basic scope and is often treated as an advanced topic.

Dealing with Activity Lifecycle

If a user rotates the device or switches to another app, the activity can be paused or destroyed. For good playback behavior you must handle these cases.

For audio playback that is tied to an activity, a simple pattern is to pause the audio in onPause and release the player in onDestroy. If you want the audio to keep playing when the user leaves the screen, you usually move the playback into a service, which is covered later when you learn about services and background tasks.

Here is a simple audio example that pauses on onPause:

override fun onPause() {
    super.onPause()
    mediaPlayer?.let { player ->
        if (player.isPlaying) {
            player.pause()
        }
    }
}

For video, pausing in onPause is also important because most of the time users expect video to stop when they are not looking at the screen. If you want to support resuming from the same position after a rotation, you can save the current position in onSaveInstanceState and restore it later, but full state management belongs to configuration changes and lifecycle topics.

Handling Errors and Edge Cases

Media playback can fail for several reasons, including invalid URLs, unsupported formats, network issues, or missing resources. Both MediaPlayer and VideoView allow you to register error listeners.

For MediaPlayer, you saw the setOnErrorListener usage earlier. Returning true from the listener means you have handled the error. Often you will stop playback, show a toast message to the user, and free resources.

For VideoView you use:

videoView.setOnErrorListener { player, what, extra ->
    // Example: show a simple message
    Toast.makeText(this, "Error playing video", Toast.LENGTH_SHORT).show()
    true
}

It is also good to check if the device has network connectivity before starting streaming audio or video. Connectivity checks are a general networking topic and are better explored in the networking chapters, but you should be aware that streaming depends on network state.

Summary

In Android, audio and video playback revolve around a few core APIs. MediaPlayer is your main tool for basic audio and for simple video behind views. VideoView simplifies displaying video on screen and offers quick integration with MediaController to show standard playback controls. You learned how to load media from resources, file paths, and URLs, how to start, pause, and stop playback, and how to connect playback behavior with the activity lifecycle.

As you progress, you will combine media playback with background tasks, notifications, and services to build richer experiences like music players or video apps that keep running when the user leaves the screen.

Views: 2

Comments

Please login to add a comment.

Don't have an account? Register now!