Custom ad playback with the IMA SDK for Android

The quickest and most straightforward way to integrate the IMA SDK for Android into your app is to have the SDK handle all of the ad-playing logic, while your app focuses on playing content video. This approach, called "SDK-owned ad playback", is the default option in Get Started.

However, if you want to also play ads in your video player, the SDK provides an interface for that. We refer to this approach as "custom ad playback" and the rest of this guide goes over its implementation.

Prerequisites

  • A basic IMA integration.

We recommend you look at the Advanced Example on github as a starting point if you do not currently have a basic IMA integration. This example already implements custom ad playback. The remainder of this guide will describe the features necessary for custom ad playback with IMA ads.

Interface with VideoAdPlayer

Custom ad playback requires your app to implement the VideoAdPlayer interface. This interface is used by the SDK to notify your app to play ad videos. Your app also uses this interface to inform the SDK of major video ad events. Follow these steps to implement the interface.

Create a VideoAdPlayer

The first step is to create an anonymous VideoAdPlayer class in requestAds():

private VideoAdPlayer videoAdPlayer;
...

private void requestAds(String adTagUrl) {
    videoAdPlayer = new VideoAdPlayer() {
    };
}

Add video methods

Next, add methods that tell your video player to play, load, stop, and pause ad videos. We also add the methods to release the player and get the volume here:

videoAdPlayer = new VideoAdPlayer() {
        @Override
        public void playAd() {
            if (mIsAdDisplayed) {
                videoPlayer.resume();
            } else {
                isAdDisplayed = true;
                videoPlayer.play();
            }
        }

        @Override
        public void loadAd(String url) {
            isAdDisplayed = true;
            videoPlayer.setVideoPath(url);
        }
        @Override
        public void stopAd() {
            videoPlayer.stopPlayback();
        }
        @Override
        public void pauseAd() {
            videoPlayer.pause();
        }

        @Override
        public void release() {
            // any clean up that needs to be done
        }

        @Override
        public int getVolume() {
            return videoPlayer.getVolume();
        }
};

These methods are thin wrappers around your video player's own similar methods. Note that these methods set an internal variable that's used to keep track of whether an ad is displayed. In custom ad playback, the video player plays both content video and video ads, so you need to keep track of which is currently displayed.

Ad playback progress

The VideoAdPlayer interface implements another interface, AdProgressProvider, so you must implement it as well. It only has one method, getAdProgress(), which is used by the SDK to get playback information for ads. Add it to your anonymous VideoAdPlayer class, below the other methods:

VideoAdPlayer videoAdPlayer = new VideoAdPlayer() {
        ...
        @Override
        public VideoProgressUpdate getAdProgress() {
            if (!isAdDisplayed || videoPlayer.getDuration() <= 0) {
                return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
            }
            return new VideoProgressUpdate(videoPlayer.getCurrentPosition(),
                    videoPlayer.getDuration());
        }
};

getAdProgress() returns a VideoProgressUpdate type, which must consist of the video's current position and duration. If the player is not playing an ad, or the duration is unavailable, have it return VideoProgressUpdate.VIDEO_TIME_NOT_READY as shown in the example.

Manage video callbacks

Custom ad playback requires your app to inform the SDK of major video events. From the SDK's view, these are callbacks that are described by the VideoAdPlayer.VideoAdPlayerCallback interface. Before getting into the callback methods themselves, you need to be able to add and remove the callbacks at the SDK's request. This is done inside VideoAdPlayer using addCallback() and removeCallback():

private List<VideoAdPlayerCallback> adCallbacks = new ArrayList<>(1);

VideoAdPlayer videoAdPlayer = new VideoAdPlayer() {
        ...
        @Override
        public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
            adCallbacks.add(videoAdPlayerCallback);
        }

        @Override
        public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
            adCallbacks.remove(videoAdPlayerCallback);
        }
};

This implementation uses a List<> for the callbacks on which to call the List<>.add() and remove() methods.

Call the callbacks

Now that the SDK has a way to tell your app to add and remove callbacks, define the places where the callback is called. Your app needs to call these callbacks when major video events happen, such as playing, pausing or resuming a video, or when a video finishes or experiences an error.

To do this, expand the SampleVideoPlayer to have a listener for these video events that are added from VideoFragment. The reason to make a separate listener in SampleVideoPlayer to call these ad callbacks is because SampleVideoPlayer doesn't know anything about ads, so you must forward its video events to something that can deal with ads.

public interface OnVideoEventsListener {
    void onPlay();
    void onResume();
    void onPause();
    void onError();
}

private final List<OnVideoEventsListener> onVideoEventsListeners = new ArrayList<>(1);

public void addVideoEventsListener(OnVideoEventsListener listener) {
    onVideoEventsListeners.add(listener);
}

Start, pause, and resume

Create a new enum to keep track of playback state and add new overrides for the start() and pause() methods in SampleVideoPlayer:

private enum PlaybackState {
    STOPPED, PAUSED, PLAYING
}

private PlaybackState playbackState = PlaybackState.STOPPED;

@Override
public void start() {
    super.start();
    switch (playbackState) {
        case STOPPED:
            for (OnVideoEventsListener listener : onVideoEventsListeners) {
                listener.onPlay();
            }
            break;
        case PAUSED:
            for (OnVideoEventsListener listener : onVideoEventsListeners) {
                listener.onResume();
            }
            break;
        default:
            // Already playing; do nothing.
            break;
    }
    playbackState = PlaybackState.PLAYING;
}

@Override
public void pause() {
    super.pause();
    playbackState = PlaybackState.PAUSED;
    for (OnVideoEventsListener listener : onVideoEventsListeners) {
        listener.onPause();
    }
}

Handle errors

Override the video player's anonymous error listener you set in init():

@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
    playbackState = PlaybackState.STOPPED;
    for (OnVideoEventsListener listener : onVideoEventsListeners) {
        listener.onError();
    }

    // Returning true signals to MediaPlayer that the error was handled.
    // This  prevents the completion handler from being called.
    return true;
}

Implement the listener

Go back to VideoFragment and add an anonymous OnVideoEventsListener to your SampleVideoPlayer instance:

mVideoPlayer.addVideoEventsListener(new OnVideoEventsListener() {
    @Override
    public void onPlay() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onPlay();
            }
        }
    }

    @Override
    public void onResume() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onResume();
            }
        }
    }

    @Override
    public void onPause() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onPause();
            }
        }
    }

    @Override
    public void onError() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onError();
            }
        }
    }
});

Change the onVideoCompleted() method of the OnVideoCompletedListener to handle the case that an ad video finished:

public void onVideoCompleted() {
    // Handle completed event for playing post-rolls.
    if (isAdDisplayed) {
        for (VideoAdPlayerCallback callback : adCallbacks) {
            callback.onEnded();
        }
    } else {
        if (adsLoader != null) {
            adsLoader.contentComplete();
    }
}

Switch between content and ads

This example uses the same instance of the video player to play both content and ads, so you need to add some logic to switch between content and ads in your player. You can then reload and seek the content video to return to the point where the ad started. Add two functions to do this:

private int savedContentPosition = 0;

private void pauseContent() {
    savedContentPosition = videoPlayer.getCurrentPosition();
    videoPlayer.stopPlayback();
    isAdDisplayed = true;
}

private void resumeContent() {
    videoPlayer.setVideoPath(getString(R.string.content_url));
    videoPlayer.seekTo(mSavedContentPosition);
    videoPlayer.play();
    isAdDisplayed = false;
}

These are called when the CONTENT_PAUSE_REQUESTED and CONTENT_RESUME_REQUESTED events are received, in VideoFragment.onAdEvent(), respectively:

case CONTENT_PAUSE_REQUESTED:
    pauseContent();
    break;
case CONTENT_RESUME_REQUESTED:
    resumeContent();
    break;

Enable custom ad playback

The last step is to tell the SDK that you're using custom ad playback. This is done by passing a VideoAdPlayer to your AdDisplayContainer:

adDisplayContainer.setPlayer(videoAdPlayer);

It's necessary to pass your player to setPlayer(). Otherwise, the SDK uses SDK-owned playback.

That's it. Those are all the steps needed to add custom ad playback to your IMA implementation. You can compare your own implementation with the Advanced Example on github if you run into issue.