Exposed Pause Ad

The Pause Ad integration enables high-impact, non-intrusive advertising within the StreamLayer Web SDK. When a user pauses a video, the SDK handles the asynchronous loading and rendering of a VAST-compliant ad overlay and GPT ads (Google Publisher Tag ads loaded in a sandboxed iframe via StreamLayer's ad host). It features a robust state management system to help developers synchronize their video player UI with the ad’s lifecycle, ensuring that interactive controls (like play/resume) and animations work seamlessly on desktop.

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

Overview

The Pause Ad feature displays an advertisement overlay when the user pauses video playback. It supports two ad variants:

  • VAST ads — Server-fetched static image ads via the SDK's external pause ad store
  • GPT ads — Google Publisher Tag ads loaded in a sandboxed iframe via StreamLayer's ad host

Integration

Import UI Component

Import the StreamLayerPauseAd component from the @streamlayer/react/pause-ad package:

import { StreamLayerPauseAd } from '@streamlayer/react/pause-ad'

Standalone Usage (Without Wrapping Video)

StreamLayerPauseAd accepts optional children. If you don't want to wrap your video element, you can render it as a standalone sibling alongside your video. No children are required — the component works on its own.

<App>
  {/* Your video — not wrapped by the SDK */}
  <div className="video-container">
    <video ref={videoRef} onPause={onVideoPause} onPlay={onVideoPlay} />
  </div>

  <StreamLayerProvider sdkKey="your-sdk-key" event="your-event-id">
    {/* Pause ad rendered independently */}
    <StreamLayerPauseAd
      showPauseAd={showPauseAd}
      onRenderPauseAd={onRenderPauseAd}
      onClosePauseAd={onClosePauseAd}
      videoPlayerController={videoPlayerController}
      pauseAdExternalUrls={[{ template: 'default', url: 'your-ad-url' }]}
    />
  </StreamLayerProvider>
</App>

When used this way, you control the pause ad's size and position through CSS by targeting the .SL_PauseAd class name:

.SL_PauseAd {
  width: 300px !important;
  left: auto !important;
}

You can inspect the rendered pause ad element in your browser's developer tools to discover additional internal class names for further styling.


Ad Configuration

VAST Ad

Pass a VAST tag URL via pauseAdExternalUrls:

<StreamLayerPauseAd
  pauseAdExternalUrls={[
    { template: 'default', url: 'https://your-vast-tag-url.com/vast.xml' },
  ]}
  // ...other props
/>

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

GPT Ad (Slot-Based) — Recommended

Pass a GPT slot ID (non-HTTP path) via pauseAdExternalUrls. The SDK detects it as a GPT ad because the URL does not start with http:

<StreamLayerPauseAd
  pauseAdExternalUrls={[
    {
      url: '/12345/ad-unit-name',  // Non-HTTP = treated as GPT slot ID
      adWidth: 315,
      adHeight: 400,
      npa: 0,
      is_lat: 0,
      rdid: 'device-advertising-id',
      idtype: 'idfa',  // Roku = RIDA, Samsung = TIFA, Android = adid, tvOS = idfa
      targeting: { genre: 'sports', league: 'nba' },
    },
  ]}
  // ...other props
/>

GPT Ad (URL-Based)

Pass a Google Ad Manager URL with output=ldjh query param via pauseAdExternalUrls:

<StreamLayerPauseAd
  pauseAdExternalUrls={[
    {
      url: 'https://pagead2.googlesyndication.com/gampad/ads?...&output=ldjh',  // output=ldjh = GPT
      product: 'tsn',
      platform: 'cotv',
      platformtype: 'amazonfire',
      pagetype: 'playerpage',
      content: 'cfl-news-and-highlights',
      npa: 0,
      is_lat: 0,
      rdid: 'device-advertising-id',
      idtype: 'idfa',  // Roku = RIDA, Samsung = TIFA, Android = adid, tvOS = idfa
      targeting: { genre: 'sports', league: 'nba' },
    },
  ]}
  // ...other props
/>

Ad Type Detection

The SDK automatically determines which backend to use based on the URL:

  • GPT — if the URL does not start with http (slot ID) or contains the output=ldjh query param
  • VAST — otherwise

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.
pauseAdExternalUrlsPauseAdExternalUrl[]Array of ad configurations (supports VAST and GPT). See below.
optionsStreamLayerPauseAdOptionsOptional configuration (see Options below).

pauseAdExternalUrls Fields

FieldTypeDescription
urlstringRequired. VAST URL, GPT URL (output=ldjh), or GPT slot ID (non-HTTP path)
productstring?Product identifier (e.g., tsn). Used for ad unit path building
platformstring?Platform identifier (e.g., cotv). Used for ad unit path building
platformtypestring?Platform type (e.g., amazonfire). Used for ad unit path building
pagetypestring?Page type (e.g., playerpage). Used for ad unit path building
contentstring?Content identifier (e.g., cfl-news-and-highlights). Used for ad unit path building
adWidthnumber?Custom ad width for GPT iframe
adHeightnumber?Custom ad height for GPT iframe
npa0 | 1Non-personalized ads flag (1 = non-personalized). When npa=1, permutive targeting keys are stripped
is_lat0 | 1Limit ad tracking flag (1 = limited)
rdidstring?Device advertising ID (e.g., Roku RIDA, Samsung TIFA, Android adid, tvOS idfa)
idtypestring?Device ID type identifier (e.g., RIDA, TIFA, adid, idfa)
targetingRecord<string, string>?Custom GPT key-value targeting pairs (passed as t= param to the ad host)

Options

type StreamLayerPauseAdOptions = {
  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
}

How It Works

VAST Path

  1. showPauseAd becomes true → SDK fetches ad from the VAST URL
  2. Store loads and returns ad data with imageSrc and adUrl
  3. Static image ad is rendered in the overlay
  4. On desktop, the image is wrapped in a click-through link
  5. Emits rendered event via onRenderPauseAd

GPT Path

  1. showPauseAd becomes true → component renders immediately (no store fetch)
  2. An iframe is created pointing to the StreamLayer ad host.
  3. URL params include targeting data (product, platform, platformtype, pagetype, content), privacy flags (npa, is_lat), custom targeting (t), device ID (rdid/idtype), and either url or slot.
  4. Iframe communicates via postMessage:
    • slGptAdLoaded — ad creative loaded (includes dimensions)
    • slGptAdReady — ad fully rendered
  5. Iframe is scaled to fit the container via ResizeObserver + CSS transform: scale()
  6. 10s safety timeout — if slGptAdLoaded isn't received within 10 seconds, the ad auto-closes

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

Event Lifecycle

The pause ad emits lifecycle events via callbacks:

EventWhen
enabledshowPauseAd becomes true
disabledshowPauseAd becomes false (without explicit close)
closedUser explicitly dismisses the ad
renderedAd content is visible (image loaded or GPT iframe ready)
navigatedUser clicks the ad link (VAST desktop only)

UIState Integration

When the pause ad is active:

  • Sidebar content is hidden
  • Overlay promotions are hidden
  • Banner ads are hidden
  • Notification overlays are hidden

This ensures no visual conflicts between the pause ad and other SDK UI.


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 StreamLayer Element
  • Closing delay (400ms): SDK animates the StreamLayer Element before removing it

State combinations

showPauseAdpauseAdRenderedState
falsefalseVideo playing or paused without ad
truefalseVideo paused, SDK preparing StreamLayer Element
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, type VideoPlayerCallback } from '@streamlayer/react'
import { StreamLayerPauseAd } from '@streamlayer/react/pause-ad'

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" event="your-event-id">
      <StreamLayerPauseAd
        showPauseAd={showPauseAd}
        onRenderPauseAd={onRenderPauseAd}
        onClosePauseAd={onClosePauseAd}
        videoPlayerController={videoPlayerController}
        pauseAdExternalUrls={[
          {
            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>
      </StreamLayerPauseAd>
    </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. Two ad backends: Use pauseAdExternalUrls for VAST ads or pauseAdExternalUrls for GPT ads (slot-based or URL-based).
  5. Ad type auto-detection: The SDK automatically determines VAST vs GPT based on the URL format.

Related