Implement pause ads for Android TV

On Android TV, the pause ad format lets you show an ad during the time the user pauses the video stream.

To prevent ad latency, Interactive Media Ads (IMA) SDK requests the pause ad after the user pauses the content video. After receiving the ad response, IMA SDK displays the pause ad after a one-second delay.

This guide covers integrating pause ads into your Video on Demand (VOD) streams using Google Dynamic Ad Insertion (DAI) SDK. You can only request pause ads for VOD streams.

Prerequisites

Before you continue, you must have IMA SDK for DAI version 3.38.0 or later. For details, see Set up IMA SDK for DAI.

Step 1: Prepare the UI layout

To display ads over your paused video, provide a ViewGroup object as a pause ad container. Do the following:

  1. Keep the pause ads container object in the view hierarchy for the duration of the stream. This process lets IMA SDK populate the container with ad content.
  2. Toggle the pause ads container's visibility accordingly to your video player's state.

  3. Follow these sizing guidelines:

    • Account for TV safe areas by setting the video player view to fill the screen. Leave a 5% to 10% padding on the edges of the TV safe area.
    • Set the ad display container and the pause ad container to the same size as the video player view.
  4. Arrange your UI elements in the following order:

    • Video layer: plays the content stream.
    • Stream display container: renders the ad break UI elements during the content stream playback.
    • Pause ad container: displays the ads when the content stream is paused. When the user closes the ad or resumes content playback, hide this layer.
  5. For IMA SDK for DAI to measure ad view, make sure that video controls or UI elements don't obstruct the ad display and pause ad containers.

The following example sets the video layer, stream display container, and pause ad container:

<!-- Example layout configuration -->
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Video layer: plays the content stream. -->
    <androidx.media3.ui.PlayerView
        android:id="@+id/playerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Stream display container: renders the ad break UI elements during the
         content stream playback. -->
    <FrameLayout
        android:id="@+id/streamDisplayContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Pause ad container: displays the ads when the content stream is paused.
         Initialize this container to invisible, not gone. This process lets
         Android measure the dimensions during layout, while keeping the layout
        hidden during video playback. -->
    <FrameLayout
        android:id="@+id/pauseAdContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />
    
</RelativeLayout>

Step 2: Create and set the ad slot size

Before the user starts playing the video stream, create an AdSlot object and set the size to the density-independent pixel (dp) size of the pause ad container. IMA SDK uses the ad slot size to request ads every time the user pauses the current video stream. If you provide an incorrect size, the pause ad might fail to render.

The following example creates an AdSlot object and sets the object size in dp relative to the pause ad container:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);

    ViewGroup pauseAdContainer = findViewById(R.id.pauseAdContainer);

    // Instantiate a persistent AdSlot for pause ads. This reusable instance is
    // sufficient for the duration of the Activity's lifecycle.
    AdSlot pauseAdSlot = ImaSdkFactory.getInstance().createAdSlot(pauseAdContainer);
    setAdSlotSizeInDp(pauseAdSlot);
}

/**
 * Configures an AdSlot's dimensions using its container view's measurements
 * converted from raw pixels to DPs.
 */
private void setAdSlotSizeInDp(AdSlot adSlot) {
    View container = adSlot.getContainer();
    if (container != null) {
        // Get the device's screen density (e.g., 2.0 for a 1080p screen)
        float density = getResources().getDisplayMetrics().density;
        // Convert the view's raw pixels into DPs by dividing by density
        int width = (int) (container.getWidth() / density);
        int height = (int) (container.getHeight() / density);
        adSlot.setSize(width, height);
    }
}

If your app resizes the video player during playback, such as an app config change in the manifest, TV rotation, or Picture-in-Picture mode, you must call the pauseAdSlot.setSize method again with the updated dimensions. This process makes sure IMA SDK requests the correct ad size.


Step 3: Register the pause ad slot

When creating your stream display container, register the pause ad slot you created. Before registering the AdSlot object, make sure that you've configured the width and height greater than zero. This process informs IMA SDK where to render the pause ad when a pause occurs.

The following example registers the pause ad slot:

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    // 1. Configure the ad slot dimensions before setting it on the display
    // container.
    setAdSlotSizeInDp(pauseAdSlot);

    // ...

    // Assign the stream display container view and assume videoStreamPlayer is
    // created
    ViewGroup streamDisplayContainer = findViewById(R.id.streamDisplayContainer);
    StreamDisplayContainer displayContainer =
        ImaSdkFactory.createStreamDisplayContainer(
            streamDisplayContainer, videoStreamPlayer);

    // 2. Register the pause ad slot.
    // Before you call setPauseAdSlot, make sure the value from
    // pauseAdSlot.getWidth() and pauseAdSlot.getHeight() are greater than zero.
    displayContainer.setPauseAdSlot(pauseAdSlot);

    // Continue to initialize your AdsLoader.
    AdsLoader adsLoader =
        sdkFactory.createAdsLoader(context, imaSdkSettings, displayContainer);
}

Step 4: Sync playback state

To pause the ad and resume content, invoke the onResume callback method on the VideoStreamPlayer.VideoStreamPlayerCallback interface. Make sure actions, such as pressing the play or back buttons, invoke your player to resume playback. This process invokes the callback to dismiss the ad.

If you are using Media3 ExoPlayer, you can override the playback behavior using a ForwardingPlayer class.

The following example signals to IMA SDK that content has paused and resumed:

playerView.setPlayer(new ForwardingPlayer(player) {
    @Override
    public void pause() {
        super.pause();
        // Signal IMA that content has paused
        if (videoStreamPlayerCallback != null) {
            videoStreamPlayerCallback.onPause();
        }
    }

    @Override
    public void play() {
        super.play();
        // Signal IMA that content has resumed
        if (videoStreamPlayerCallback != null) {
            videoStreamPlayerCallback.onResume();
        }
    }
});

IMA SDK doesn't automatically detect when the user pauses or resumes the content. Your app is responsible for handling these video player events and forwarding the events to IMA SDK.

You must ensure that your implementation of the VideoStreamPlayer interface notifies IMA SDK by invoking the onPause and onResume callbacks. For details, see Integrate the IMA DAI SDK.


Step 5: (Optional) Handle ad events

When a pause ad loads is ready to display, IMA SDK emits a PAUSE_AD_READY event. You can listen for this event using the AdEventListener interface. This process handles specific UI treatments, such as hiding your video player controls and managing Directional Pad (D-pad) focus on Android TV.

The following example checks if the pause ad slot is ready and filled:

@Override
public void onAdEvent(AdEvent event) {
    switch (event.getType()) {
        case PAUSE_AD_READY:
            // The ad slot is ready; perform UI preparations here
            if (pauseAdSlot.getContainer() != null) {
                // Example: For Android TV, move D-pad focus to the ad
                pauseAdSlot.getContainer().requestFocus();
            }
            break;

        case STARTED:
            // Check if the pause ad slot was the one that got filled
            if (pauseAdSlot.isFilled()) {
                Log.i("IMA", "The Pause Ad Slot is currently filled and displaying an ad.");
            }
            break;

        default:
            break;
    }
}

Step 6: Clean up ad resources

To clean up ad resources, do the following:

  1. When a user stops watching a stream or an error occurs, destroy the stream manager. To stop stream monitoring, call the streamManager.destroy method.
  2. When a user starts watching a new stream, create an AdSlot object using the same pause ad container across streaming sessions.
  3. When a user transitions away from the video playing activity, call the streamManager.destroy and adsLoader.release methods.

The following example cleans up resources during stream transitions and activity destruction:

/**
 * Cleans up the previous stream manager and creates a fresh ad slot when
 * switching to a new VOD stream or upon stream completion.
 */
public void onSwitchStream() {
    if (streamManager != null) {
        // Destroying the stream manager cleans up stream-specific resources,
        // including the pause ad slot associated with this stream.
        streamManager.destroy();
        streamManager = null;
    }

    // 1. Reuse the existing pause ad container from your view hierarchy
    ViewGroup pauseAdContainer = findViewById(R.id.pauseAdContainer);

    // 2. Instantiate a fresh AdSlot object for the upcoming VOD stream
    pauseAdSlot = ImaSdkFactory.getInstance().createAdSlot(pauseAdContainer);
    setAdSlotSizeInDp(pauseAdSlot);

    // 3. Re-create or re-configure your StreamDisplayContainer with the new slot
    displayContainer = ImaSdkFactory.createStreamDisplayContainer(streamDisplayContainer, videoStreamPlayer);
    displayContainer.setPauseAdSlot(pauseAdSlot);

    // Continue requesting ads for the new stream using your persistent AdsLoader
}

@Override
public void onDestroy() {
    // Release ad resources to prevent memory leaks during activity destruction
    if (streamManager != null) {
        streamManager.destroy();
        streamManager = null;
    }
    if (adsLoader != null) {
        adsLoader.release();
        adsLoader = null;
    }
    super.onDestroy();
}

Verify user interactions with pause ads

To verify that your pause ads setup is successful, check for the following behavior in your app:

  • Ad display: When you pause the video stream, the pause ad container displays an ad over the paused content in the TV safe area. For details, see Overscan.
  • Ad dismissal: When you press standard remote control buttons such as Play or Back, your app resumes playback and signals IMA SDK to dismiss the ad.