This example starts as a standard WHEPClient subscriber, then adds post-subscribe controls that tell the server to switch playback to an interstitial stream without disconnecting.
It also supports resuming the original target stream after an insert.
A common example is ad insertion on a live stream.
- standard WHEP subscribe lifecycle (
init+subscribe) - server-directed interstitial switching during active playback
- resume command to return to the original target stream
- interstitial request UI that is enabled only after subscribe starts
Once subscribe is active:
- Switch to Interstitial posts an
insertspayload to the interstitial endpoint. - Resume posts a
resumepayload for the target stream. - Playback remains connected; stream selection is changed server-side.
- Interstitial support is preconfigured in the
livewebapp (defaultappcontext in most setups). - Requests are sent directly to:
/{app}/interstitial(for example,/live/interstitial)
Stream Manager deployments must use InterstitialStream for clientBroadcastStream.
In red5-common.xml, switch this bean:
<!-- default -->
<bean
id="clientBroadcastStream"
scope="prototype"
lazy-init="true"
class="com.red5pro.override.ProStream"
/>
<!-- interstitial-enabled -->
<bean
id="clientBroadcastStream"
scope="prototype"
lazy-init="true"
class="com.red5pro.interstitial.InterstitialStream"
/>The server supports two payload modes:
inserts: switch to interstitial contentresume: return to target stream
If both resume and inserts are present, resume takes precedence and inserts is ignored.
{
"user": "any",
"digest": "any",
"inserts": [
{
"id": 3,
"target": "live/stream1",
"uri": "live/stream2",
"loop": true,
"type": "INDEFINITE",
"isInterstitialVideo": true,
"isInterstitialAudio": true,
"start": 0,
"duration": 30000
}
]
}{
"user": "any",
"digest": "any",
"resume": "live/stream1"
}user: required by API contract; passed through to request handlersdigest: required by API contract; passed through to request handlersinserts: array of interstitial insert operationsid: insert id; typically for request trackingtarget: target live stream path (app/streamName)uri: inserted stream path (or FLV name inwebapps/live/streams)loop: whether inserted FLV loopsisInterstitialAudio: whether audio channel is switched (default true)isInterstitialVideo: whether video channel is switched (default true)type: duration control (INDEFINITE,STREAM_CLOCK,WALL_CLOCK)start: start time in millisecondsduration: duration in millisecondsresume: stream path to resume (app/streamName)
In this testbed code, the insert field is named interstitial in src/lib/interstitial-api.ts, but it represents the same concept as uri in the generic API description.
Direct POST to:
;`${protocol}://${host}:${port}/${app}/interstitial`POST is forwarded through Stream Manager to the resolved origin interstitial endpoint:
;`http://${originHost}:5080/${app}/interstitial`The helper handles this routing via forwardPOSTRequest(...) after origin resolution.
const subscriber = new red5prosdk.WHEPClient()
await subscriber.init({
endpoint,
streamName,
mediaElementId: 'subscriber-video',
connectionParams,
})
await subscriber.subscribe()
await postInterstitialSwitch(
{ settings, user: 'admin', digest: 'changeme' },
{
target: 'live/stream1',
interstitial: 'live/stream2',
loop: true,
type: 'INDEFINITE',
isInterstitialAudio: true,
isInterstitialVideo: true,
start: '0',
duration: '30000',
}
)
await postInterstitialResume({ settings, user: 'admin', digest: 'changeme' }, 'live/stream1')- subscribe flow:
startSubscribe() - switch request submit:
submitInterstitialSwitch() - resume request submit:
submitInterstitialResume() - interstitial endpoint + POST routing:
src/lib/interstitial-api.ts - post-subscribe UI enable/disable:
syncInterstitialSection()
Pair this with whep-basic to compare standard subscribe playback and interstitial-controlled playback.