Exposed Pause Ad

The Pause Ad Integration enables high-impact, non-intrusive advertising within the StreamLayer TV SDK. When a user pauses a video, the SDK handles the asynchronous loading and rendering of a VAST-compliant ad overlay. It features a robust state management system to help developers synchronize their native video player UI with the ad’s lifecycle, ensuring that interactive controls (like play/resume) and animations work seamlessly with TV remote navigation.

Alpha Version: This feature is currently in alpha. Expect potential instability, including occasional freezes.

Prerequisites

1. PAL SDK (Optional)

Add the Google PAL SDK to your HTML <head> if you use video ad content and Google Ads:

<script type="text/javascript" src="https://imasdk.googleapis.com/pal/sdkloader/pal.js"></script>

2. Spatial Navigation

Note on Focus Behavior: The SDK internally utilizes the @noriginmedia/norigin-spatial-navigation library to handle remote control interactions. Because this library operates as a singleton, please note:

Initialization: If your app already uses Norigin, we recommend setting shouldFocusDOMNode: true. If not, the SDK initializes it automatically with the required parameters.

Focus Capture: When the pause ad is rendered (pauseAdRendered: true), the SDK automatically captures focus to ensure the overlay is interactive via remote control.

Focus Return: Upon closing the ad or resuming video, the SDK does not automatically return focus to a specific element in your host application. You should use the onRenderPauseAd (when rendered becomes false) or onClosePauseAd callbacks to manually re-focus your desired UI component (e.g., your play button or video container).


Integration

Props

PropTypeDescription
showPauseAdbooleanControls pause ad visibility. Set to true when video pauses.
onRenderPauseAd(params: { rendered: boolean }) => voidCalled when pause ad overlay renders. Use to hide your UI elements.
onClosePauseAd() => voidCalled when pause ad closes without resuming video (user dismissed overlay).
videoPlayerControllerVideoPlayerCallbackCalled when user resumes video via the pause ad overlay button.
pauseAdVastUrlArray<{ template: string; url: string }>VAST tag configuration.
optionsStreamLayerSDKTvOptionsOptional configuration.

Options

type StreamLayerSDKTvOptions = {
  showPauseButton?: boolean // toggle render our pause ad button or use your button, default is true
  pauseAdDelay?: number // delay in ms before showing pause ad after video pause, default is 0 (no delay)
  pauseAdRefetchInterval?: number // background refetch interval in ms, used only when pauseAdDelay is 0
}

pauseAdVastUrl Configuration

pauseAdVastUrl={[
  {
    template: 'default', // Currently only 'default' template is supported
    url: 'https://your-vast-tag-url.com/vast.xml',
  },
]}

Note: Only the first item in the array is used. Multi-item rotation is not yet supported.


Ad Loading Strategies

The SDK supports two ad loading strategies depending on the pauseAdDelay and pauseAdRefetchInterval options. This gives you control over whether ads are loaded on-demand when the user pauses, or prefetched in the background for instant display.

On-Demand Loading

When pauseAdDelay is set to a value greater than 0, the SDK does not prefetch or refresh ads in the background. Instead, the ad is loaded during the delay between the video pause and the pause ad display.

How it works:

  1. User pauses the video, showPauseAd is set to true.
  2. The SDK starts the pauseAdDelay timer and simultaneously begins loading the VAST ad.
  3. If the ad finishes loading before the timer expires, the ad is displayed when the timer completes.
  4. If the ad has not finished loading when the timer expires, the SDK continues waiting without rendering — the ad is displayed as soon as loading completes.
  5. If no ad is available (empty VAST response or load failure), nothing is rendered.
Video pauses
    │
    ▼
pauseAdDelay timer starts + ad load begins
    │
    ├── Ad loads before timer expires → show ad when timer completes
    │
    └── Timer expires before ad loads → continue waiting → show ad when load completes
                                                         (or skip if no ad available)
options={{
  pauseAdDelay: 5000, // load ad during this 5s delay
}}

Prefetch with Background Refresh

When pauseAdDelay is set to 0 and pauseAdRefetchInterval is defined, the SDK prefetches the ad immediately on initialization and keeps it fresh in the background.

How it works:

  1. On SDK initialization, the VAST ad is loaded and cached immediately.
  2. The SDK refreshes the cached ad in the background at the specified pauseAdRefetchInterval.
  3. When the user pauses the video, the cached ad is displayed instantly (no delay).
  4. After the ad is displayed, the cache is invalidated and a new ad is loaded right away — this guarantees the user sees a new advertisement every time.
  5. The background refetch interval restarts after the new ad is loaded.

Because the cache is always invalidated after each display, the pauseAdRefetchInterval only serves as a background keep-alive to ensure the cached ad stays fresh between pauses. You do not need a short interval to guarantee fresh ads — that is already handled by the post-display invalidation.

Recommended: Set pauseAdRefetchInterval to a high value (up to 1 hour). Google restricts VAST ad caching to a maximum of 1 hour, so 3600000 (1 hour) is the upper limit. A longer interval reduces unnecessary network requests and lowers costs while still keeping the cache ready for instant display.

SDK initializes
    │
    ▼
Load ad → cache ready
    │
    ├── refetch interval ──→ refresh cache ──→ refetch interval ──→ ...
    │
    ▼
Video pauses → show cached ad instantly
    │
    ▼
Invalidate cache → load new ad immediately (guarantees fresh ad next time)
    │
    ▼
Restart refetch interval
options={{
  pauseAdDelay: 0,                // no delay — use prefetched ad
  pauseAdRefetchInterval: 3300000, // refresh cached ad every 55 minutes (Google max is 1 hour)
}}

Note: Even if both pauseAdDelay and pauseAdRefetchInterval are set to 0, there will still be a minimal delay before the pause ad appears. This delay is the time the SDK needs to load and prepare the ad content for rendering.

Choosing a Strategy

StrategypauseAdDelaypauseAdRefetchIntervalBest For
On-Demand> 0Not setLower bandwidth usage; acceptable to wait before showing ad
Prefetch0 (default)Set (e.g. 3300000)Instant ad display; always-ready ad experience

Understanding showPauseAd vs pauseAdRendered

These two flags serve different purposes and are controlled by different parties:

FlagControlled ByPurpose
showPauseAdYou (integrator)Request to show/hide pause ad
pauseAdRenderedSDK (via callback)Actual visibility state on screen

Why two flags?

showPauseAd is your intent — "I want the pause ad to appear/disappear."

pauseAdRendered is the actual state — "The pause ad is currently visible on screen."

These values don't always match due to animation delays:

  • Opening delay: SDK waits for the configured pauseAdDelay before showing the overlay
  • Closing delay (400ms): SDK animates the overlay before removing it

State combinations

showPauseAdpauseAdRenderedState
falsefalseVideo playing or paused without ad
truefalseVideo paused, SDK preparing overlay
truetruePause ad visible on screen
falsetrueClosing animation in progress (400ms)

Timeline

Video pauses
    │
    ▼
showPauseAd = true              ← You set this on video pause
pauseAdRendered = false
    │
    │  ← pauseAdDelay (SDK loads/retrieves ad, prepares overlay)
    ▼
showPauseAd = true
pauseAdRendered = true          ← SDK calls onRenderPauseAd({ rendered: true })
                                  Hide your UI controls now
    │
    │  ← User resumes video or closes overlay
    ▼
showPauseAd = false             ← You set this on play/close
pauseAdRendered = true          ← Still true! Closing animation playing
    │
    │  ← 400ms closing animation
    ▼
showPauseAd = false
pauseAdRendered = false         ← SDK calls onRenderPauseAd({ rendered: false })
                                  Safe to show your UI controls

Why this matters

Always use pauseAdRendered to control your UI visibility:

// Correct: hide UI based on actual render state
const showControls = !pauseAdRendered

// This ensures:
// 1. Your controls don't disappear before the ad appears
// 2. Your controls don't reappear during the 400ms closing animation

If you used showPauseAd instead, your controls would:

  • Disappear immediately on pause (before the ad actually shows)
  • Reappear during the closing animation (overlapping with the ad)

Implementation Example

import { useState, useCallback, useRef } from 'react'
import { StreamLayerProvider, StreamLayerSDKTv, type VideoPlayerCallback } from '@streamlayer/web-os'

export const VideoView = () => {
  const [showPauseAd, setShowPauseAd] = useState(false)
  const [pauseAdRendered, setPauseAdRendered] = useState(false)
  const videoRef = useRef<HTMLVideoElement>(null)

  // Called when pause ad overlay renders/hides
  const onRenderPauseAd = useCallback((params: { rendered: boolean }) => {
    setPauseAdRendered(params.rendered)
  }, [])

  // Called when pause ad closes without video resumption
  const onClosePauseAd = useCallback(() => {
    setShowPauseAd(false)
  }, [])

  // Called when user clicks resume on pause ad overlay
  const videoPlayerController: VideoPlayerCallback = useCallback((data) => {
    if (data.play === true) {
      videoRef.current?.play()
    }
  }, [])

  // Video event handlers
  const onVideoPause = useCallback(() => {
    setShowPauseAd(true)
  }, [])

  const onVideoPlay = useCallback(() => {
    setShowPauseAd(false)
  }, [])

  // Hide your UI controls when pause ad is rendered
  const showControls = !pauseAdRendered

return (
<StreamLayerProvider sdkKey="your-sdk-key">
<StreamLayerSDKTv
      showPauseAd={showPauseAd}
      onRenderPauseAd={onRenderPauseAd}
      onClosePauseAd={onClosePauseAd}
      videoPlayerController={videoPlayerController}
      pauseAdVastUrl={[
        {
          template: 'default',
          url: 'https://your-vast-tag-url.com',
        },
      ]}
    >
      {/* Your video container */}
      <div>
        <video
          ref={videoRef}
          onPause={onVideoPause}
          onPlay={onVideoPlay}
        />

        {/* Hide controls when pause ad is displayed */}
        {showControls && (
          <div className="video-controls">
            {/* Play button, timeline, etc. */}
          </div>
        )}
      </div>
    </StreamLayerSDKTv>
</StreamLayerProvider>
  )
}

Key Points

  1. You control showPauseAd: Set it to true on video pause, false on play/resume.
  2. Hide UI on render: When onRenderPauseAd fires with rendered: true, hide your video controls (play button, timeline, etc.).
  3. Handle both exit paths: User can either resume video (via videoPlayerController) or dismiss the overlay (via onClosePauseAd).
  4. Single VAST tag: Only one item in pauseAdVastUrl is processed; additional items are ignored.