MediaPlayer: Simplified Video Playback on Android
Playing videos is a common requirement in Android apps. In this tutorial learn about handling video playback via the MediaPlayer API on Android.
Version
- Kotlin 1.4, Android 4.4, Android Studio 4.0

Playing audio or video in Android apps is a common requirement for many projects. Many apps in the Google Play Store, even some non-streaming ones, provide audio and video playback. Above all, it’s an important topic that will lead you to many job opportunities.
In this tutorial, you’ll build an Android app that plays video from various sources, such as videos locally stored in your phone, the res folder, gallery and a URL using MediaPlayer
.
Along the way you’ll learn about:
- MediaPlayer.
- The states of MediaPlayer.
- Playing video from the res/raw folder.
- Playing video from a URL.
- Best practices for MediaPlayer.
- Digital Right Management.
Getting Started
Download the materials using the Download Materials button at the top or the bottom of this page. Extract and open the starter project in Android Studio.
Build and run. You’ll see something like this:
The app isn’t interactive yet because the starter project only consists of UI and some basic code. You’ll implement the functionality throughout this tutorial.
Understanding the code
Before the hands-on part of this tutorial, take some time to understand the codebase you’ll build on. Navigate to these three files and check out their contents:
- AndroidManifest.xml: At the top of the manifest file you’ll find
android.permission.INTERNET
. In this tutorial, you’ll play a video from a URL, so you’ll need the INTERNET permission. -
activity_video.xml: This is the one and only layout file in the project. It consists of:
- A
VideoView
to play video. - A
ProgressBar
to shows the user it’s loading a video. - Two
TextViews
and aSeekBar
to show progress. - An
ImageButton
to play and pause a video.
- A
-
VideoActivity.kt: This might look a bit overwhelming, but if you skim through and read the comments, you’ll find it quite simple.
The class implements a few interface classes to manage
MediaPlayer
andseekBar
callbacks. You’ll understand these implementations once you start working on the functionality.Also, to keep your app from falling asleep, you’ll need to keep your screen on. Notice this line inside
onCreate()
which adds a flagwindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
. Setting this flag ensures your screen stays on, while the app is in use. You don’t want the user’s device to fall asleep and lock while they’re watching a video on your app, do you?
MediaPlayer
MediaPlayer
is a part of the Android multimedia framework that plays audio or video from the resource directory and gallery. It also streams music or video from a URL.
When a media file plays, the MediaPlayer
API goes through several states, which are summarized in the diagram below:
That’s a lot of information to take in, but before you can use the
-
Idle State:
MediaPlayer
is in an idle state when you first instantiate it, or first create it using the new keyword. You also reach this state after you callreset()
.At this stage, you can’t play, pause or stop the media. If you try to force it, the app might crash.
-
End State: Calling
MediaPlayer
‘srelease()
method frees resources and moves it to the end state. At this stage, you can’t play or pause the media. -
Error State: You reach this state if you try to play, pause or stop an uninstantiated
MediaPlayer
object. However, you can catch the error using MediaPlayer’sonErrorListener.onError()
callback. -
Initialized State:
MediaPlayer
reaches this state when you set a data source. To set a one, usesetDataSource()
method. Be aware that you can only set a data source whenMediaPlayer
is in an idle state or it’ll throw an IllegalStateException. -
Prepared State: Before you play any media from a file or a URL, you need to prepare your
MediaPlayer
by calling eitherprepare()
orprepareAsync()
. Once prepared, it reaches this state and callsonPreparedListener()
. -
Started State: Once
MediaPlayer
is ready, you can play the media by callingstart()
. While the music or video plays,MediaPlayer
is in a started state. -
Paused State: When you pause the media,
MediaPlayer
is in this state. To pauseMediaPlayer
, callpause()
. -
Stopped State:
MediaPlayer
is in this state when media stops playing. IfMediaPlayer
is in this state and you want to play the media again, you have to prepare it again by callingprepare()
orprepareAsync()
. -
PlaybackCompleted State: When the playback is complete,
MediaPlayer
is in this state. Additionally, invokesonCompletion.onCompletion()
. IfMediaPlayer
is in this state you can call thestart()
and play audio or video again.
Okay, enough theory. It’s time to code!
Playing Video From Local Resources
You will start with playing a video file in the raw directory. There’s a video in the starter project named test_video.mp4.
From the states you learned above, you may see that to play the video in your VideoView
, you have to:
- Set the data source using
MediaPlayer
- Call the
start()
function
But you may be wondering where do you add this code? For this, a couple of options may come to your mind.
The most common way to add new code into Android apps is to put it in the onCreate()
method, because you know it will be the entry point of your activity. But in the case of Media Player
that’s not good practice for two reasons:
- First, you don’t know the size of the video or how much time
MediaPlayer
will take to load it. - Second,
MediaPlayer
can play a video inVideoView
through its surface, which also takes time.
Take a look at the code inside the onCreate()
method of VideoActivity. In there you will see the following line: video_view.holder.addCallback(this)
, this gets a callback when the VideoView
‘s surface is ready for playing video. When this surface is ready, it calls surfaceCreated()
.
Next, inside surfaceCreated()
replace // TODO (1) with:
mediaPlayer.apply {
setDataSource(applicationContext,
// 1
Uri.parse("android.resource://$packageName/raw/test_video"))
// 2
setDisplay(surfaceHolder)
// 3
prepareAsync()
}
In this code, you perform three tasks:
- First, you pass the location, URI, of the video.
- Then, you set
MediaPlayer
‘s display to theVideoView
‘s surface by callingsetDisplay(surfaceHolder)
.Note: If you started theMediaPlayer
without calling this function, you wouldn’t see any video becauseMediaPlayer
wouldn’t know where to display the video. - Finally, you call
prepare()
. This function preparesMediaPlayer
to playback the video synchronously on the main thread.
MediaPlayer is prepared
When MediaPlayer
is prepared, it invokes onPrepared()
. This is the point where you will start playing the video.
To do that, replace // TODO (2) inside onPrepared()
with:
// 1
progress_bar.visibility = View.GONE
//2
mediaPlayer?.start()
Here you:
- Make the
progressBar
invisible. - Tell
MediaPlayer
to start the video.
Now Build and Run.
Tada! Now you can play the video from your raw or resource directory, but still, something is missing. The seekBar
isn’t updating, and you can’t pause or play the video.
Next, you’ll implement the missing functionality and make the app more intuitive.
Improving the UX
Before you add the functionality to play, pause or fast-forward video using SeekBar, you need to create a few functions and extension properties.
At the bottom of VideoActivity.kt
replace // TODO (3) with:
// 1
private val MediaPlayer.seconds: Int
get() {
return this.duration / SECOND
}
// 2
private val MediaPlayer.currentSeconds: Int
get() {
return this.currentPosition / SECOND
}
These extension properties help you implement those functionalities. MediaPlayer itself provides you with the video duration and currentPosition, therefore you don’t have to worry for tracking them.
-
seconds
returns the total duration of the video in seconds. -
currentSeconds
returns the current playback position in seconds of the video.
Next, convert the seconds to a more readable format by replacing // TODO (4) with:
private fun timeInString(seconds: Int): String {
return String.format(
"%02d:%02d",
(seconds / 3600 * 60 + ((seconds % 3600) / 60)),
(seconds % 60)
)
}
In this function you convert seconds to MM:SS format. If the video is more than 60 seconds long, it’s better to show 2:32 minutes rather than 152 seconds.
In adition, you’ll create three functions that initialize and periodically update seekbar
and convert the seconds to a more readable format.
To initialize seekbar
, replace // TODO (5) with:
private fun initializeSeekBar() {
// 1
seek_bar.max = mediaPlayer.seconds
// 2
text_progress.text = getString(R.string.default_value)
text_total_time.text = timeInString(mediaPlayer.seconds)
// 3
progress_bar.visibility = View.GONE
// 4
play_button.isEnabled = true
}
When MediaPlayer
prepares to play the video this function is executed. The code performs the following:
- Sets the maximum value for
SeekBar
- Sets default values for
TextViews
which shows the progress and the total duration of the video. - Hides the
ProgressBar
. - Enables the
play button
.
Next, to periodically update the seekbar
as the video plays, replace // TODO (6) with:
private fun updateSeekBar() {
runnable = Runnable {
text_progress.text = timeInString(mediaPlayer.currentSeconds)
seek_bar.progress = mediaPlayer.currentSeconds
handler.postDelayed(runnable, SECOND.toLong())
}
handler.postDelayed(runnable, SECOND.toLong())
}
In this function, you use Runnable to execute the code periodically after every one second. Runnable is a Java interface and executes on a separate thread. Since it executes on a separate thread, it won't block your UI and the SeekBar
and TextViews
will update periodically.
Instead of playing the video when MediaPlayer
is ready, it would be better if the user could play and pause the video by using the imageButton
.
Inside the onCreate()
function replace // TODO (7) with:
play_button.setOnClickListener {
// 1
if (mediaPlayer.isPlaying) {
// 2
mediaPlayer.pause()
play_button.setImageResource(android.R.drawable.ic_media_play)
} else {
// 3
mediaPlayer.start()
play_button.setImageResource(android.R.drawable.ic_media_pause)
}
}
Here you:
- Check if
MediaPlayer
is playing any video. - If it is, you pause the video and change the button icon to play.
- If not, you play the video and change the button icon to pause.
Next, replace all the code added to the onPrepared()
's body with a call to initalizeSeekBar()
and updateSeekBar()
which you created earlier:
initializeSeekBar()
updateSeekBar()
Now your app is more intuitive.
Build and Run. You can play or pause the video and see the progress on seekBar
and TextView
.
Interacting with the SeekBar
Now the app is more intuitive for the user, except for the SeekBar
. Even though the SeekBar
updates with time, you can't fast-forward or rewind the video by tapping or dragging it.
For that, you'll use the onProgressChanged()
method of SeekBar
. Whenever there's a change in the SeekBar
's progress, it will invoke this function.
The SeekBar
change listener is already in the code, so navigate to onProgressChanged()
and replace // TODO (8) with:
if (fromUser){
mediaPlayer.seekTo(progress * SECOND)
}
The function onProgressChanged()
has three parameters:
-
seekBar: Instance of the
seekBar
. -
progress: Progress of
seekBar
in seconds. -
fromUser: Boolean which tells you if the change is because of user interaction. If the change in progress is due to user interaction, it'll be true. If not, it'll be false.
You use this parameter to update
MediaPlayer
's progress if theseekbar
's progress level is manually changed.
Now your user can fast-forward or rewind the video using seekBar
. Build and run to give it a try. :]
Playing Video from the Gallery
The app works great now, but it would be better if users could select a video from their gallery and play it. You'll implement that next.
The options button in the toolbar and its functionality are already in the starter project.
Before you add new code, you need to understand what's happening in the existing code. When the user selects an option the app invokes onOptionItemSelected()
with the menuItem
as a parameter.
Now, inside the when statement replace // TODO (9) with:
// 1
val intent = Intent()
// 2
intent.type = "video/*"
// 3
intent.action = Intent.ACTION_GET_CONTENT
// 4
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_file)), GET_VIDEO)
Here you use an intent to get the URI for the file the user selected from the gallery. In the code:
- You get an Intent which is a messaging object in Android used to request different action types.
- You ensure that the intent type is a video format.
- Then you specify this is an Intent with an action of GET content type.
- Finally, you trigger the intent waiting for a result.
Playing the video after it has been downloaded
Once the activity returns something from the intent, startActivityForResult()
invokes onActivityResult()
, which passes GET_VIDEO as a request_code
.
Inside the startActivityForResult()
function replace // TODO (10) with:
// 1
if (resultCode == Activity.RESULT_OK) {
// 2
if (requestCode == GET_VIDEO) {
// 3
selectedVideoUri = data?.data!!
// 4
video_view.holder.addCallback(this)
}
}
Here you:
- Check the
resultCode
. If the operation was executed successfully, it returnsActivity.RESULT_OK
. - Check the
requestCode
to identify the caller and define if the requestCode was actually a video. - Assign URI to the
selectedVideoUri
variable you declared earlier. - Invoke
surfaceCreated()
by callingvideo_view.holder.addCallback(this)
.
It will probably ask you to import Activity, so add it at the top with your other inputs with:
import android.app.Activity
Next, update setDataSource()
so you pass the correct URI in the parameter. Navigate to surfaceCreated()
and replace the function body with:
mediaPlayer.apply {
setDataSource(applicationContext, selectedVideoUri)
setDisplay(surfaceHolder)
prepareAsync()
}
Voila! Now your user can open the gallery, select a video and play it in your app. Build and run to see how it works now.
Releasing the Resources
MediaPlayer
API consumes a lot of resources. So, you should always release those resources by calling release()
.
As per the official Android documentation, you should release the resources whenever your app is in background or stopped state. Failing to could lead to continuous battery consumption for the device and playback failure for other apps if multiple instances of the same codec aren't supported. It could also degrade the device's performance in general.
In onDestroy()
, replace // TODO (11) with:
handler.removeCallbacks(runnable)
mediaPlayer.release()
Here you remove the runnable from the thread or message queue by calling removeCallbacks()
. If you didn't, your runnable would keep executing in the background.
Build and Run. It looks like nothing changed, but you ensured the resources release when the user kills the app.
Executing Asynchronously
Asynchronous code is very important for Android apps. You may have not noticed, but currently, you are loading the video asynchronously.
In most cases, videos are heavy and take time to load, therefore, you need to make the code asynchronous. When that happens, MediaPlayer
will lead the app to ANR, or Application Not Responding, state. In short, your app will become non-responsive and crash.
To load the media file asynchronously what you did was using prepareAsync()
instead of prepare()
inside the surfaceCreated()
function. In other words, if you try to use prepare()
in that line and run the app, it will be there loading for a long time.
Seriously, that's it! That's all you had to do.
Next, you'll implement asynchronously streaming a video from a URL.
Streaming a Video From a URL
Fortunately, you don't have to make many changes to prepare MediaPlayer
asynchronously and stream a video from a URL.
In surfaceCreated()
, replace setDataSource()
with:
setDataSource(URL)
Then, in surfaceCreated()
replace prepare()
with prepareAsync()
.
After you make those changes, surfaceCreated()
will look like this:
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
mediaPlayer.apply {
setDataSource(URL)
setDisplay(surfaceHolder)
prepareAsync()
}
}
Nice! Now your MyTube app can play videos from res directory, gallery and even stream a video from a URL.
Before you finish, there's one more topic you need to explore: DRM, or Digital Right Management.
Digital Right Management
DRM, or Digital Right Management, technologies are a set of tools that protect or encrypt video and audio files. There are many technologies to help with that, such as Microsoft's PlayReady and Google's Widevine. Working with DRM-protected files is a topic worthy of it's own tutorial.
MediaPlayer
added support to play DRM protected videos in Android 8 with API 26. If you want to play a DRM protected video, either you have to set your app's minSdkVersion to 26, or annotate your class with @RequiresApi(Build.VERSION_CODES.O).
DRM MediaPlayer
Because DRM mostly relies on the security-by-obscurity principle, it's hard to get your hands on any DRM protected media files. You won't play a DRM protected file as it's too complicated for this tutorial. Instead, you'll see an overview of the changes you have to make to play a DRM protected file.
Take a look at onCreate()
again. You'll see this:
mediaPlayer.setOnDrmInfoListener(this)
This line of code tells MediaPlayer
it could be accessing a DRM protected file. You don't have to worry about the app crashing if the media file is not DRM protected: It takes care of that itself. If the media file is DRM protected the app invokes onDrmInfo()
.
After that, inside onDrmInfo()
replace // TODO (12) with :
mediaPlayer?.apply {
// 1
val key = drmInfo?.supportedSchemes?.get(0)
key?.let {
// 2
prepareDrm(key)
// 2
val keyRequest = getKeyRequest(
null, null, null,
MediaDrm.KEY_TYPE_STREAMING, null
)
provideKeyResponse(null, keyRequest.data)
}
}
Here you:
- Fetch the UUID, or key, of the DRM scheme in use. There are several types of DRM protection schemes. Examine the map of UUIDs in
drmInfo?.supportedSchemes?.get(0)
and retrieve the supported schemes. Schemes supported depend mainly on the device and OEM, if you ever get a DRM, you will need to dive deeper on this topic. - Pass this retrieved key to
prepareDrm()
to prepare the DRM. - Get the decrypt key from the licensed server using
getKeyRequest()
. - Then provide the key response from the license server to the DRM engine plugin using
provideKeyResponse()
.
Android Studio will most likely ask you to import the DRM library, so add the following line to the top of your file with your other imports:
import android.media.MediaDrm
This code is only for demonstration purposes. Because of this, it won't affect the app since you don't have access to any DRM protected files.
If you ever have to work with a DRM protected media file, a better alternative would be using ExoPlayer
because it provides more functionalities.
Where To Go From Here?
Congratulations on your first MediaPlayer app! You can download the final project using the Download Materials button at the top or bottom of this tutorial.
In this tutorial, you covered some basics of MediaPlayer
like:
- Understanding
MediaPlayer
and its states. - Playing video from the res/raw folder.
- Playing video from a URL.
- Async Programming.
- Best practices for media player.
- Digital Right Management.
While you covered a lot in this tutorial, there's still more to learn. For a next step, explore ExoPlayer. Google created ExoPlayer, a library that lets you take advantage of features not yet supported by MediaPlayer
API, such as DASH and SmoothStreaming. To learn more about ExoPlayer check out our ExoPlayer tutorial.
I hope you enjoyed this tutorial. If you have any questions or comments, please join the discussion below. :]
Comments