# Ads: ad breaks, ad videos, VAST and VMAP

> How to put ad breaks into a live stream, which ads play in a break, how to connect your own ad server with a VAST or VMAP tag, and which events the player sends.

FirstByte Stream plays ads in **ad breaks**. A break has a start time and a
length. During a break, the player shows ads in a layer above the content.
For a live stream, the stream continues under the ads with no sound, and the
player goes back to it when the break ends. For a recorded (VOD) asset, the
player stops the content, plays the ads, and then continues.

This page explains:

- how a live stream gets ad breaks,
- where the ads come from (your own ad videos, or your ad server),
- what the player does and which events it sends.

## Ad breaks on a live stream

A live stream gets an ad break in one of two ways.

| Source | How | When to use |
| --- | --- | --- |
| **Insert ad break** | The console button, or the API procedure `streams.liveInputs.insertAdBreak`. | You decide when a break starts. |
| **SCTE-35 from the encoder** | Your SRT encoder sends SCTE-35 `splice_insert` or `time_signal` messages in the MPEG-TS stream. | Your playout system already marks breaks. |

FirstByte Stream puts each break on a separate track of the stream, the
`cues` track. The track uses the MoQ event timeline format and carries the
SCTE-35 message. All viewers get the same break at the same point of the
stream, so the break starts at the same frame for every viewer.

### Insert a break from the console

1. Open **Live inputs** and select the input.
2. In the **Ad breaks** card, set the length (5 to 600 seconds).
3. Select **Insert ad break**.

The input must be live. The break starts at the newest frame of the stream.

### Insert a break with the API

```ts
await client.streams.liveInputs.insertAdBreak.mutate({
  orgId,
  id: liveInputId,        // the stream_live_input id
  durationSeconds: 30,    // 5 to 600
});
```

The webhook `streams.ad_break.inserted` tells you that a break was sent to the
stream.

### SCTE-35 from the encoder

Only the SRT ingest reads SCTE-35. Put the SCTE-35 messages on a PID with
`stream_type` `0x86`, and put a `CUEI` registration descriptor in the PMT.
FirstByte Stream reads:

- `splice_insert` with `out_of_network_indicator` set: the start of a break,
  with its `break_duration`.
- `splice_insert` with the cancel flag: the break is cancelled.
- `splice_insert` without `out_of_network_indicator`: return to the program.
- `time_signal` with a segmentation descriptor of a start or end type.

`splice_null` heartbeats are ignored. An encrypted `splice_info_section` is
ignored.

## Where the ads come from

Each live input has an **ad decision** setting. It tells the platform where
the ads of a break come from.

| Ad decision | The ads in a break |
| --- | --- |
| **Ad videos** (default) | Your own VOD assets, in the order that you set. |
| **VAST tag** | Your ad server, called for each viewer. Your ad videos fill any time that the ad server does not fill. |
| **OpenRTB (Prebid)** | An OpenRTB 2.6 auction for each viewer, through the platform's Prebid Server. Your ad videos fill any time that the bids do not fill. |

### Ad videos

Use **Ad videos** in the **Ad breaks** card to choose up to 20 of your VOD
assets. An asset can be an ad video only when all of these are true:

- its status is `ready`,
- it was transcoded (it has a CMAF ladder),
- it does not need signed URLs.

For each break, the platform starts at a position in your list that depends on
the break, and adds videos in order until their total length fills the break.
All viewers of one break get the same videos.

API: `streams.liveInputs.setAdAssets({ orgId, id, assetIds })`.

### VAST tag

Select **VAST tag** and enter your ad server's tag URL. Then select
**Test tag**. The test calls the tag once and shows the ads that it found:
the title, the length, the media file that the player will use, and whether
the ad can be skipped. A test sends no impression or tracking requests.

API: `streams.liveInputs.setAdDecision({ orgId, id, mode: 'vast', vastTagUrl })`.

Rules for the tag URL:

- It must start with `https://`.
- It must not point at a private, loopback or cloud metadata address.
- It can be at most 4096 characters.

For each viewer and each break, the platform calls your tag. It sends the
viewer's IP address and user agent in the `X-Device-IP`, `X-Forwarded-For`,
`User-Agent` and `X-Device-User-Agent` headers. It follows VAST wrappers to a
depth of 5. The whole call has a limit of 3.5 seconds. If your ad server does
not answer, or returns no ads, the viewer gets your ad videos, or no ads.

The platform supports VAST 2.0, 3.0 and 4.x linear ads. Ads with a `sequence`
attribute make a pod, in sequence order. It uses stand-alone ads to fill the
rest of the break. It chooses a media file in this order: progressive MP4,
then WebM, then HLS. VPAID creatives and `http://` media files are not used.

#### Macros in the tag URL

The platform replaces these macros in your tag URL before it calls it:

| Macro | Value |
| --- | --- |
| `[CACHEBUSTING]` | 8 random digits |
| `[TIMESTAMP]` | The time of the call, ISO 8601, UTC |
| `[PAGEURL]`, `[DOMAIN]` | The page that plays the stream, and its host |
| `[BREAKMAXDURATION]` | The length of the break, in seconds |
| `[BREAKPOSITION]` | `1` pre-roll, `2` mid-roll, `3` post-roll (live: `2`) |
| `[CC_INPUT_ID]`, `[CC_ASSET_ID]` | The live input, or the asset |
| `[CC_EVENT_ID]` | The SCTE-35 event id of the break |
| `[CC_DURATION_MS]` | The length of the break, in milliseconds |

Macros that the platform does not know stay in the URL as they are.

### OpenRTB (Prebid)

Use this when you have accounts with SSPs (bidders) but no ad server of your
own. The platform runs the auction in its own Prebid Server.

1. Open **Settings → Ads → OpenRTB demand**.
2. Turn it on and add your bidders. Each bidder has a code (for example
   `appnexus`) and the parameters that the bidder gave you. The console lists
   the bidder codes that Prebid Server knows.
3. Set the floor price (CPM), the currency and the auction timeout
   (200 to 1500 ms).
4. On the live input, set **Ad decision** to **OpenRTB (Prebid)**.

API: `streams.ads.setOpenRtb({ orgId, enabled, bidders, floorCpm, currency, tmaxMs, gdprDefault, test, ... })`.

For each viewer and each break, the platform sends one OpenRTB 2.6 request. The
request has one in-stream video slot for each 15 seconds of the break (at most
6), with the pod fields `podid`, `slotinpod` and `poddur`. It includes the page,
the viewer's user agent and IP address, your supply chain node, and the
viewer's consent signals. The winning bid's VAST goes through the same rules
as a VAST tag. The platform sends the win notice (`nurl`). The player sends the
billing notice (`burl`) when the ad starts.

Without bidder accounts, the auction returns no bids, and the break uses your
ad videos.

### Consent signals

Give the player the viewer's consent signals from your consent management
platform. The player sends them with each ad request, and they fill the
`[GDPR]`, `[GDPRCONSENT]`, `[US_PRIVACY]`, `[GPP]` and `[GPP_SID]` macros:

```ts
player.setAdConsent({ gdpr: 1, gdprConsent: tcString, usPrivacy: "1YNN", gpp, gppSid: "2,6" });
```

On the element, use the attributes `ad-gdpr`, `ad-gdpr-consent`,
`ad-us-privacy`, `ad-gpp` and `ad-gpp-sid`. If the page gives no GDPR signal,
the OpenRTB request uses the organization's GDPR default (on, unless you
change it).

## Ads on a recorded asset (VMAP)

A VOD asset can have an **ad tag**. Open the asset's playback endpoints and
use **Ads**:

| Ads | The breaks |
| --- | --- |
| **None** | No ads. |
| **VAST pre-roll** | One break before the content, from a VAST tag. |
| **VMAP** | The breaks that your VMAP 1.0 document gives. |

API: `streams.assets.setAdTag({ orgId, id, kind: 'vmap' | 'vast' | null, url })`.

The platform reads the VMAP `AdBreak` elements with a linear `breakType`.
It supports these `timeOffset` values: `start`, `end`, `HH:MM:SS(.mmm)` and
`n%`. It does not support `#n` (position). Each break can use an `AdTagURI`
or an inline `VASTAdData`. The platform keeps the VMAP document for 5 minutes.

The player:

- plays a pre-roll before the content starts,
- stops the content at a mid-roll, plays the break, and continues,
- plays a post-roll when the content ends,
- plays each break one time for each page load.

If a viewer seeks forward past breaks that did not play, the player plays the
last of those breaks one time, and then continues from the seek position.

## What the player does

The `<clutchcall-player>` element and the `StreamPlayer` class do all of the
above when `ads` is on (the default). To turn ads off, set `ads: false`.

During a break, the player shows:

- the label **Ad i of n** with the time that remains,
- **Skip ad**, when a VAST ad allows a skip and the skip time has passed,
- **Learn more**, when a VAST ad has a click-through URL. It opens the URL in
  a new tab and pauses the ad.

The player sends the VAST tracking requests: impression, `creativeView`,
`start`, the quartiles, `progress`, `complete`, `mute`, `unmute`, `pause`,
`resume`, `skip`, click tracking, and `error` with an error code (400, 402 or
405). It sends each request one time.

### Player events

| Event | When |
| --- | --- |
| `adbreakstart` | A break starts. |
| `adstart` | An ad starts. `kind` is `asset` or `vast`; a VAST ad also has `adId` and `adTitle`. |
| `adend` | An ad ends. |
| `adclick` | The viewer selects **Learn more**. |
| `adskip` | The viewer selects **Skip ad**. |
| `adbreakend` | A break ends. `reason` is `completed`, `cancelled`, `returned` or `error`. |

A live break has `eventId` (the SCTE-35 event id). A VOD break has `breakId`.

```ts
const player = document.querySelector("clutchcall-player");
player.addEventListener("adstart", (e) => console.log("ad", e.detail.kind, e.detail.adId));
player.addEventListener("adbreakend", (e) => console.log("break end", e.detail.reason));
```
