Skip to main content

Side-by-Side Ad

Integration

Follow these steps to add side-by-side ads to your application.

Step 1 — Install Packages

Install the StreamLayer React SDK:

npm install @streamlayer/react

Or with your preferred package manager:

# yarn
yarn add @streamlayer/react

# pnpm
pnpm add @streamlayer/react

Step 2 — Import Styles

Import the SDK stylesheet in your application entry point (e.g., App.tsx or index.tsx). This is required — without it the side-by-side layout, animations, and all other SDK UI will render without styles.

import '@streamlayer/react/style.css'

Step 3 — Import the Component

Import StreamLayerSDKAdvertisement from the @streamlayer/react/advertisement package:

import { StreamLayerSDKAdvertisement } from '@streamlayer/react/advertisement'

Step 4 — Set Up the Layout

Place StreamLayerSDKAdvertisement alongside your video player inside a wrapper element. Pass a ref to your video container via the videoRef prop. When a side-by-side ad activates, the SDK takes control of the video element's position and smoothly animates it into the left frame of a 50/50 split layout.

const videoContainerRef = useRef<HTMLDivElement>(null)

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}
muted={muted}
/>
</StreamLayerProvider>
</div>
)

Important: The video's parent container must have position: relative for correct absolute positioning.

Step 5 — Configure the Background Container

Set containerId on StreamLayerProvider to the HTML id of the outermost wrapper element that contains your video player and all StreamLayer SDK UI (ads, overlays, etc.).

When an advertisement is active, the SDK uses this container to apply the advertisement background image. It should be the same container you treat as the "player root" (the one that can be fullscreened/resized), not an inner ad-only element.

Important: containerId must point to the outermost wrapper — the same element that holds both the video and the SDK components. Do not point it at an inner element.


Props

PropTypeDescription
sideBySidebooleanRequired. Enables side-by-side ad mode.
externalAdbooleanEnables VAST/GAM external ad support. When true, the SDK renders VAST video ads.
mutedbooleanMutes the active ad video. Default: false.
videoRefReact.RefObject<HTMLElement | null>Required. Ref to the host video container for positioning.
videoPlayerControllerVideoPlayerCallbackCallback for video control events (mute, play) during ad playback.
persistentbooleanShow the ad each time it activates, ignoring the "already viewed" check.

VideoPlayerCallback

type VideoPlayerCallback = (data: { muted?: boolean; play?: boolean }) => void

Register this callback to react to SDK-initiated video control events. For example, when an external VAST ad plays, the SDK may request muting the host video.


How It Works

  1. Promotion activates — the SDK renders the ad layout off-screen to measure the target slot dimensions.
  2. Style capture — the SDK snapshots the video element's current inline styles so they can be restored later.
  3. Animated transition — the video element receives position: absolute and is smoothly transitioned (750ms) into the left half of the layout while the ad content fades in on the right.
  4. Live tracking — a ResizeObserver watches the layout slot, keeping the video coordinates up-to-date on window or container resize.
  5. Ad closes — the video transitions back to its original size and position, and the saved inline styles are restored.

Note: The SDK temporarily overrides the video element's inline styles while the ad is visible. Avoid setting the same style properties on the video element during this time.


Responsive Layout

The SDK automatically adapts the side-by-side layout based on the container width:

Container WidthLayoutBehavior
≥ 576pxDesktop50/50 split — video on left, ad content on right, with header and footer.
< 576pxMobileOverlay — ad appears as an overlay on top of the video instead of the split layout.

The breakpoint is calculated from the SDK container width, not the viewport.

mobileBreakpoint

The mobileBreakpoint prop on StreamLayerProvider controls when the SDK switches from the 50/50 split to the overlay layout. The default is 576 pixels — this works well for most integrations and typically does not need to be changed.

The overlay activates when the container width is below this value and the device is in portrait orientation.

<StreamLayerProvider
sdkKey="your-sdk-key"
event="your-event-id"
mobileBreakpoint={1024}
>
<StreamLayerSDKAdvertisement
sideBySide
videoRef={videoContainerRef}
/>
</StreamLayerProvider>

Note: Only override mobileBreakpoint if you need the overlay layout to appear at a wider container size than the default 576px. In most cases, the default is sufficient.


Implementation Example

import { useCallback, useRef, useState } from 'react'
import { StreamLayerProvider, type VideoPlayerCallback } from '@streamlayer/react'
import { StreamLayerSDKAdvertisement } from '@streamlayer/react/advertisement'

export const VideoView = () => {
const videoRef = useRef<HTMLVideoElement>(null)
const videoContainerRef = useRef<HTMLDivElement>(null)
const [muted, setMuted] = useState(false)

// Called when SDK requests video control (e.g., mute during external ad)
const videoPlayerController: VideoPlayerCallback = useCallback((data) => {
if (!videoRef.current) return

if (data.muted !== undefined) {
videoRef.current.muted = data.muted
setMuted(data.muted)
}
if (data.play === true) {
videoRef.current.play()
}
}, [])

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

<StreamLayerProvider
sdkKey="your-sdk-key"
event="your-event-id"
>
<StreamLayerSDKAdvertisement
sideBySide
externalAd
videoRef={videoContainerRef}
videoPlayerController={videoPlayerController}
muted={muted}
/>
</StreamLayerProvider>
</div>
)
}

Key Points

  1. videoRef is required: Pass a ref to your video container element. The video's parent must have position: relative.
  2. Enable externalAd for VAST ads: If your promotions include external VAST/GAM video ads, pass externalAd alongside sideBySide.
  3. Register videoPlayerController: Implement this callback to respond to SDK-initiated mute/play requests during ad playback.
  4. Responsive by default: The SDK automatically switches between the 50/50 split and overlay based on container width. The default mobileBreakpoint of 576px works for most cases.
  5. Don't fight inline styles: The SDK temporarily overrides video element styles during ad playback. Avoid modifying the same properties while an ad is active.

Fullscreen: Use Fake Fullscreen

The browser's native Fullscreen API (element.requestFullscreen()) creates a new top-level stacking context that is isolated from the rest of the document. StreamLayer overlays cannot render inside a native fullscreen element — the ad panel, controls, and any other SDK UI will be hidden behind the fullscreen layer.

Instead, implement fake fullscreen — a CSS-based approach that makes the video container fill the entire viewport without leaving the normal DOM flow:

.video-container--fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background: #000;
}
const [isFullscreen, setIsFullscreen] = useState(false)

const toggleFullscreen = () => setIsFullscreen((prev) => !prev)

return (
<div
className={isFullscreen ? 'video-container--fullscreen' : 'video-container'}
style={{ position: 'relative' }}
>
<div ref={videoContainerRef}>
<video ref={videoRef} src="https://example.com/stream.m3u8" autoPlay controls />
<button onClick={toggleFullscreen}>
{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
</button>
</div>

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

Because the SDK components remain siblings of the video container inside the same DOM tree, the side-by-side layout, overlays, and all other SDK UI continue to work correctly in fake fullscreen.

Warning: If your app already uses requestFullscreen(), you must replace it with the CSS approach above. There is no workaround — the native Fullscreen API is fundamentally incompatible with external overlays rendered outside the fullscreen element.


Muting the Host Video During Ad Playback

When a side-by-side ad includes audio (e.g., an external VAST video ad), two audio sources play simultaneously — the host video stream and the ad. The SDK uses the videoPlayerController callback to request that the host video is muted while the ad is playing and unmuted when the ad ends.

How it works

  1. The SDK calls your videoPlayerController with { muted: true } when an ad with audio starts playing.
  2. When the ad ends, pauses, or is dismissed, the SDK calls it again with { muted: false }.
  3. Your callback is responsible for actually muting the host <video> element (or your third-party player).

Basic implementation

const videoPlayerController: VideoPlayerCallback = useCallback((data) => {
if (!videoRef.current) return

if (data.muted !== undefined) {
videoRef.current.muted = data.muted
}
}, [])

Implementation with volume reduction

If you prefer to reduce volume instead of fully muting, you can lower the volume and restore it when the ad finishes:

const savedVolume = useRef(1)

const videoPlayerController: VideoPlayerCallback = useCallback((data) => {
if (!videoRef.current) return

if (data.muted === true) {
savedVolume.current = videoRef.current.volume
videoRef.current.volume = 0.1 // reduce to 10%
} else if (data.muted === false) {
videoRef.current.volume = savedVolume.current
}
}, [])

Third-party player integration

For players like Video.js, hls.js, or custom wrappers, adapt the callback to use your player's API:

// Video.js example
const videoPlayerController: VideoPlayerCallback = useCallback((data) => {
if (!playerRef.current) return

if (data.muted !== undefined) {
playerRef.current.muted(data.muted)
}
}, [])

Important: Always pass the videoPlayerController callback alongside externalAd when using external VAST ads. Without it, the host video and the ad will play audio simultaneously, resulting in a poor user experience.