This codelab is part of the Advanced Android Development training course, developed by the Google Developers Training team. You will get the most value out of this course if you work through the codelabs in sequence.

For complete details about the course, see the Advanced Android Development overview.

Introduction

A multimedia app that plays audio or video usually has two parts:

The architecture of a media app can get a lot more complicated, especially if your app's entire purpose is to play audio or video. Android provides an overall architecture for audio and video apps, and large number of media-related classes, which you can learn about in Media Apps Overview.

The simplest possible way to play a video clip in your app, however, is to use the VideoView class to play the video, and the MediaController class as the UI to control it. In this practical you build a simple app that plays a video clip that is either embedded in the app's resources, or available on the internet.

What you should already know

You should be familiar with:

What you'll learn

What you'll do

In this practical you build the SimpleVideoView app from scratch. SimpleVideoView plays a video file in its own view in your app:

The SimpleVideoView app includes a familiar set of media controls. The controls allow you to play, pause, rewind, fast-forward, and use the progress slider to skip forward or back to specific places in the video.

You start by playing a video file embedded with the app's resources. In the second task, you modify the app to buffer and play a video file from the internet.

The simplest way to play video in your app is to use a VideoView object, which is part of the Android platform. The VideoView class combines a media player (the MediaPlayer class) with a SurfaceView to actually display the video. Although it does not provide a lot of features or customization, VideoView class implements a lot of the basic behavior you need to play a video in your app.

Your app can play media files from a variety of sources, including embedded in the app's resources, stored on external media such as an SD card, or streamed from the internet. In this example, to keep things simple, you embed the video file in the app itself.

For the video playback UI, Android provides the MediaController view. The MediaController view provides a set of common media-control buttons that can control a media player (the VideoView object).

In this task you build the first iteration of the SimpleVideoView app, which plays a video clip.

1.1 Create the project and initial layout

This first iteration of the app starts playing a video clip when it launches, and plays the clip to the end.

  1. Create a new Android project. Call it SimpleVideoView and use the Empty activity template. You can leave all other defaults the same.
  2. Create a new raw resource directory to hold the video file the app will play: click the res directory in the Project view and select File > New > Android resource directory.
  3. Change both the Directory name and Resource type to "raw". Leave all other options as is and click OK.

  1. Download the Tacoma Narrows MP4 video file. If the video begins to play in your browser rather than downloading, you can use File > Save Page As... to save the file.
  2. Move the tacoma_narrows.mp4 file into the raw directory in your project.
  3. In activity_main.xml, delete the existing TextView, and replace it with a VideoView element with these attributes:
<VideoView
   android:id="@+id/videoview"
   android:layout_width="0dp"
   android:layout_height="0dp"
   android:layout_margin="8dp"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintDimensionRatio="4:3"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent"/>

When layout_width and layout_height are 0dp, the size and position of the VideoView are calculated dynamically based on the other constraints. The layout_constraintDimensionRatio attribute indicates that when the app calculates the size of the VideoView, the ratio of the width to the height should be 4:3. This constraint keeps the aspect ratio of the video the same and prevents the view from being stretched too far in either direction (depending on how the device is rotated).

1.2 Implement MainActivity

  1. In MainActivity.java, add a constant for the name of the video sample file, and a member variable to hold the VideoView instance.
private static final String VIDEO_SAMPLE = "tacoma_narrows";
private VideoView mVideoView;
  1. Also in MainActivity, create a private method called getMedia() that takes a String and returns a Uri. The implementation looks like this:
private Uri getMedia(String mediaName) {
   return Uri.parse("android.resource://" + getPackageName() + 
      "/raw/" + mediaName);
}

This method converts the name of the sample media file (a string) into a full URI object representing the path to the sample in your app's resources. Note that although the actual filename in the app's resource directory is tacoma_narrows.mp4, the string name and resulting resource name do not include the extension.

  1. In onCreate(), get a reference to the VideoView in the layout:
mVideoView = findViewById(R.id.videoview);
  1. Create a new private method called initializePlayer() that takes no arguments and returns void.
private void initializePlayer() {

}
  1. Inside initializePlayer(), use the getMedia() and setVideoUri() methods to set the media URI that the VideoView will play.
Uri videoUri = getMedia(VIDEO_SAMPLE);
mVideoView.setVideoURI(videoUri);
  1. Call start() on the VideoView to start playing.
mVideoView.start();
  1. Create a private method called releasePlayer(), and call the stopPlayback() method on the VideoView. This stops the video from playing and releases all the resources held by the VideoView.
private void releasePlayer() {
   mVideoView.stopPlayback();
}

1.3 Implement Activity lifecycle methods

Media playback uses many more system resources than most apps. It is critical that your app completely release those resources when it's not using them, even if the app is paused in the background. Implement the Activity lifecycle method for onStop() to release the media resources when the app is stopped. Implement onStart() to initialize the resources when the app is started again.

  1. Override the onStart() method and call initializePlayer().
@Override
protected void onStart() {
   super.onStart();

   initializePlayer();
}
  1. Override the onStop() method and call releasePlayer().
@Override
protected void onStop() {
   super.onStop();

   releasePlayer();
}
  1. Override onPause() and add a test for versions of Android older than N (lower than 7.0, API 24). If the app is running on an older version of Android, pause the VideoView here.
@Override
protected void onPause() {
   super.onPause();

   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
       mVideoView.pause();
   }
}

This test is required because the behavior of onPause() and onStop() changed in Android N (7.0, API 24). In older versions of Android, onPause() was the end of the visual lifecycle of your app, and you could start releasing resources when the app was paused.

In newer versions of Android, your app may be paused but still visible on the screen, as with multi-window or picture-in-picture (PIP) mode. In those cases the user likely wants the video to continue playing in the background. If the video is being played in multi-window or PIP mode, then it is onStop() that indicates the end of the visible life cycle of the app, and your video playback should indeed stop at that time.

If you only stop playing your video in onStop(), as in the previous step, then on older devices there may be a few seconds where even though the app is no longer visible on screen, the video's audio track continues to play while onStop() catches up. This test for older versions of Android pauses the actual playback in onPause() to prevent the sound from playing after the app has disappeared from the screen.

  1. Build and run the app.

When the app starts, the video file is opened and decoded, begins playing, and plays to the end. There is no way to control the media playback, for example using pause, play, fast-forward, or rewind. You add these capabilities in the next task.

1.4 Add a MediaController

An app that plays video but does not provide a way for the user to control that video is less than useful. The Android platform provides a way to control media using the MediaController view, which is in the android.widget package. A MediaController view combines the most common media control UI elements (buttons for play, pause, fast-forward, and rewind, as well as a seek or progress bar) with the ability to control an underlying media player, such as a VideoView.

To use a MediaController view, you don't define it in your layout as you would other views. Instead you instantiate it programmatically in your app's onCreate() method and then attach it to a media player. The controller floats above your app's layout and enables the user to start, stop, fast-forward, rewind, and seek within the video.

In the figure above:

  1. A VideoView, which includes a MediaPlayer to decode and play video, and a SurfaceView to display the video on the screen
  2. A MediaController view, which includes UI elements for video transport controls (play, pause, fast-forward, rewind, progress slider) and the ability to control the video

In this task you add a MediaController to the SimpleVideoView app.

  1. Locate the onCreate() method in MainActivity. Below the assignment of the mVideoView variable, create a new MediaController object and use setMediaPlayer() to connect the object to the VideoView.
MediaController controller = new MediaController(this);
controller.setMediaPlayer(mVideoView);
  1. Use setMediaController() to do the reverse connection, that is, to tell the VideoView that the MediaController will be used to control it:
mVideoView.setMediaController(controller);
  1. Build and run the app. As before, the video begins to play when the app starts. Tap the VideoView to make the MediaController appear. You can then use any of the elements in that controller to control media playback. The MediaController disappears on its own after three seconds.

1.5 Preserve playback position throughout the activity lifecycle

When the onStop() method is called and your app goes into the background, or restarts because of a configuration change, the VideoView class releases all its resources and does not preserve any video playback state such as the current position. This means that each time the app starts or comes into the foreground, the video is reopened and plays from the beginning.

To get a baseline for comparison, run the app, if it's not already running. With your app in the foreground, try the following tasks:

Note in each of these cases how the video restarts from the beginning.

In this task you keep track of the current playback position, in milliseconds, throughout the app's lifecycle. If the video resources are released, the video can restart where it left off.

  1. In MainActivity, add a member variable to hold the video's current position (initially 0). The playback position is recorded in milliseconds from 0.
private int mCurrentPosition = 0;
  1. Add a member variable to hold the key for the playback position in the instance state bundle:
private static final String PLAYBACK_TIME = "play_time";
  1. In initializePlayer(), after setVideoUri() but before start(), check to see whether the current position is greater than 0, which indicates that the video was playing at some point. Use the seekTo() method to move the playback position to the current position.
if (mCurrentPosition > 0) {
   mVideoView.seekTo(mCurrentPosition);
} else {
   // Skipping to 1 shows the first frame of the video.
   mVideoView.seekTo(1);
}

If the current position is 0, the video has not yet played. Use seekTo() to set the playback position to 1 millisecond. This will show the first frame of the video rather than a black screen.

  1. Override the onSaveInstanceState() method to save the value of mCurrentTime to the instance state bundle. Get the current playback position with the getCurrentPosition() method.
@Override
protected void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);

   outState.putInt(PLAYBACK_TIME, mVideoView.getCurrentPosition());
}
  1. In onCreate(), check for the existence of the instance state bundle, and update the value of mCurrentTime with the value from that bundle. Add these lines before you create the MediaController.
if (savedInstanceState != null) {
   mCurrentPosition = savedInstanceState.getInt(PLAYBACK_TIME);
}
  1. Build and run the app. Repeat the baseline tasks and note how the playback position is saved in each case.

1.6 Add an onCompletion() callback

The VideoView class (and the MediaPlayer it contains) lets you define listeners for several events related to media playback. For example:

Listeners for the preparation and completion events are the most common listeners to implement in a media app. You haven't yet needed to handle media preparation in the SimpleVideoView app, because the video clip is embedded in the app and is fairly small, so it plays quickly. This may not be the case for larger video clips or those you play directly from the internet. You come back to preparing media in a later task.

When media playback is finished, the completion event occurs, and the onCompletion() callback is called. In this task you implement a listener for onCompletion() events to display a toast when the playback finishes.

  1. At the end of the initializePlayer() method, create a new MediaPlayer.OnCompletionListener, override the onCompletion() method, and use setOnCompletionListenter() to add that listener to the VideoView.
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
   @Override
   public void onCompletion(MediaPlayer mediaPlayer) {
       // Implementation here.
   }
});
  1. Inside the onCompletion() method, add a Toast to display the message "Playback completed."
  2. After the toast, add a call to seekTo() to reset the playback and the MediaController to the beginning of the clip.
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
   @Override
   public void onCompletion(MediaPlayer mediaPlayer) {
       Toast.makeText(MainActivity.this, "Playback completed", 
          Toast.LENGTH_SHORT).show();
       mVideoView.seekTo(1);
   }
});

By default, when the media finishes playing, both the VideoView and the MediaController show the end state of the media. The code shown above resets both the player and the controller to the start of the video so that the video can be played again.

  1. Build and run the app. Tap the video to display the MediaController, and move the slider nearly all the way to the end. When the video finishes playing, the toast appears and the controller resets to the beginning of the clip.

In a real-world app, many media files are too large to embed directly in your app as you did with the video in the SimpleVideoView app. More commonly, if your app plays media it will get the files it needs from external storage such as an SD card, or locate and buffer the media file from a server on the internet.

If you play media from the internet, the VideoView class and the underlying MediaPlayer implement a lot of the background work for you. When you use VideoView you don't need to open a network connection, or set up a background task to buffer the media file.

You do, however, need to handle the time period between when you tell your app to play the media file and when the content in that file is actually available to play. If you do nothing, your app may appear to freeze for quite a long time while the file is buffering, especially on slow network connections. Even a message to the user that something is going on provides a better user experience.

VideoView (and MediaPlayer) provide a preparation event listener to handle this case. To use the onPrepared() callback, set the URI of the media the VideoView will play, then receive a callback when that media is ready to play.

In this task you modify the SimpleVideoView app to play a video from a URL on the internet. You use the preparation event listener to handle updating the app's UI while the file is being loaded.

2.1 Add a buffering message

In this task you modify the app layout to display a simple buffering message, and you use view visibility to show and hide the message at the appropriate time.

  1. In the main layout file, add a TextView element. The new TextView is centered in the layout, same as the VideoView, and appears on top.
<TextView
   android:id="@+id/buffering_textview"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_margin="8dp"
   android:text="Buffering..."
   android:textSize="18sp"
   android:textStyle="bold"
   android:textColor="@android:color/white"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent"/>
  1. In MainActivity, change the VIDEO_SAMPLE constant to indicate the URL for the media file.
private static final String VIDEO_SAMPLE =
     "https://developers.google.com/training/images/tacoma_narrows.mp4";

You can use any URL for any video that is available as a file on the internet.

  1. Add a member variable to hold the TextView that shows the buffering message.
private TextView mBufferingTextView;
  1. In onCreate(), get a reference to the TextView in the layout:
mBufferingTextView = findViewById(R.id.buffering_textview);
  1. In the getMedia() method, change the implementation to test whether the incoming string is a URL or a raw resource. Return the appropriate URI.
private Uri getMedia(String mediaName) {
   if (URLUtil.isValidUrl(mediaName)) {
       // media name is an external URL
       return Uri.parse(mediaName);
   } else { // media name is a raw resource embedded in the app
       return Uri.parse("android.resource://" + getPackageName() +
               "/raw/" + mediaName);
   }
}

2.2 Add the internet permission

In the Android manifest file, add an internet permission to enable the app to access the media file on the internet.

  1. In AndroidManifest.xml, add this line just before the <application> element, at the top level of the manifest.
<uses-permission android:name="android.permission.INTERNET" />

2.3 Add an onPrepared() callback

Preparing video or other media can involve streaming it, buffering it in memory, and decoding it to make it ready to play. With VideoView and MediaPlayer, the preparation step happens asynchronously in a separate thread, so your app does not have to pause and wait. Use the onPrepared() callback to enable your app to be notified when the preparation step has completed and the media is ready to play.

  1. In the initializePlayer() method, before the definition for setOnCompletionListener(), create a new MediaPlayer.onPreparedListener, override the onPrepared() method, and use setOnPreparedListener() to add that listener to the VideoView.
mVideoView.setOnPreparedListener(
   new MediaPlayer.OnPreparedListener() {
      @Override
      public void onPrepared(MediaPlayer mediaPlayer) {
         // Implementation here.
      }
});
  1. Inside the onPrepared() method, set the visibility of the TextView to invisible. This removes the "Buffering..." message.
mBufferingTextView.setVisibility(VideoView.INVISIBLE);
  1. In initializePlayer(), locate the lines of code that test for the current position, seek to that position, and start playback. Move those lines into the onPrepared() method, placing them after the calls to setVisibility(). The final onPrepared() definition looks like this:
mVideoView.setOnPreparedListener(
   new MediaPlayer.OnPreparedListener() {
      @Override
      public void onPrepared(MediaPlayer mediaPlayer) {
          mBufferingTextView.setVisibility(VideoView.INVISIBLE);

          if (mCurrentPosition > 0) {
              mVideoView.seekTo(mCurrentPosition);
          } else {
              mVideoView.seekTo(1);
          }

          mVideoView.start();
      }
});
  1. At the top of initializePlayer(), before setting the media URI to play, restore the visibility of the buffering TextView:
mBufferingTextView.setVisibility(VideoView.VISIBLE);

Each time initializePlayer() is called, the buffering message should be turned on. It is turned off only when onPrepared() is called and the media is ready to play.

  1. Build and run the app. Initially the "Buffering..." message appears on the screen. Depending on the speed of your network connection, after some moments the video will appear and start playing.

Android Studio project: SimpleVideoView

Challenge: Replace the MediaController in the SimpleVideoView app with a single button.

Challenge: Add a "Skip 10s" button. When the user taps the button, the current position of the media playback should skip ahead ten seconds.

The related concept documentation is 13.1 Simple media playback.

Android developer documentation:

Android API reference:

Other:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Use the SimpleVideoView app as a starting point for an app that plays multiple videos.

Answer these questions

Question 1

VideoView is a wrapper for which two classes?

Question 2

Which of the following sources can VideoView play?

Question 3

Which of these callbacks are available for media events in the VideoView class?

Submit your app for grading

Guidance for graders

Check that the app has the following features:

To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.