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.

4.6 (5) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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:

  1. Make the progressBar invisible.
  2. Tell MediaPlayer to start the video.

Now Build and Run.

Playing the video from local resource

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.

  1. seconds returns the total duration of the video in seconds.
  2. 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.

Note: You implemented the MM:SS format but you can add the logic to incorporate HH:MM:SS format as well.

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:

  1. Sets the maximum value for SeekBar
  2. Sets default values for TextViews which shows the progress and the total duration of the video.
  3. Hides the ProgressBar.
  4. 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:

  1. Check if MediaPlayer is playing any video.
  2. If it is, you pause the video and change the button icon to play.
  3. 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.

Enabling play/pause and progress in app

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.

  1. seekBar: Instance of the seekBar.
  2. progress: Progress of seekBar in seconds.
  3. 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 the seekbar'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. :]

Enabling seeking in the app

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:

  1. You get an Intent which is a messaging object in Android used to request different action types.
  2. You ensure that the intent type is a video format.
  3. Then you specify this is an Intent with an action of GET content type.
  4. 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:

  1. Check the resultCode. If the operation was executed successfully, it returns Activity.RESULT_OK.
  2. Check the requestCode to identify the caller and define if the requestCode was actually a video.
  3. Assign URI to the selectedVideoUri variable you declared earlier.
  4. Invoke surfaceCreated() by calling video_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.

Playing video selected from gallery