External VAST Ads

Render your own VAST tag through the StreamLayer Element with the externalAdData prop, in side-by-side, sidebar, or L-bar format.

Overview

By default, ad units arrive from StreamLayer Studio through the event feed. The externalAdData
prop lets your app drive the ad itself: you hand the SDK a VAST tag URL and the format you want,
and the SDK builds an ad unit from it and renders it through the same pipeline as a Studio-scheduled
ad — including VAST impression, quartile, and click-through tracking.

Use this when the creative is selected by your own ad stack rather than by StreamLayer Studio.

Requires @streamlayer/react 2.0.0 or later.


Quick Start

import { useMemo, useRef } from 'react'
import { StreamLayerProvider } from '@streamlayer/react'
import { StreamLayerSDKAdvertisement } from '@streamlayer/react/advertisement'
import '@streamlayer/react/style.css'

export const Player = () => {
  const videoContainerRef = useRef<HTMLDivElement>(null)

  // Memoize the object — the SDK keys the injected ad unit on its identity.
  const externalAdData = useMemo(
    () => ({
      vastUrl: 'https://example.com/vast.xml',
      uiType: 'side-by-side' as const,
    }),
    []
  )

  return (
    <div id="player-root" style={{ position: 'relative' }}>
      <div ref={videoContainerRef}>
        <video src="https://example.com/stream.m3u8" autoPlay controls />
      </div>

      <StreamLayerProvider sdkKey="your-sdk-key" event="your-event-id" containerId="player-root">
        <StreamLayerSDKAdvertisement
          sideBySide
          videoRef={videoContainerRef}
          externalAdData={externalAdData}
        />
      </StreamLayerProvider>
    </div>
  )
}

The ad unit appears as soon as the VAST tag resolves, and is removed when externalAdData changes
to a different tag or the component unmounts.


ExternalAdInput

type ExternalAdUiType = 'side-by-side' | 'sidebar' | 'l-bar'

type ExternalAdInput = {
  /** VAST tag URL — raw VAST/VMAP, or a Google Ad Manager ad-tag URL. */
  vastUrl: string
  /** Which format the ad unit renders in. */
  uiType: ExternalAdUiType
}
FieldTypeDescription
vastUrlstringRequired. VAST tag URL. Raw VAST/VMAP is fetched directly; a pubads.g.doubleclick.net tag is resolved through Google PAL.
uiType'side-by-side' | 'sidebar' | 'l-bar'Required. Where the ad unit renders. See the table below.

Formats

uiTypeRenders asCreative used
'side-by-side'Double box beside the host videoVAST linear creative, played in the SDK's own player
'sidebar'Ordinary sidebar slotVAST linear creative as the sidebar media
'l-bar'Sidebar plus bottom banner stripLinear creative in the sidebar; the VAST companion ad as the strip

An 'l-bar' tag whose VAST carries no companion ad degrades to a plain sidebar — no empty strip is
reserved.

The sidebar video autoplays muted. Title and description come from the VAST <AdTitle> and
<Description> elements.


Slot Placement

Explicit host intent wins: an ad injected with uiType renders where the uiType says, regardless
of the VAST streamlayer extension. Ads that arrive from the event feed continue to follow the VAST
extension's adType (side-by-side when absent).

Because an external ad normally belongs to the side-by-side slot, useStreamLayerUI() alone can't
tell you whether to keep your sidebar and banner slots mounted for one. The
useExternalAdPlacement hook resolves this:

import { useStreamLayerUI } from '@streamlayer/react'
import {
  StreamLayerSDKAdvertisement,
  useExternalAdPlacement,
} from '@streamlayer/react/advertisement'

const Layout = () => {
  const uiState = useStreamLayerUI()
  const { externalSidebar, externalLbar } = useExternalAdPlacement(uiState.promotionExternalAd)

  return (
    <>
      <aside style={{ width: externalSidebar || uiState.promotionSidebar ? 320 : 0 }}>
        <StreamLayerSDKAdvertisement sidebar="right" externalAdData={externalAdData} />
      </aside>

      {externalLbar && (
        <div className="banner">
          <StreamLayerSDKAdvertisement banner="bottom" externalAdData={externalAdData} />
        </div>
      )}
    </>
  )
}
Return valueMeaning
externalSidebarThe active external ad resolves to the sidebar or L-bar format — keep the sidebar slot sized.
externalLbarThe active external ad resolves to L-bar and its VAST carries a companion — keep the banner slot sized.

Both flip only once the VAST has been parsed, so the sidebar never opens on an empty creative.


Rules and Limits

  • Memoize externalAdData. The SDK derives a stable id from vastUrl + uiType, but a fresh
    object literal on every render still churns the effect that injects and removes the ad unit. Use
    useMemo, or a module-level constant when the tag is fully static.
  • One injected ad at a time. The advertisement store holds a single ad unit, so only one
    externalAdData instance per page is supported. Injecting replaces any active ad unit, and a
    Studio-scheduled ad replaces an injected one.
  • externalAdData implies external-ad mode. You do not also need to pass the boolean
    externalAd prop for it to render.
  • Don't double up slots. A slot marked with the boolean externalAd prop is the programmatic
    GAM/PAL slot; it stands down for sidebar and L-bar placements so the plain slot renders the ad
    once. This behavior changed in 2.0.0.
  • Mute the host video. Pass a videoPlayerController so the host stream is muted while the ad's
    audio plays. See Video Controller.

Related