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.
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.
You should be familiar with:
Activity
lifecycle, and how configuration changes such as changes to the device screen orientation affect that lifecycleVideoView
class to play video in your appMediaController
class to control video playing in a VideoView
VideoView
VideoView
and MediaController
classes. 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.
This first iteration of the app starts playing a video clip when it launches, and plays the clip to the end.
tacoma_narrows.mp4
file into the raw
directory in your project. 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).
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;
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.
onCreate()
, get a reference to the VideoView
in the layout:mVideoView = findViewById(R.id.videoview);
initializePlayer()
that takes no arguments and returns void
. private void initializePlayer() {
}
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);
start()
on the VideoView
to start playing. mVideoView.start();
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();
}
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.
onStart()
method and call initializePlayer()
.@Override
protected void onStart() {
super.onStart();
initializePlayer();
}
onStop()
method and call releasePlayer()
.@Override
protected void onStop() {
super.onStop();
releasePlayer();
}
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.
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.
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:
VideoView
, which includes a MediaPlayer
to decode and play video, and a SurfaceView
to display the video on the screenMediaController
view, which includes UI elements for video transport controls (play, pause, fast-forward, rewind, progress slider) and the ability to control the videoIn this task you add a MediaController
to the SimpleVideoView app.
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);
setMediaController()
to do the reverse connection, that is, to tell the VideoView
that the MediaController
will be used to control it:mVideoView.setMediaController(controller);
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. 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.
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;
private static final String PLAYBACK_TIME = "play_time";
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.
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());
}
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);
}
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.
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.
}
});
onCompletion()
method, add a Toast
to display the message "Playback completed." 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.
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.
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.
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"/>
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.
TextView
that shows the buffering message.private TextView mBufferingTextView;
onCreate()
, get a reference to the TextView
in the layout:mBufferingTextView = findViewById(R.id.buffering_textview);
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);
}
}
In the Android manifest file, add an internet permission to enable the app to access the media file on the internet.
AndroidManifest.xml
, add this line just before the <application>
element, at the top level of the manifest. <uses-permission android:name="android.permission.INTERNET" />
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.
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.
}
});
onPrepared()
method, set the visibility of the TextView
to invisible. This removes the "Buffering..." message. mBufferingTextView.setVisibility(VideoView.INVISIBLE);
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();
}
});
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.
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.
VideoView
class. The VideoView
class wraps a MediaPlayer
and a SurfaceView
. VideoView
can be added to a layout like any other view.VideoView.setVideoURI()
to specify the URI of the video file to play, whether that URI is in the app's resources, or on the internet. This method preloads the video sample data in an asynchronous thread. VideoView.start()
to start the video playback, once the video is available for playing.VideoView.stopPlayback()
to stop the video playback and release the resources the VideoView
is using. VideoView.getCurrentPosition()
to retrieve the current playback position, in milliseconds. Use VideoView.seekTo()
to seek to a specific playback position, in milliseconds. MediaController
widget (android.widget.MediaController
) provides a set of common controls (play, pause, fast-forward, rewind, and a progress slider) for media playback. MediaController
is a ViewGroup
that can be added programmatically to any layout to control a VideoView
. MediaController.setMediaPlayer()
to attach a media player such as a VideoView
to the controller. VideoView.setMediaController()
to attach a MediaController
to the VideoView
. onStart()
and onStop()
lifecycle methods to start and stop video playback in your app. These methods represent the visible lifecycle of your app. onPause()
represents the end of the app's visible lifecycle, but audio may continue to play for several seconds until onStop()
is called. Add a test for the Android version to pause video playback in onPause()
on older versions of Android. VideoView
class does not preserve the state of video playback across any lifecycle transition, including changes to background/foreground, device orientation, and multi-window mode. To retain this state information, save and restore the current video position in the activity instance state bundle.onStop()
. Resources used by VideoView
are released when you call stopPlayback()
. MediaPlayer
class (and thus VideoView
) provides a set of media event callbacks for various events, including onCompletion()
and onPrepared()
. onCompletion()
callback is invoked when the media playback is complete. Use this callback to announce the end of the media or reset the UI to a default state. onPrepared()
callback is invoked when the media is ready to play. Depending on the media and its location (embedded in the app, available on local storage, played from the internet), it may take some time for the media to be available. Use the onPrepared()
callback to keep the user informed about the state of the media by providing a "Buffering" message or some other status message.The related concept documentation is 13.1 Simple media playback.
Android developer documentation:
Android API reference:
VideoView
MediaPlayer
MediaPlayer.OnCompletionListener
MediaPlayer.OnPreparedListener
MediaController
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.
Use the SimpleVideoView app as a starting point for an app that plays multiple videos.
RecyclerView
for the list. VideoView
and MediaController
.VideoView
is a wrapper for which two classes?
Video
and View
MediaPlayer
and MediaController
MediaPlayer
and SurfaceView
MediaPlayer
and Uri
Which of the following sources can VideoView
play?
Which of these callbacks are available for media events in the VideoView
class?
onError()
onInfo()
onPrepared()
onBufferingUpdate()
Check that the app has the following features:
RecyclerView
. The videos may come from any source. VideoView
and a media controller.onComplete()
callback to determine whether to play the next video in the list or finish playing altogether. To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.