Written by Chris Leversuch
Aug 05, 2013

Custom Android media controller

This post will explain the process that we went through and a sample project to help you build your own media controls.

Building a custom media play from scratch

For a recent Android project we needed to provide a custom media player based on the design provided for the app. We also needed to add a mute button to the media controls. This post will explain the process that we went through and a sample project to help you build your own media controls.

Here's how we did it

Instead of writing your own media controller from scratch, you could start with the MediaController class that is included in Android, however you’ll need to make a lot of tweaks to the code (such as removing references to com.android.internal.R).

A better starting point is provided in this StackOverflow post, which I’ll use for this post.

1 - Download VideoControllerView.java into your project then make the following changes:

  • Change the package name to match your project
  • Either change the last import line to match your project or delete the line if it’s not necessary
  • If your project’s minSdkVersion is less than 14, delete the onInitializeAccessibilityEvent and onInitializeAccessibilityNodeInfo methods on lines 561-571.

2 - Download media_controller.xml into your project’s layout folder. This file references a string called description so either create a string with this name (see 2a) or delete the android:contentDescription references in the layout.

2a - To create a description string, open strings.xml and add a line like <string name="description">Media Controls</string>

3 - Download the 4 images into your project’s drawable-xhdpi folder and name them as ic_media_play.png, ic_media_pause.png, ic_media_fullscreen_shrink.png and ic_media_fullscreen_stretch.png

With these files in place, we can now create a layout for our media player. In the layout folder, create a new XML file called activity_video_player.xml and copy the following code into it -

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/video_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    android:gravity="center_horizontal|center_vertical"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/videoSurfaceContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <SurfaceView
            android:id="@+id/videoSurface"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </FrameLayout>

</LinearLayout>

Next, create a new Activity class called VideoPlayerActivity. We’ll start with some basic code and build up the functionality:


public class VideoPlayerActivity extends Activity implements SurfaceHolder.Callback, MediaPlayer.OnPreparedListener, VideoControllerView.MediaPlayerControl {

    SurfaceView videoSurface;
    MediaPlayer player;
    VideoControllerView controller;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_player);
    }
}

Remember to add VideoPlayerActivity to your manifest file. The first thing to do inside onCreate is initialise some variables so add the following code after setContentView() -

videoSurface = (SurfaceView) findViewById(R.id.videoSurface);
SurfaceHolder videoHolder = videoSurface.getHolder();
videoHolder.addCallback(this);

player = new MediaPlayer();
controller = new VideoMediaController(this);

Here we’re finding the SurfaceView from our layout and linking its lifecycle to the activity (the addCallback(this) call is related to the implements SurfaceHolder.Callback in our class definition. We’ll implement the relevant methods later).

We can now tell our player object the location of the video to play and also link the player’s lifecycle to the activity (setOnPreparedListener(this) relates to the MediaPlayer.OnPreparedListener reference in the class definition. Again, we’ll implement the relevant methods later).

try {
    player.setAudioStreamType(AudioManager.STREAM_MUSIC);
    player.setDataSource(this, Uri.parse("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"));
    player.setOnPreparedListener(this);
} catch (IllegalArgumentException e) {
    e.printStackTrace();
} catch (SecurityException e) {
    e.printStackTrace();
} catch (IllegalStateException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

You’ll notice that I’ve included an example video URL. Playing this will require internet access so add the following line to your manifest file -

  <uses-permission android:name="android.permission.INTERNET" />

We want the controller to appear when we touch the screen so we’ll implement the activity’s onTouchEvent() method -

@Override
public boolean onTouchEvent(MotionEvent event) {
    controller.show();
    return false;
}

We’ll now implement the SurfaceHolder.Callback interface. Add the following code to your activity -

// Implement SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    player.setDisplay(holder);
    player.prepareAsync();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
// End SurfaceHolder.Callback

All we’re doing is linking the player to the surface once the surface is ready and then telling the player to prepare itself asynchronously. It’s this asynchronous behaviour that causes us to need the MediaPlayer.OnPreparedListener interface which we’ll implement next -

// Implement MediaPlayer.OnPreparedListener
@Override
public void onPrepared(MediaPlayer mp) {
    controller.setMediaPlayer(this);
    controller.setAnchorView((FrameLayout) findViewById(R.id.videoSurfaceContainer));
    player.start();
}
// End MediaPlayer.OnPreparedListener

This code links the media controller with the media player and places the controller in the same container as the player. Finally, we’ll implement the VideoControllerView.MediaPlayerControl interface -

// Implement VideoMediaController.MediaPlayerControl
@Override
public boolean canPause() {
    return true;
}

@Override
public boolean canSeekBackward() {
    return true;
}

@Override
public boolean canSeekForward() {
    return true;
}

@Override
public int getBufferPercentage() {
    return 0;
}

@Override
public int getCurrentPosition() {
    return player.getCurrentPosition();
}

@Override
public int getDuration() {
    return player.getDuration();
}

@Override
public boolean isPlaying() {
    return player.isPlaying();
}

@Override
public void pause() {
    player.pause();
}

@Override
public void seekTo(int i) {
    player.seekTo(i);
}

@Override
public void start() {
    player.start();
}

@Override
public boolean isFullScreen() {
    return false;
}

@Override
public void toggleFullScreen() {

}
// End VideoMediaController.MediaPlayerControl

These just link the controller buttons with the player so that, for example, clicking play calls the start() method on the player. The video player activity is now ready to be used. You just need to add a way of launching the activity to your app e.g. add an onClickListenter on a button which calls -

startActivity(new Intent(this, VideoPlayerActivity.class));

Download the sample project

Top