Playback Using a Media Player

Appportunity

Build your own Android App Dev Empire

Get Instant Access

In Chapters 6 and 7, those dealing with audio and networked audio, we introduced the MediaPlayer class. The very same MediaPlayer class can also be used for video playback, in much the same manner.

Using a MediaPlayer object for video playback gives us the greatest amount of flexibility in the control of the playback itself, as compared with playing video using VideoView or via an intent. In fact, the mechanism used to handle the actual playback within the VideoView and the activity triggered via the intent is a MediaPlayer.

NOTE: Unfortunately, none of the video playback classes are as flexible as the most flexible audio playback class, AudioTrack, which allows us to generate on the fly the data that will be played.

MediaPlayer States

MediaPlayer objects operate as a state machine. This means that operations need to be performed in a specific order and various methods should be called only when the object is in the correct state to handle them.

The MediaPlayer class defines several listeners that allow applications that use it to be notified of various state changes and act accordingly.

Let's go through a full MediaPlayer example to explore further. Figure 9-4 shows the diagram again for reference.

Idle

Error

OnErrorListener.onError0 -»

OnErrorListener.onError0 -»

Error prepareAsyncO

Preparing -<-—- Initialized seekToO

prepareAsyncO

Looping — true && playback completes stopO

prepareAsyncO

Preparing -<-—- Initialized seekToO

Looping — true && playback completes prepareAsyncO

seekToQ/pauseO

Stopped

Paused startO (note: from beginning)

seekToQ/pauseO

Stopped

Paused

Looping — false && onCompletionO invoked on OnCompletionListener

Looping — false && onCompletionO invoked on OnCompletionListener startO (note: from beginning)

seekToO PlaybackCompleted~^

Figure 9-4. MediaPlayer state diagram from MediaPlayer class reference documentation

MediaPlayer Example

The following is a full example using the MediaPlayer to create a custom video playback application. Figure 9-5 shows the application running.

package com.apress.proandroidmedia.ch09.videoplayercustom;

import java.io.IOException;

import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.Display; import android.widget.LinearLayout;

We are importing the MediaPlayer and several of its inner classes that are interfaces we'll be implementing.

import android.media.MediaPlayer;

import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnlnfoListener; import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnSeekCompleteListener; import android.media.MediaPlayer.OnVideoSizeChangedListener;

SurfaceHolder and SurfaceView will be used to draw the video.

import android.view.SurfaceHolder; import android.view.SurfaceView;

Our activity will implement all of the MediaPlayer state change listeners as well as the SurfaceHolder.Callback interface, which will enable us to get notified of changes to a SurfaceView.

public class CustomVideoPlayer extends Activity implements OnCompletionListener, OnErrorListener, OnlnfoListener, OnPreparedListener, OnSeekCompleteListener, OnVideoSizeChangedListener, SurfaceHolder.Callback

Display currentDisplay;

SurfaceView surfaceView; SurfaceHolder surfaceHolder;

The workhorse of our application will be this MediaPlayer object. MediaPlayer mediaPlayer;

int videoWidth = 0; int videoHeight = 0;

boolean readyToPlay = false;

public final static String LOGTAG = "CUSTOM_VIDEO_PLAYER"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

After we set the content view, we can get a reference to the SurfaceView defined in the layout XML and get a reference to the SurfaceHolder, which allows us to monitor what happens to the underlying Surface.

surfaceView = (SurfaceView) this.findViewById(R.id.SurfaceView); surfaceHolder = surfaceView.getHolder();

Since our activity implements SurfaceHolder.Callback, we'll assign it to be the callback listener.

surfaceHolder.addCallback(this);

We need to make sure the underlying surface is a push buffer surface, which is currently required for video playback and camera previews.

surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

Now we start constructing the actual MediaPlayer object. We aren't passing in any parameters, getting back a generic MediaPlayer in the "idle" state.

mediaPlayer = new MediaPlayer(); We'll also specify that our activity should be the listener for the various events.

mediaPlayer.setOnCompletionListener(this);

mediaPlayer.setOnErrorListener(this);

mediaPlayer.setOnInfoListener(this);

mediaPlayer.setOnPreparedListener(this);

mediaPlayer.setOnSeekCompleteListener(this);

mediaPlayer.setOnVideoSizeChangedListener(this);

Before we finish the onCreate method, we'll tell the MediaPlayer object what to play. In this example, we are using the same video file that we used in previous examples. You can download it from www.mobvcasting.com/android/video/Test_Movie.m4v or create your own file.

String filePath = Environment.getExternalStorageDirectory().getPath() + "/Test^ _Movie iPhone.m4v";

The setDataSource method on the MediaPlayer can throw multiple exceptions, which we should handle gracefully. In this case, we are just quitting. In your application, you probably want to present the user with an opportunity to select a different file or explain what went wrong.

mediaPlayer.setDataSource(filePath); } catch (IllegalArgumentException e) { Log.v(LOGTAG,e.getMessage()); finish();

} catch (IllegalStateException e) { Log.v(LOGTAG,e.getMessage()); finish(); } catch (IOException e) {

Log.v(LOGTAG,e.getMessage());

currentDisplay = getWindowManager().getDefaultDisplay();

Since our activity implements SurfaceHolder.Callback and is assigned to be the callback listener, the following three methods will get triggered.

surfaceCreated will be called when the underlying Surface in SurfaceView is created.

public void surfaceCreated(SurfaceHolder holder) { Log.v(LOGTAG,"surfaceCreated Called");

When the Surface is created, we can specify that the MediaPlayer use the Surface for playback by calling its setDisplay method, passing in the SurfaceHolder object.

mediaPlayer.setDisplay(holder);

Finally, after we specify the Surface, we can call prepare. The prepare method blocks rather than doing the work in the background. To have it do the work in the background, so as to not tie up the application, we could use prepareAsync instead. Either way, since we have implemented the OnPreparedListener and our activity is set to be the listener, our onPrepared method will be called when it is done.

The prepare method can throw a couple of extensions that we need to take care of. For brevity we'll just log the error and quit. In your application, you'll probably want to intelligently handle these exceptions.

mediaPlayer.prepare(); } catch (IllegalStateException e) { Log.v(LOGTAG,e.getMessage()); finish(); } catch (IOException e) {

surfaceChanged will be called when the width, height, or other parameter of the Surface underlying the SurfaceView changes. In this example, we don't need to do anything in this case.

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){ Log.v(LOGTAG,"surfaceChanged Called");

surfaceDestroyed will be called when the underlying Surface of our SurfaceView is destroyed. In this example, we won't be doing anything when this occurs.

public void surfaceDestroyed(SurfaceHolder holder) { Log.v(LOGTAG,"surfaceDestroyed Called");

Since we implement the MediaPlayer.OnCompletionListener and register ourselves as the listener, our onCompletion method will be called when the MediaPlayer finishes playing a file. We could use this to load another video or perform some other action such as loading another screen. In this example, we'll just quit.

public void onCompletion(MediaPlayer mp) { Log.v(LOGTAG,"onCompletion Called"); finish();

Our activity implements the MediaPlayer.OnErrorListener, and it is registered as the error listener for our MediaPlayer object, so the following onError method will be called when one occurs. Unfortunately, not much error information is available, just two constants as shown here.

public boolean onError(MediaPlayer mp, int whatError, int extra) { Log.v(LOGTAG,"onError Called");

if (whatError == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {

Log.v(LOGTAG,"Media Error, Server Died " + extra); } else if (whatError == MediaPlayer.MEDIA_ERROR_UNKNOWN) { Log.v(LOGTAG,"Media Error, Error Unknown " + extra);

Returning false from the method indicates that the error wasn't handled. If an OnCompletionListener is registered, its onCompletion method will be called. The MediaPlayer object will be put into the "error" state. It can be put back to the "idle" state by calling the reset method.

return false;

The onInfo method, specified in the OnInfoListener, is called when specific information about the playback of the media is available or if warnings need to be issued.

public boolean onInfo(MediaPlayer mp, int whatInfo, int extra) { if (whatInfo == MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING) {

This will be triggered if the audio and video data in the file are not properly interleaved. A properly interleaved media file has audio and video samples arranged in an order that makes playback efficient and smooth.

Log.v(LOGTAG,"Media Info, Media Info Bad Interleaving " + extra); } else if (whatInfo == MediaPlayer.MEDIA_INFO_NOT_SEEKABLE) {

This will be triggered if the media cannot be seeked (meaning it is probably a live stream).

Log.v(LOGTAG,"Media Info, Media Info Not Seekable " + extra); } else if (whatInfo == MediaPlayer.MEDIA_INFO_UNKNOWN) {

This is self-explanatory, in that the information isn't specified or is otherwise unknown.

Log.v(LOGTAG,"Media Info, Media Info Unknown " + extra); } else if (whatInfo == MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING) {

This will be triggered if the device is having trouble playing the video. It is possible that the audio will play but the video is either too complex or the bitrate is too high.

Log.v(LOGTAG,"MediaInfo, Media Info Video Track Lagging " + extra);

} else if (whatInfo == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {

MEDIA_INFO_METADATA_UPDATE is available in Android 2.0 and higher. It is triggered when new metadata is available.

Log.v(LOGTAG,"MediaInfo, Media Info Metadata Update " + extra);

return false;

Following a successful preparation by the MediaPlayer to start playback, the onPrepared method will be called. This is specified as part of the OnPreparedListener interface that we are implementing. Once this method is called, the MediaPlayer has entered the "prepared" state and is ready to play.

public void onPrepared(MediaPlayer mp) { Log.v(LOGTAG,"onPrepared Called");

Before we can play the video, we should set the size of the Surface to match the video or the display size, depending on which is smaller.

First we get the dimensions of the video using the getVideoWidth and getVideoHeight methods available on the MediaPlayer object.

videoWidth = mp.getVideoWidth(); videoHeight = mp.getVideoHeight();

If the width or height of the video is greater than the display, then we'll figure out the ratio we should use.

if (videoWidth > currentDisplay.getWidth() || videoHeight > currentDisplay.getHeight())

float heightRatio = (float)videoHeight/(float)currentDisplay.getHeight(); float widthRatio = (float)videoWidth/(float)currentDisplay.getWidth();

We'll use whichever ratio is bigger and set the videoHeight and videoWidth by dividing the video size by the larger ratio.

if (heightRatio > widthRatio) {

videoHeight = (int)Math.ceil((float)videoHeight/(float)heightRatio); videoWidth = (int)Math.ceil((float)videoWidth/(float)heightRatio); } else {

videoHeight = (int)Math.ceil((float)videoHeight/(float)widthRatio); videoWidth = (int)Math.ceil((float)videoWidth/(float)widthRatio);

We can now set the size of the SurfaceView we are displaying the video in to be either the actual dimensions of the video or the resized dimensions if the video was bigger than the display.

surfaceView.setLayoutParams(

new LinearLayout.LayoutParams(videoWidth,videoHeight));

Finally, we can start the playback of the video by calling the start method on the MediaPlayer object.

onSeekComplete is specified as part of the OnSeekListener that we are implementing, and our activity is the registered listener for our MediaPlayer. It is called when a seek command has completed.

public void onSeekComplete(MediaPlayer mp) { Log.v(LOGTAG,"onSeekComplete Called");

onVideoSizeChanged is specified as part of the OnVideoSizeChangedListener that we are implementing, and our activity is the registered listener for our MediaPlayer. It is called when a size change occurs. It will be called at least once after the data source is specified and the video metadata is read.

public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { Log.v(LOGTAG,"onVideoSizeChanged Called");

Here is the layout XML file, main.xml, for use with the foregoing activity. <?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"

android:id="@+id/MainView" >

<SurfaceView android:id="@+id/SurfaceView" android:layout_height="wrap_content"^ android:layout_width="wrap_content"></SurfaceView> </LinearLayout>

i flUC) 12:34p

Video Player Custom

Figure 9-5. Video playing in CustomVideoPlayer activity

MediaPlayer with MediaController

The MediaController view that we used in our VideoView example can also be used with a MediaPlayer as shown in Figure 9-6. Unfortunately, it takes significantly more work in order to have it work correctly.

First our class needs to implement MediaController.MediaPlayerControl in addition to other classes it already implements.

import android.widget.MediaController; public class CustomVideoPlayer extends Activity implements OnCompletionListener, OnErrorListener, OnlnfoListener, OnPreparedListener, OnSeekCompleteListener, OnVideoSizeChangedListener, SurfaceHolder.Callback, MediaController.MediaPlayerControl

This interface defines a series of functions that the MediaController uses to control the playback, and we need to implement them in our activity.

Here are the functions and their implementation in our CustomVideoPlayer example. For several of the functions, we just return true, meaning the capability is there. For the rest, we call the corresponding function on our MediaPlayer object.

public boolean canPause() { return true;

e public boolean canSeekBackward() { return true;

public boolean canSeekForward() { return true;

public int getBufferPercentage() { return 0;

public int getCurrentPosition() {

return mediaPlayer.getCurrentPosition();

public int getDuration() {

return mediaPlayer.getDuration();

public boolean isPlaying() {

return mediaPlayer.isPlaying();

public void pause() {

if (mediaPlayer.isPlaying()) { mediaPlayer.pause();

public void seekTo(int pos) { mediaPlayer.seekTo(pos);

public void start() { mediaPlayer.start();

Now we are free to add the actual MediaController object. We'll declare it with the rest of the instance variables.

MediaController controller; In the onCreate method, we'll instantiate it.

controller = new MediaController(this);

We won't actually set it up and use it until after the MediaPlayer is prepared. At the end of the onPrepared method, we can add the following. First we specify the object that implements MediaController.MediaPlayerControl by calling the setMediaPlayer method. In this case, it is our activity, so we pass in this.

Then we set the root view of our activity so the MediaController can determine how to display itself. In the foregoing layout XML, we gave the root LinearLayout object an ID of MainView so we can reference it here.

Finally we enable it and show it.

controller.setMediaPlayer(this);

controller.setAnchorView(this.findViewById(R.id.MainView));

controller.setEnabled(true);

controller.show();

In order to bring the controller back up after it disappears (the default behavior of the MediaController is to auto-hide after a timeout), we can override onTouchEvent in our activity to show or hide it.

@Override public boolean onTouchEvent(MotionEvent ev) { if (controller.isShowing()) {

controller.show();

return false;

return false;

«

»

00:02 ^flR —

— oo:li

Figure 9-6. CustomVideoPlayer activity with MediaController

Figure 9-6. CustomVideoPlayer activity with MediaController

Was this article helpful?

+1 0

Responses

  • daniela weber
    Can i catch the listener in mediaplayer in android?
    8 years ago
  • Sara
    How to display an media player with an layout in android by specifying height and width?
    8 years ago
  • faith
    How to mediaplayer declair in surfaceview class?
    3 years ago

Post a comment