Fullscreen functionality with Android ExoPlayer

YouTube makes use of the ExoPlayer library

ExoPlayer is a great Android library that deals with a lot of the pains you might encounter when trying to stream video. However, unless the video you’re playing is being displayed in a fullscreen Activity by default this can cause some issues when it comes to smooth playback.

You may find that the audio will keep playing while the video hangs waiting to resync with the audio, or that when you go back to the calling activity the video has now gone blank but still responds to input.

As much as I tried to find a good solution on StackOverflow and other sources on the internet, I just couldn’t. After approaching this problem from various angles. Here is a nice, reliable solution that I came up with:

ExoPlayerVideoHandler:

public class ExoPlayerVideoHandler
{
private static ExoPlayerVideoHandler instance;

public static ExoPlayerVideoHandler getInstance(){
if(instance == null){
instance = new ExoPlayerVideoHandler();
}
return instance;
}
    private SimpleExoPlayer player;
private Uri playerUri;
private boolean isPlayerPlaying;

private ExoPlayerVideoHandler(){}

public void prepareExoPlayerForUri(Context context, Uri uri,
SimpleExoPlayerView exoPlayerView){
if(context != null && uri != null && exoPlayerView != null){
if(!uri.equals(playerUri) || player == null){
// Create a new player if the player is null or
// we want to play a new video
playerUri = uri;
                // Do all the standard ExoPlayer code here...
                // Prepare the player with the source.
player.prepare(source);
}
player.clearVideoSurface();
player.setVideoSurfaceView(
(SurfaceView)exoPlayerView.getVideoSurfaceView());
player.seekTo(player.getCurrentPosition() + 1);
exoPlayerView.setPlayer(player);
}
}

public void releaseVideoPlayer(){
if(player != null)
{
player.release();
}
player = null;
}

public void goToBackground(){
if(player != null){
isPlayerPlaying = player.getPlayWhenReady();
player.setPlayWhenReady(false);
}
}

public void goToForeground(){
if(player != null){
player.setPlayWhenReady(isPlayerPlaying);
}
}
}

This class uses the Singleton Design Pattern to ensure there always only one instance in the app as we’re only interested in the playback of one video. If you want to do multiple videos at the same time look at keeping references to each instance of this class instead.

After we have got the refernce to the current instance of the ExoPlayerVideoHandler we then prepare the SimpleExoPlayerView of the parent activity to play the video from the URI.

If the player variable is null or the URI does not match the previously stored URI than we want to play a new video and re-run the code required to setup ExoPlayer. Otherwise, we want to reset the video surface (this prevents the screen from being blank when going back to an activity) and reset the position of the player to it’s current position. (The +1 also forces a re-draw by buffering the video).

goToBackground and goToForeground are 2 helper methods that ensure that the video state is kept when switching between activities (namely the parent Activity and the video’s fullscreen Activity) and will continue playing or stay paused.

Used in practice the code looks something like this:

String videoUrl = "http://techslides.com/demos/sample-videos/small.mp4"
@Override
public void onResume()
{
super.onResume();
exoPlayerView = (SimpleExoPlayerView)rootView.findViewById(
R.id.exoplayer_video);
    if(videoUrl != null && exoPlayerView != null){
ExoPlayerVideoHandler.getInstance()
.prepareExoPlayerForUri(rootView.getContext(),
Uri.parse(videoUrl), exoPlayerView);
ExoPlayerVideoHandler.getInstance().goToForeground();
        rootView.findViewById(R.id.exo_fullscreen_button)
.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intent = new Intent(context,
FullscreenVideoActivity.class);
context.startActivity(intent);
}
});
}
}
@Override
public void onPause(){
super.onPause();
ExoPlayerVideoHandler.getInstance().goToBackground();
}

@Override
public void onDestroyView(){
super.onDestroyView();
ExoPlayerVideoHandler.getInstance().releaseVideoPlayer();
}

The view is found and set to the ExoPlayer instance created inside the ExoPlayerVideoHandler. When the activity is paused the player goes to the background and when the activity is destroyed the player is released.

An OnClickListener is also added to the SimpleExoPlayerView’s added fullscreen button. This fires off an intent to the FullscreenVideoActivity. The code of which looks like this:

private boolean destroyVideo = true;
@Override
protected void onResume(){
super.onResume();
SimpleExoPlayerView exoPlayerView =
(SimpleExoPlayerView)findViewById(R.id.exoplayer_video);
    ExoPlayerVideoHandler.getInstance()
.prepareExoPlayerForUri(getApplicationContext(),
Uri.parse(videoUrl), exoPlayerView);
ExoPlayerVideoHandler.getInstance().goToForeground();

findViewById(R.id.exo_fullscreen_button).setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View v){
destroyVideo = false;
finish();
}
});
}

@Override
public void onBackPressed(){
destroyVideo = false;
super.onBackPressed();
}

@Override
protected void onPause(){
super.onPause();
ExoPlayerVideHandler.getInstance().goToBackground();
}

@Override
protected void onDestroy(){
super.onDestroy();
if(destroyVideo){
ExoPlayerVideoHandler.getInstance().releaseVideoPlayer();
}
}

This Activity gets the existing instance of the player from the ExoPlayerVideoHandler and brings it back to the foreground which will continue playing the video from where the last activity was.

The destroyVideo flag determines if the entire app is being forcibly killed by the OS or if the user simply wants to stop viewing the video in fullscreen. By default the flag is true and will only be set to false if the user makes a deliberate action. Otherwise, the player will be released.

Hopefully this will help anyone out that’s also having the same issues when trying to show the same ExoPlayer video stream in different activities. Good luck!