Skip to main content

Ad Markers

CUE-OUT/IN

For LL-HLS/HLS

You can dynamically insert Ad Markers into LL-HLS and HLS playlists using the REST API.

When requesting the CUE-OUT event, the following tags will be added to the playlist:

#EXT-X-CUE-OUT:DURATION=<time>

#EXT-X-CUE-IN
ElementDescription
#EXT-X-CUE-OUT,
#EXT-X-CUE-IN
#EXT-X-CUE-OUT and #EXT-X-CUE-IN are a pair, and the entire section between the two tags will be replaced with ad content by the ad server
DURATION=<time>DURATION=<time> is required and represents the duration of the ad.

You can request the CUE-IN event to end an inserted ad early. When the event is called, the #EXT-X-CUE-IN tag is immediately added to the playlist, and the previously added #EXT-X-CUE-IN tag is removed.

API Interface

Request

POST /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvent

Header

Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>

Body

{
"eventFormat": "cue",
"startOffset": 0, // optional, milliseconds to delay the marker from the current position
"events":[
{
"cueType": "out", // out | in
"duration": 60500 // milliseconds, only available when cueType is out
}
]
}
POST /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvents

Header

Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>

Body

[
{
"eventFormat": "id3v2",
"eventType": "video", // "eventTarget": "video" is same
"events":[
{
"frameType": "TXXX",
"info": "AirenSoft",
"data": "OvenMediaEngine"
},
{
"frameType": "TIT2",
"data": "OvenMediaEngine 123"
}
]
},
{
"eventFormat": "cue",
"startOffset": 0, // optional, milliseconds to delay the marker from the current position
"events":[
{
"cueType": "out", // out | in
"duration": 60500 // milliseconds, only available when cueType is out
}
]
}
]

Responses

200 Ok

The request has succeeded

Header

Content-Type: application/json

Body

{
"message": "OK",
"statusCode": 200
}
400 Bad Request

Invalid request. Body is not a Json Object or does not have a required value

401 Unauthorized

Authentication required

Header

WWW-Authenticate: Basic realm=”OvenMediaEngine”

Body

{
"message": "[HTTP] Authorization header is required to call API (401)",
"statusCode": 401
}
404 Not Found

The given vhost name or application name could not be found.

Body

{
"message": "[HTTP] Could not find the application: [default/app2] (404)",
"statusCode": 404
}

SCTE-35 Event Insertion

For LL-HLS/HLS

Using the REST API, you can insert ad markers into LL-HLS and HLS playlists as #EXT-X-DATERANGE tags. #EXT-X-DATERANGE specifies ad timing via the SCTE-35 OUT/IN attributes.

For SRT Push

Starting with OvenMediaEngine Enterprise 0.20.0.0-1, you can insert SCTE-35 events (splice_insert()) not only into LL-HLS and HLS playlists, but also into SRT Push. When you send ad start/end signals (OUT/IN) or other custom events to OvenMediaEngine Enterprise via the sendEvents API, the information is inserted into the SRT Push and propagated to other systems.

Behavior Rule

These rules apply to markers inserted through the API. An SCTE-35 event that arrives in an input stream is forwarded with the return point its sender chose.

  1. When an OUT event sets autoReturn with a duration, an IN is inserted automatically at the end of that duration.
  2. Sending an IN before then replaces the automatic one, so the break ends where you send it.
  3. Without autoReturn, no IN is inserted on your behalf, because an explicit splice-in is expected to follow. Send the IN yourself to end the break.
warning

Some downstream devices may fail to detect a return from ad break. We recommend sending an IN event after an OUT event.

API Interface

Request

POST /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvent

Header

Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>

Body

{
"eventFormat": "scte35",
"startOffset": 0, // optional, milliseconds to delay the marker from the current position
"events":[
{
"id": {{randomId}}, // required, 32bits unsigned number, auto filled if not present
"type": "out", // required, out | in
"duration": 10000, // milliseconds, only available when type is out
"autoReturn": false // optional, fixes the return at the end of the duration
}
]
}
POST /v1/vhosts{vhost}/apps/{app}/streams/{stream}:sendEvents

Header

Authorization: Basic {credentials}

# Authorization
Credentials for HTTP Basic Authentication created with <AccessToken>

Body

[
{
"eventFormat": "scte35",
"startOffset": 0, // optional, milliseconds to delay the marker from the current position
"events":[
{
"spliceCommand": "spliceInsert",
"id": {{randomId}}, // required, 32bits unsigned number, auto filled if not present
"type": "out", // required, out | in
"duration": 30000, // milliseconds, only available when type is out
"autoReturn": false // optional, fixes the return at the end of the duration
}
]
}
]
info

You enter duration in milliseconds (ms), but it is emitted to the playlist as PLANNED-DURATION in seconds (s).

Responses

200 Ok

The request has succeeded

Header

Content-Type: application/json

Body

{
"message": "OK",
"statusCode": 200
}
400 Bad Request

Invalid request. Body is not a JSON object or does not have a required value

401 Unauthorized

Authentication required

Header

WWW-Authenticate: Basic realm=”OvenMediaEngine”

Body

{
"message": "[HTTP] Authorization header is required to call API (401)",
"statusCode": 401
}
404 Not Found

The given vhost name or application name could not be found.

Body

{
"message": "[HTTP] Could not find the application: [default/app2] (404)",
"statusCode": 404
}

Example: Successful Event Insertion

For LL-HLS/HLS

Below is an LL-HLS playlist sample after injecting an SCTE-35 event:

#EXT-X-DATERANGE:ID="123",START-DATE="2025-01-01T09:15:00+00:00",PLANNED-DURATION=10.0,SCTE35-OUT=0xF...
...
#EXT-X-DATERANGE:ID="123",START-DATE="2025-01-01T09:15:00+00:00",SCTE35-IN=0xF
ElementDescription
SCTE35-OUTSCTE-35 payload indicating the start of the ad break (content → ad).
SCTE35-INSCTE-35 payload indicating the end of the ad break (ad → content).
PLANNED-DURATIONAd break duration in seconds. When OUT sets autoReturn, an IN is inserted at the end of this time.
ID

An identifier that ties the OUT/IN to the same break.

* 32-bit unsigned integer.

START-DATEAd start timestamp (ISO-8601).
* yyyy-mm-ddThh:mm:ss±UTC

For SRT Push

If the OvenMediaEngine Enterprise log shows output similar to the example below, the SCTE-35 event was delivered successfully.

[11-03 21:29:02.028] D [SW-Push:2415407] FFmpegWriter | writer.cpp:523 | SCTE-35 Event: SpliceCommandType=5, ID=2025, OutOfNetwork=true, Timestamp=372370 ms, Duration=30000 ms, AutoReturn=false

Scheduling a Marker Ahead

startOffset delays a marker by that many milliseconds from the position the stream has reached when the request arrives. Omitting it, or sending 0, places the marker at that position. Negative values are not allowed, and the maximum is 300000.

A scheduled marker holds the break sequence until it is reached. Between the request and that position, no other OUT or IN is accepted, because a break must be opened and closed in order. Keep the offset only as long as the moment you are aiming at.

Segmentation Mode

Every track has to mark the break at the same place. In duration segmentation each track decides its own boundaries, so the same marker can land on a different segment on each of them.

Set <SegmentationMode> to synced on the streams that carry ad breaks:

<Publishers>
<LLHLS>
<SegmentationMode>synced</SegmentationMode>
...
</LLHLS>
</Publishers>

Keyframe on Cue

HLS and LL-HLS segments can only be split at keyframes. When an ad marker arrives in the middle of a GOP, the segment carrying the marker ends at the next keyframe, so the actual split can land up to one keyframe interval after the requested position and the ad break plays shorter than the duration it advertises.

When KeyframeOnCue is enabled, every video encoder inserts a keyframe exactly at the marker position, so the segment splits right where the marker was requested and the break length matches the advertised duration. This applies to both CUE and SCTE-35 events. Enable it when a downstream ad stitcher replaces the break content, so the replaced range starts exactly at the marker.

In Server.xml, configure <Application><OutputProfiles><MediaOptions> as below:

<OutputProfiles>
<MediaOptions>
<KeyframeOnCue>true</KeyframeOnCue>
</MediaOptions>
...
</OutputProfiles>
ElementValueDescription
KeyframeOnCuetrue | false
(default: false)
Inserts a keyframe at the position of every CUE and SCTE-35 marker, on every encoded video rendition of the application.
info

The inserted keyframe is an extra one; the configured keyframe interval keeps its original cadence. It applies to encoded video renditions only. A bypass (passthrough) video keeps the keyframes of the source, so its splits still follow the source's own keyframes.