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. By Bhavesh Misri.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
MediaPlayer: Simplified Video Playback on Android
25 mins
- Getting Started
- Understanding the code
- MediaPlayer
- Playing Video From Local Resources
- MediaPlayer is prepared
- Improving the UX
- Interacting with the SeekBar
- Playing Video from the Gallery
- Playing the video after it has been downloaded
- Releasing the Resources
- Executing Asynchronously
- Streaming a Video From a URL
- Digital Right Management
- DRM MediaPlayer
- Where To Go From Here?
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:
You use this parameter to update MediaPlayer
's progress if the seekbar
's progress level is manually changed.
-
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.