diff --git a/.prettierignore b/.prettierignore index 9b93b4abaee6..e2f8a2233143 100644 --- a/.prettierignore +++ b/.prettierignore @@ -17,6 +17,7 @@ theoplayer_versioned_docs/*/changelog.md theoplayer_versioned_docs/*/static/theoplayer/*/api-reference ads/api/ads-client.swagger.json ads/api/signaling/ +ads_versioned_docs/*/api/ adengine/static/ad-engine.yaml adengine/reference/ millicast/api/ diff --git a/ads/assets/img/breaks/break-lifecycle.svg b/ads/assets/img/breaks/break-lifecycle.svg new file mode 100644 index 000000000000..54c414a029f2 --- /dev/null +++ b/ads/assets/img/breaks/break-lifecycle.svg @@ -0,0 +1,30 @@ + + + + + + + + initial + + PREPARING + + CUED + + READY + + ERROR + + SIGNALED + + + worker/EABN + + worker/EABN + + worker/health + + API punch + + manifest / proxy + diff --git a/ads/assets/img/breaks/format-double.svg b/ads/assets/img/breaks/format-double.svg new file mode 100644 index 000000000000..978e9035f2eb --- /dev/null +++ b/ads/assets/img/breaks/format-double.svg @@ -0,0 +1,8 @@ + + + + + CONTENT + AD + side-by-side primary and companion windows + diff --git a/ads/assets/img/breaks/format-lshape-ad.svg b/ads/assets/img/breaks/format-lshape-ad.svg new file mode 100644 index 000000000000..5f7a72692bfb --- /dev/null +++ b/ads/assets/img/breaks/format-lshape-ad.svg @@ -0,0 +1,8 @@ + + + + AD + companion + backdrop + ad window with companion backdrop + diff --git a/ads/assets/img/breaks/format-lshape-content.svg b/ads/assets/img/breaks/format-lshape-content.svg new file mode 100644 index 000000000000..96bdc0bdf71e --- /dev/null +++ b/ads/assets/img/breaks/format-lshape-content.svg @@ -0,0 +1,8 @@ + + + + CONTENT + companion + backdrop + live content window with companion backdrop + diff --git a/ads/assets/img/breaks/format-overlay.svg b/ads/assets/img/breaks/format-overlay.svg new file mode 100644 index 000000000000..f526f3b3bcc6 --- /dev/null +++ b/ads/assets/img/breaks/format-overlay.svg @@ -0,0 +1,7 @@ + + + CONTENT + + OVERLAY + semi-transparent overlay over live content + diff --git a/ads/assets/img/breaks/format-single.svg b/ads/assets/img/breaks/format-single.svg new file mode 100644 index 000000000000..a83e60ae25ca --- /dev/null +++ b/ads/assets/img/breaks/format-single.svg @@ -0,0 +1,5 @@ + + + AD + full-screen replacement + diff --git a/ads/concepts/break-manifest.mdx b/ads/concepts/break-manifest.mdx new file mode 100644 index 000000000000..bfe75792e175 --- /dev/null +++ b/ads/concepts/break-manifest.mdx @@ -0,0 +1,227 @@ +--- +sidebar_position: 7 +sidebar_label: Break Manifest +--- + +# Break Manifest + +The Break Manifest is the canonical, machine-readable description of the ad breaks that are currently relevant for a [channel](/ads/concepts/channels). It is a small JSON document that the OptiView Player polls on a fixed cadence to learn which breaks to prepare and play. + +The Break Manifest is **side-loaded**: it is served from its own endpoint, separately from the media (HLS/DASH) manifest. The player fetches the media manifest from your CDN as usual and, in parallel, polls the Break Manifest to drive ad break scheduling. This is different from server-side ad insertion (SSAI), where ad cues are injected directly into the media manifest. + +## Side-loading versus SSAI cue injection + +OptiView Ads can deliver break timing to the player in two distinct ways. A channel can use either mechanism depending on how the workflow is integrated. + +| Delivery mechanism | Where the break information lives | Who consumes it | +| ----------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| Side-loaded (this page) | A separate JSON Break Manifest served from a dedicated endpoint. | The OptiView Player, which polls the endpoint and schedules breaks client-side. | +| SSAI cue injection | `#EXT-X-DATERANGE` cues rewritten inline into the proxied HLS media playlist. | Any player that reads the manifest; used for Google DAI server-guided pods. | + +With **side-loading**, the media manifest is untouched: the player merges the break timeline it reads from the Break Manifest with the content timeline it reads from the media manifest. This keeps the media manifest cacheable and lets the player own the ad experience (layout, skip, snapback). + +With **SSAI cue injection**, OptiView Ads proxies the upstream HLS playlist and inserts `#EXT-X-DATERANGE` cues in place. Cue injection applies only to `wallclock` channels that have a Google DAI (`SSAI_DAI`) integration configured, because `#EXT-X-DATERANGE` requires a `START-DATE`, which has no `pts` equivalent. + +## Endpoint + +```text +GET /manifest/v1/:orgId/channels/:channelId +``` + +| Path parameter | Description | +| -------------- | --------------------------------------- | +| `orgId` | The organization that owns the channel. | +| `channelId` | The channel to read breaks for. | + +The Break Manifest endpoint is a public read endpoint: it takes no authentication and is served with permissive CORS so that players and CDNs can fetch it directly. It differs from the [Channels](/ads/concepts/channels) management API, which is authenticated. Do not place secrets in the polling URL. + +```bash +curl 'https://ads.example.com/manifest/v1/org_123/channels/sports-main' +``` + +### Responses + +| Status | Meaning | +| ------ | -------------------------------------------------------------------------------------- | +| `200` | The channel exists. Returns the Break Manifest JSON document described below. | +| `404` | No channel with `channelId` exists in the organization. Returns a JSON error envelope. | + +### Caching + +The response carries a `Cache-Control` header so that players and CDNs poll at a rate the channel controls. + +| Case | `Cache-Control` | Source | +| ------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------- | +| `200` (any channel) | `public, max-age=` | The channel's active polling interval (`pollingActiveSeconds`), in seconds. | +| `404` (not found) | `public, max-age=` | A short negative-cache window (default `5` seconds) so a missing channel is not hammered. | + +The `max-age` on a successful response always uses the **active** polling interval, so that a cached copy is never held longer than the shortest polling cadence the channel advertises. Use the `polling` values inside the manifest body (see below) to decide how often to poll; use `Cache-Control` for CDN and HTTP cache behavior. + +## Manifest envelope + +The response body is the Break Manifest envelope. The following descriptions are written from the service `breakManifestSchema`. + +| Field | Type | Description | +| ---------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `version` | string | Break Manifest format version. Currently `1.0.0`. Use it to guard against future format changes. | +| `timebase` | `wallclock` or `pts` | The channel timebase. Determines how each break's `start` is expressed (see [Channels → Timebase](/ads/concepts/channels)). | +| `polling` | object | Advertised polling cadence, in seconds. Contains `idle` and `active`. | +| `polling.idle` | integer | Interval to poll at when no break is active (from the channel `pollingIdleSeconds`). | +| `polling.active` | integer | Interval to poll at while a break is active (from the channel `pollingActiveSeconds`). | +| `breaks` | array | The breaks currently relevant for the channel. May be empty. Each entry is described in [Break entries](#break-entries). | + +## Break entries + +Each element of `breaks` describes one ad break. The fields are written from the service break schema. + +| Field | Type | Required | Description | +| -------------- | --------------------------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | string | Yes | Stable identifier of the break, unique within the channel. | +| `start` | ISO 8601 string, or number | Yes | Break start on the channel timebase. A UTC ISO 8601 timestamp when `timebase` is `wallclock`; a numeric presentation timestamp when `timebase` is `pts`. | +| `duration` | number (seconds) | Yes | Length of the break, in seconds. | +| `resumeOffset` | number (seconds) | No | Where content playback resumes relative to the break, in seconds. Omitted when the break does not override the default resume behavior. | +| `controls` | object | No | Playback controls for the break. See [Controls](#controls). | +| `variant` | object, or array of objects | Yes | The ad experience(s) to render for the break. A single variant object, or a non-empty list of variants. See [Variants](#variants). | + +A break only appears once its timebase-specific start is known: `wallclock` breaks require a resolved start timestamp, and `pts` breaks require a numeric start. Breaks missing that value for the channel timebase are not included. + +### Controls + +When present, `controls` refines how the player treats the break. + +| Field | Type | Description | +| ------------ | ---------------- | --------------------------------------------------------------------------------- | +| `skipOffset` | number (seconds) | How long into the break before it becomes skippable. Omit to make it unskippable. | +| `snapback` | boolean | When `true`, the player snaps back to the break-in point after seeking past it. | + +### Variants + +`variant` carries the ad experience. Each variant has a `format` and a set of `assets`; some formats add layout fields. Provide a single variant, or a list when the break offers more than one experience (for example, targeted by device type). + +| `format` | Description | +| ---------------- | --------------------------------------------------------------------------------- | +| `single` | Full-screen ad insertion that replaces the content. | +| `double` | Double Box: content continues alongside the ad and a companion asset. | +| `lshape_ad` | L-shape with the ad in the main area and a companion asset. | +| `lshape_content` | L-shape with content scaled into the main area. | +| `overlay` | Overlay ad positioned and sized over the content (`position`, `size`, `opacity`). | + +## Which breaks are included + +The Break Manifest reflects the breaks that are currently relevant for delivery, not the channel's entire break history. Selection is driven by two channel settings, [`dvrWindowMs` and `liveOffsetMs`](/ads/concepts/channels): + +- A cutoff time is computed as `now − liveOffsetMs − dvrWindowMs`. +- A break is included when its end (its `start` plus `duration`) is at or after that cutoff. This keeps breaks whose window still overlaps the DVR buffer, and keeps upcoming breaks, while dropping breaks that ended before the DVR look-back. +- Only breaks in the `READY` or `SIGNALED` [status](#break-lifecycle) are eligible. Breaks that are still `PREPARING` or `CUED`, or that have `ERROR`, are never exposed. + +`liveOffsetMs` lets a channel account for live latency by shifting the effective "now" backward, so breaks remain visible relative to the live playhead rather than raw server time. `dvrWindowMs` (default `300000`, i.e. 5 minutes) sets how far back the look-back extends. + +## Break lifecycle + +A break moves through a small set of statuses. Two of them are visible in the Break Manifest. + +| Status | In manifest | Meaning | +| ----------- | :---------: | --------------------------------------------------------------------------- | +| `PREPARING` | No | The break is being prepared (for example, awaiting a Google DAI pod asset). | +| `CUED` | No | The break is pre-decisioned and awaiting a confirmed start time. | +| `READY` | Yes | The break is ready to be delivered and is eligible for the manifest. | +| `SIGNALED` | Yes | The break has been served in the Break Manifest at least once. | +| `ERROR` | No | The break failed to prepare and is not delivered. | + +### READY → SIGNALED + +Serving the Break Manifest is what advances a break from `READY` to `SIGNALED`. When a poll includes one or more `READY` breaks, the service returns them in the response **and** transitions them to `SIGNALED` as a side effect of that read. A break that is already `SIGNALED` continues to be returned (while it remains within the DVR window) without any further status change. This makes the first appearance of a break in the manifest the moment it is considered signaled to players. + +## Player polling + +The OptiView Player consumes the Break Manifest by polling the endpoint: + +1. Fetch the Break Manifest for the channel. +2. Read `polling.idle` and `polling.active` (seconds) to set the next poll delay: poll at the `idle` cadence when no break is active, and at the `active` cadence while a break is active. +3. Merge each `break` onto the content timeline using `start` (interpreted with `timebase`) and `duration`, and render the `variant`. +4. Honor `controls` (`skipOffset`, `snapback`) and `resumeOffset` when playing the break and resuming content. + +Because the endpoint sets `Cache-Control` from the channel's active polling interval, a shared cache never serves a manifest older than the fastest advertised cadence. + +## Annotated examples + +### Wallclock channel + +For a channel created with `timebase: wallclock`, each break `start` is a UTC ISO 8601 timestamp. + +```json +{ + "version": "1.0.0", + "timebase": "wallclock", + "polling": { + "idle": 10, + "active": 1 + }, + "breaks": [ + { + "id": "break-1", + "start": "2026-07-16T12:30:00.000Z", // UTC wallclock start of the break + "duration": 30, // seconds + "resumeOffset": 0, // resume content at the break-in point + "controls": { + "skipOffset": 5, // skippable 5s in + "snapback": true // snap back to the break if the viewer seeks past it + }, + "variant": { + "format": "single", + "assets": [ + { + "id": "a1", + "type": "static", + "mediaType": "video", + "uri": "https://cdn.example.com/ads/ad.m3u8" + } + ] + } + } + ] +} +``` + +### PTS channel + +For a channel created with `timebase: pts`, each break `start` is a numeric presentation timestamp on the channel's media clock instead of a wallclock timestamp. The envelope and the rest of each break entry are otherwise identical. + +```json +{ + "version": "1.0.0", + "timebase": "pts", + "polling": { + "idle": 10, + "active": 1 + }, + "breaks": [ + { + "id": "break-9", + "start": 5400000, // numeric PTS start on the channel media clock + "duration": 30, // seconds + "variant": [ + { + "format": "single", // default full-screen experience + "assets": [{ "id": "a1", "type": "vast", "mediaType": "video", "uri": "https://ads.example.com/vast.xml" }] + }, + { + "format": "overlay", // alternative overlay experience + "assets": [{ "id": "a2", "type": "static", "mediaType": "image", "uri": "https://cdn.example.com/ads/overlay.png" }], + "position": { "top": 0.05, "right": 0.05 }, + "size": { "width": 0.3, "height": 0.2 }, + "opacity": 0.9 + } + ] + } + ] +} +``` + +When `breaks` is empty, the envelope is still returned with the channel `timebase` and `polling` values, and the player keeps polling at the `idle` cadence. + +## Related resources + +- [Channels](/ads/concepts/channels) — the timebase, polling policy (`pollingIdleSeconds`, `pollingActiveSeconds`), and delivery window (`dvrWindowMs`, `liveOffsetMs`) that shape the Break Manifest. +- [Scheduling breaks](/ads/how-to-guides/scheduling-breaks) — how breaks are created and signaled for a channel. +- [Getting started](/ads/getting-started/) — integrating the OptiView Player that polls the Break Manifest. diff --git a/ads/concepts/breaks.mdx b/ads/concepts/breaks.mdx new file mode 100644 index 000000000000..e7c3cc3e9b00 --- /dev/null +++ b/ads/concepts/breaks.mdx @@ -0,0 +1,610 @@ +--- +sidebar_position: 2 +sidebar_label: Breaks +--- + +# Breaks + +A break is the core monetization entity in OptiView Ads. It represents an ad opportunity scheduled on a channel and contains the timing, lifecycle state, playback controls, layout variants, and typed assets that a player or delivery service needs. + +Breaks are scoped to an organization and created for a channel. API calls authenticate with an API key and secret using HTTP Basic authentication and identify the organization with the `X-Org-ID` header. + +## Dashboard path + +In the OptiView Unified Dashboard, open **Ads → Channels**, open a channel, and select **Breaks**. The Breaks area is used to schedule, inspect, and delete breaks for the channel. + +## Break identity + +The compound identity of a Break is: + +```text +orgId + channelId + id +``` + +`id` is optional when creating a Break. If omitted, the API generates an identifier. The identity is scoped by both the organization and channel, so the same `id` can exist on different channels or in different organizations. + +| Field | Type | Description | +| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------- | +| `id` | string | Break identifier. Auto-generated if omitted on create. | +| `orgId` | string | Organization scope. Supplied by the authenticated `X-Org-ID` context. | +| `channelId` | string | Parent channel identifier. | +| `originId` | string, optional | Provenance for an automatically detected Break. Set internally by the detection worker and not client-settable. | +| `templateId` | string, optional | Identifier of the Template used to create the Break, if any. | +| `eventId` | string, optional | Identifier of the Event under which the Break was scheduled, if any. | + +The Break also stores internal Google DAI fields such as `podId`, `assetKey`, `networkCode`, `customAssetKey`, and `daiAssetKeys`, plus the lifecycle `status`, optional `errorMessage`, timebase-specific start fields, denormalized indexes, and the raw `data` payload. + +### Stored fields + +| Field | Type | Required/default behavior | +| ------------------- | ---------------------- | ------------------------------------------------------------- | +| `id` | string | Required; generated when omitted on create. | +| `orgId` | string | Required organization scope. | +| `channelId` | string | Required channel scope. | +| `eventId` | string, optional | Event association. | +| `templateId` | string, optional | Template association retained after creation. | +| `podId` | string, optional | Google DAI pod identifier after vendor-pod decisioning. | +| `status` | enum | `PREPARING`, `CUED`, `READY`, `SIGNALED`, or `ERROR`. | +| `originId` | string, optional | Internal provenance for an automatically detected Break. | +| `markerRuleId` | string, optional | Marker rule associated with automatic detection. | +| `markerDetectionId` | string, optional | Detection-history record associated with automatic detection. | +| `assetKey` | string, optional | Google DAI asset key. | +| `networkCode` | string, optional | Organization Google DAI network-code snapshot. | +| `customAssetKey` | string, optional | Channel Google DAI custom-asset-key snapshot. | +| `daiAssetKeys` | string array, optional | Deduplicated SSAI DAI asset-key snapshot. | +| `errorMessage` | string, optional | Failure reason when status is `ERROR`. | +| `timebase` | `wallclock` or `pts` | Required; copied from the channel. | +| `startWallclock` | Date, optional | Wallclock start for wallclock channels. | +| `startPts` | number, optional | Numeric PTS start for PTS channels. | +| `duration` | number | Required duration in seconds. | +| `variantFormats` | string array, optional | Denormalized variant-format index. | +| `assetTypes` | string array, optional | Denormalized asset-type index. | +| `vendors` | string array, optional | Denormalized vendor index. | +| `data` | object | Required raw `BreakData` payload. | +| `createdAt` | Date | Automatically managed creation timestamp. | +| `updatedAt` | Date | Automatically managed modification timestamp. | + +## Related resources + +| Resource | Relationship | +| ---------------------------------- | ---------------------------------------------------------------------------------- | +| [Channels](/ads/concepts/channels) | Parent resource. The channel's timebase determines which start field a Break uses. | +| Events | Time windows that group related Breaks. | +| Templates | Reusable Break definitions merged into a new Break at creation time. | +| Origins | Manifest sources whose detected markers can create Breaks. | +| Marker rules and detection history | Rules and audit records associated with automatically detected Breaks. | +| Integrations | Channel-level delivery integrations, including SSAI DAI cue fan-out. | + +## Scheduling + +### Timebase-dependent starts + +Every Break copies the timebase of its channel: + +| Channel timebase | API `start` value | Stored field | Requirement | +| ---------------- | ------------------------ | ---------------- | ------------------------------------------------------- | +| `wallclock` | ISO 8601 datetime string | `startWallclock` | Optional. Omitting it creates a CUED no-start workflow. | +| `pts` | Non-negative number | `startPts` | Required. | + +The API request field is named `start`; the service maps it to `startWallclock` or `startPts` according to the channel timebase. A PTS channel rejects a missing or non-numeric start. A wallclock channel accepts an omitted start, but a supplied start must be a valid ISO datetime. + +`duration` is required and is expressed in seconds. A scheduled Break cannot overlap another Break on the same channel. Wallclock overlap is evaluated using wallclock instants; PTS overlap is evaluated using PTS values. + +The service also requires a scheduled start to be sufficiently ahead of the current effective playhead. GAM vendor pod Breaks must additionally clear the EABN decisioning margin. + +### Create a Break directly + +Create a Break by supplying its payload and, when required, its `start`: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "id": "break-2026-001", + "start": "2026-07-16T12:15:00.000Z", + "duration": 120, + "resumeOffset": 0, + "controls": { + "skipOffset": 30, + "snapback": true + }, + "variant": { + "format": "single", + "assets": [ + { + "id": "asset-001", + "type": "static", + "mediaType": "video", + "mimeType": "video/mp4", + "uri": "https://cdn.example.com/ads/asset-001.m3u8" + } + ] + } + }' +``` + +### Create from a Template + +Supply `templateId` to use a Template as the base. At creation time, the service merges the Template's stored `data` and duration with the request overrides, validates the result, and snapshots the resolved payload into the new Break's `data`. Later Template edits do not change an existing Break. + +Supported creation overrides are: + +- `id` +- `eventId` +- `start` +- `duration` +- `variant` +- `assetParameters` + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "templateId": "template-sports-spot", + "id": "break-from-template-001", + "start": "2026-07-16T12:20:00.000Z", + "eventId": "event-2026-final", + "assetParameters": { + "airingId": "airing-001" + } + }' +``` + +### Create under an Event + +An `eventId` must identify an Event belonging to the same organization and channel. For wallclock channels, the Break must satisfy all of these conditions: + +- `start >= event.startDate` +- `start <= event.endDate` +- `start + duration <= event.endDate` + +PTS channels still require the Event to exist on the same organization and channel, but the service does not compare a numeric PTS start to the Event's wallclock window. + +### Scheduling constraints + +The API rejects starts that are too close to, or behind, the effective playhead. It also rejects any overlap with an existing Break on the channel. These checks apply to direct and Template-based creation. + +## Lifecycle + +The exact Break status values are: + +```text +PREPARING +CUED +READY +SIGNALED +ERROR +``` + +`errorMessage` contains the human-readable reason when a Break is moved to `ERROR`. + +### Initial status + +| Break kind | Start supplied? | Initial status | +| ---------------------------------------------------------------- | --------------- | -------------- | +| GAM vendor pod (`vendor: "gam"`, `vendorParameters.type: "pod"`) | Either | `PREPARING` | +| Non-vendor Break | Yes | `READY` | +| Non-vendor Break on a wallclock channel | No | `CUED` | + +![Break lifecycle diagram](../assets/img/breaks/break-lifecycle.svg) + +### Status transitions and owners + +| Transition | Owner | Behavior | +| ------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `PREPARING → CUED` | Worker / EABN | After Google DAI decisioning, a Break without a timebase-appropriate start becomes CUED. | +| `PREPARING → READY` | Worker / EABN | After Google DAI decisioning, a Break with a timebase-appropriate start becomes READY. | +| `PREPARING → ERROR` | Worker / health worker | A missed unsignaled Break is failed with `Break passed its scheduling window before it could be signaled`. | +| `CUED → READY` | API punch | Punching assigns `startWallclock` and makes the Break eligible for delivery. | +| `READY → SIGNALED` | Manifest service | The Break Manifest includes READY and SIGNALED Breaks, then changes returned READY Breaks to SIGNALED. | +| `READY → SIGNALED` | Proxy | After injecting HLS cues, the proxy changes the injected READY Breaks to SIGNALED. The update is scoped to READY and is idempotent. | + +The worker can also reset a superseded active Google Break from `READY` or `SIGNALED` back to `PREPARING` when it is still outside the decision margin. + +## Cue and punch workflow + +Vendor pod Breaks can be prepared before their exact start is known: + +1. Create a GAM vendor pod Break without a start on a wallclock channel. It starts in `PREPARING`. +2. The worker/EABN service pre-decides the Break with Google DAI. +3. After decisioning, the Break receives a `podId` and becomes `CUED`. +4. Punch the Break when it should fire. Punching sets `startWallclock` and changes the status to `READY`. + +Only wallclock channels support punch. A GAM CUED Break must have a `podId` from EABN decisioning before it can be punched. The application allows only one no-start Break in `PREPARING` or `CUED` per channel; creating another one fails. + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks/gam-cued-001/punch' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "start": "2026-07-16T12:25:00.000Z" + }' +``` + +If the body is omitted, the punch uses the current time. A requested past time is clamped to now. + +## Break payload (`data`) + +The stored `data` object has this shape: + +```ts +type BreakData = { + duration: number; // required, seconds, >= 0 + resumeOffset?: number; // seconds, >= 0 + controls?: { + skipOffset?: number; // seconds, >= 0 + snapback?: boolean; + }; + variant: BreakVariant | BreakVariant[]; // one object or a non-empty array +}; +``` + +| Field | Type | Description | +| --------------------- | ----------------- | -------------------------------------------------------------- | +| `duration` | number | Required Break duration in seconds. | +| `resumeOffset` | number, optional | Resume offset in seconds. | +| `controls.skipOffset` | number, optional | Minimum elapsed time before skipping is allowed. | +| `controls.snapback` | boolean, optional | Enables snapback behavior. | +| `variant` | object or array | One layout variant, or a non-empty array of targeted variants. | + +## Layouts and variants + +This is the canonical V2 layout and variant reference. Templates use the same payload model and should refer to this section rather than duplicate the layout definitions. + +![OptiView Ads format overview](../assets/img/ads_formats.svg) + +### Asset model + +Every asset has these common fields: + +```ts +{ + id: string; + mediaType: "video" | "image"; + mimeType?: string; + duration?: number; + interaction?: { + clickThrough?: string; + }; +} +``` + +`id` is generated as a UUID when omitted. Asset `type` is one of: + +| `type` | Fields | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `static` | `uri`: a URL string or an array of `{ value, targeting? }` objects. | +| `vast` | `uri`: a URL string or an array of `{ value, targeting? }` objects. | +| `vendor` | `vendor: "gam"`, `vendorParameters`, optional `assetParameters`, and `uri`. GAM vendor parameters require `type: "pod"`. The default `uri` is `"placeholder"`. | + +For URI arrays, each entry can include optional device targeting: + +```ts +{ + value: string; + targeting?: { + deviceType?: "desktop" | "tablet" | "mobile" | "tv"; + }; +} +``` + +### `single` + +![Single format](../assets/img/breaks/format-single.svg) + +The `single` variant contains a non-empty plain `assets` array: + +```ts +{ + format: "single"; + targeting?: { deviceType?: "desktop" | "tablet" | "mobile" | "tv" }; + assets: Asset[]; +} +``` + +Use a full-screen creative. Optimize the asset size for the player and supply companion imagery separately when the player experience requires it. + +### `double` + +![Double format](../assets/img/breaks/format-double.svg) + +The `double` variant contains a non-empty array in which every entry has a primary asset and a `companion` asset: + +```ts +{ + format: "double"; + targeting?: { deviceType?: "desktop" | "tablet" | "mobile" | "tv" }; + assets: Array; +} +``` + +Use 16:9 companion imagery where possible. The double box is unsupported on many smart TVs; provide a single-format fallback for those devices. + +### `lshape_ad` + +![L-shape ad format](../assets/img/breaks/format-lshape-ad.svg) + +The `lshape_ad` variant uses the same companion-bearing asset shape as `double`: + +```ts +{ + format: "lshape_ad"; + targeting?: { deviceType?: "desktop" | "tablet" | "mobile" | "tv" }; + assets: Array; +} +``` + +The ad occupies the smaller window and the companion asset supplies the remaining backdrop. Use 16:9 companion imagery and optimize image dimensions for the target player. + +### `lshape_content` + +![L-shape content format](../assets/img/breaks/format-lshape-content.svg) + +The `lshape_content` variant uses a non-empty plain asset array: + +```ts +{ + format: "lshape_content"; + targeting?: { deviceType?: "desktop" | "tablet" | "mobile" | "tv" }; + assets: Asset[]; +} +``` + +The live content occupies the smaller window and the remaining area is supplied by the layout's companion/backdrop treatment. + +### `overlay` + +![Overlay format](../assets/img/breaks/format-overlay.svg) + +The `overlay` variant uses a non-empty plain asset array plus required position and size objects: + +```ts +{ + format: "overlay"; + targeting?: { deviceType?: "desktop" | "tablet" | "mobile" | "tv" }; + assets: Asset[]; + position: { + top?: number; + bottom?: number; + left?: number; + right?: number; + }; + size: { + width: number; + height: number; + }; + opacity?: number; +} +``` + +`position` requires at least one of `top` or `bottom` and at least one of `left` or `right`. All position and size values are fractions from `0` through `1`, not percentages. `opacity`, when supplied, is also a fraction from `0` through `1`. + +### Multiple variants and device targeting + +Set `variant` to an array when one Break contains multiple layouts for different devices. Each variant can have an optional `targeting.deviceType` value: + +```json +{ + "duration": 30, + "variant": [ + { + "format": "single", + "targeting": { + "deviceType": "mobile" + }, + "assets": [ + { + "type": "static", + "mediaType": "video", + "uri": "https://cdn.example.com/ads/mobile.m3u8" + } + ] + }, + { + "format": "single", + "targeting": { + "deviceType": "tv" + }, + "assets": [ + { + "type": "static", + "mediaType": "video", + "uri": "https://cdn.example.com/ads/tv.m3u8" + } + ] + }, + { + "format": "double", + "targeting": { + "deviceType": "desktop" + }, + "assets": [ + { + "type": "static", + "mediaType": "video", + "uri": "https://cdn.example.com/ads/desktop.m3u8", + "companion": { + "type": "static", + "mediaType": "image", + "uri": "https://cdn.example.com/ads/desktop-companion.jpg" + } + } + ] + }, + { + "format": "double", + "targeting": { + "deviceType": "tablet" + }, + "assets": [ + { + "type": "static", + "mediaType": "video", + "uri": "https://cdn.example.com/ads/tablet.m3u8", + "companion": { + "type": "static", + "mediaType": "image", + "uri": "https://cdn.example.com/ads/tablet-companion.jpg" + } + } + ] + } + ] +} +``` + +## Delivery overview + +### Break Manifest polling + +The Manifest Service returns `READY` and `SIGNALED` Breaks that remain within the channel's DVR window. It changes returned `READY` Breaks to `SIGNALED` and emits the timebase-specific start, duration, controls, resume offset, and variant data. Players poll the Manifest according to the channel's advertised idle and active polling intervals. + +### SSAI cue injection + +For wallclock GAM pod Breaks on channels with an SSAI DAI integration, the Proxy injects HLS `EXT-X-DATERANGE` OUT and IN cues into the media playlist. After the cues are written, it changes the injected Breaks from `READY` to `SIGNALED`. + +`PREPARING`, `CUED`, and `ERROR` Breaks are not delivered through either path. + +## API usage + +All examples use the same organization-scoped Basic authentication as the Channels API. + +### Create directly + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "id": "break-api-001", + "start": "2026-07-16T12:30:00.000Z", + "duration": 60, + "controls": { + "skipOffset": 10, + "snapback": false + }, + "variant": { + "format": "single", + "assets": [ + { + "type": "vast", + "mediaType": "video", + "uri": "https://ads.example.com/vast/creative-001.xml" + } + ] + } + }' +``` + +### Create from a Template + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "templateId": "template-sports-spot", + "start": "2026-07-16T12:31:00.000Z", + "duration": 45 + }' +``` + +### List Breaks + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/breaks?page=1&pageSize=20' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +`page` defaults to `1`; `pageSize` defaults to `20` and has a maximum of `100`. Lists also accept the optional RSQL `filter` and `sort` parameters. + +Filterable fields are: + +```text +wallclock, assetType, format, eventId, templateId, duration, status, originId +``` + +Sortable fields are: + +```text +wallclock, duration, status, createdAt +``` + +Filter by one status: + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/breaks?filter=status==READY' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +Filter by either READY or SIGNALED: + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/breaks?filter=status=in=(READY,SIGNALED)' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +The `/active` and `/current` variants are also available: + +```text +GET /api/v1/channels/{channelId}/breaks/active +GET /api/v1/channels/{channelId}/breaks/current +``` + +### Get one Break + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/breaks/break-api-001' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +### Delete one Break + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/breaks/break-api-001' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +### Bulk delete Breaks + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "ids": ["break-api-001", "break-api-002"] + }' +``` + +### Punch a CUED Break + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks/gam-cued-001/punch' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "start": "2026-07-16T12:35:00.000Z" + }' +``` + +## See also + +- [Channels](/ads/concepts/channels) — channel timebases, polling policy, origins, marker detection, and delivery integrations. +- Templates — reusable Break definitions and Template-based scheduling. +- Events — event windows and event-scoped Breaks. +- Marker Detection — automatic marker evaluation and Break provenance. +- Vendors and Google DAI — vendor pod decisioning and delivery. diff --git a/ads/concepts/channels.mdx b/ads/concepts/channels.mdx new file mode 100644 index 000000000000..e08e65744ee2 --- /dev/null +++ b/ads/concepts/channels.mdx @@ -0,0 +1,112 @@ +--- +sidebar_position: 1 +sidebar_label: Channels +--- + +# Channels + +A channel represents one live stream in OptiView Ads. It is the place where you decide how ads behave for that stream: how break start times are interpreted, which breaks are announced to players and when, and whether ad markers in the stream are detected automatically. + +Everything else in OptiView Ads hangs off a channel. Origins, marker rules, breaks, events, templates, and integrations are all created for — or looked up through — a channel. + +Channels are scoped to an organization. In the OptiView Unified Dashboard, open **Ads → Channels** to create and manage them. + +## Channel identity + +Every channel has a customer-facing `id`. Together with the organization, it forms the unique identity of the channel: + +```text +organizationId + channelId +``` + +Use stable channel IDs that match your operational names, such as `sports-main` or `news-east`. If you omit `id` on creation, one is generated for you. + +## Related resources + +A channel is the parent or lookup point for the rest of the OptiView Ads model: + +| Resource | Relationship | +| ----------------- | ---------------------------------------------------------------------------- | +| Origins | Manifest URLs monitored for ad markers. A channel can have multiple origins. | +| Marker rules | Rules that turn detected markers into breaks. | +| Detection history | Audit records for marker detection decisions on the channel. | +| Breaks | Scheduled or detected ad opportunities for the channel. | +| Events | Time windows that group related breaks. | +| Templates | Reusable break presets that can be scheduled on the channel. | +| Integrations | Channel-level delivery integrations, such as SSAI DAI cue fan-out. | + +In the Dashboard, opening a channel gives you access to each of these areas: + +| Area | Use it for | +| ----------------- | ------------------------------------------------------ | +| Overview | View channel settings and player/origin quick actions. | +| Breaks | Schedule, inspect, and delete breaks for the channel. | +| Events | Manage event windows and event-scoped breaks. | +| Origins | Add, enable, disable, and prioritize manifest origins. | +| Break Detection | Configure marker rules and review detection history. | +| Break Integration | Manage channel-level delivery integrations. | + +## Timing and delivery settings + +These channel settings determine how break start times are interpreted and how breaks are delivered to players through the [Break Manifest](./break-manifest.mdx). + +### Timebase + +The `timebase` determines which timeline break start times are expressed on: + +- **`wallclock`** — breaks are scheduled with UTC wallclock timestamps (`startWallclock`). Use this when the stream carries wallclock timing, typically from HLS `EXT-X-PROGRAM-DATE-TIME` tags. +- **`pts`** — breaks are scheduled against the encoder's presentation timestamp timeline (`startPts`). The player needs to retrieve the PTS value from the media segments to know where it is on that timeline. Use this when your workflow schedules breaks against encoder PTS values rather than wallclock time. + +Choose the timebase when creating the channel; all breaks on the channel use the same timebase. + +### DVR window + +`dvrWindowMs` describes how far behind live a viewer can be while watching the channel — the time-shifted (DVR) window of the stream. + +It directly impacts the Break Manifest: a break is included in the manifest as long as it is still relevant for a viewer anywhere inside the DVR window. With a larger DVR window, breaks remain in the manifest for longer so that time-shifted viewers still receive them; with a small window, only breaks near the live edge are returned to the player. + +### Live offset + +`liveOffsetMs` describes how far the player's playhead is behind live. Players never play exactly at the live edge — they buffer a few seconds behind it. + +OptiView Ads uses this offset when evaluating wallclock break start times: instead of comparing against raw server time, break timing is evaluated against the position viewers are actually watching, so breaks activate when the playhead reaches them. + +### Polling intervals + +The Break Manifest response tells players how often to poll for updates. Two channel settings control this cadence: + +- **`pollingIdleSeconds`** — the polling interval advertised when no break is active. A slower cadence keeps request load low while nothing is happening. +- **`pollingActiveSeconds`** — the polling interval advertised while a break is active (also used for active manifest caching). A faster cadence lets players catch break transitions quickly. + +### Ad prefetch window + +`adPrefetchMs` defines how far ahead of a break's start time the break is announced to the player through the Break Manifest. + +With the default of `10000` (10 seconds), a break whose start time is within the next 10 seconds is included in the manifest. This lead time gives players room to prepare and prefetch the ad content before the break actually starts. + +## Google Ad Manager (pod serving) integration + +`customAssetKey` connects the channel to Google Ad Manager for server-guided pod serving. It is the Google DAI custom asset key that identifies this live stream in Google Ad Manager, and it must be unique within your organization. + +Set it when the channel uses Google DAI pod serving; leave it unset otherwise. See the [Google vendor guide](../vendors/google.mdx) for the full Google Ad Manager setup. + +## Marker detection + +A channel can automatically detect ad markers (such as SCTE-35 cues or `EXT-X-DATERANGE` tags) in its origin manifests and turn them into breaks. + +Detection is controlled per channel with the read-only `detectionEnabled` state and dedicated enable/disable actions (in the Dashboard under **Break Detection**). When detection is enabled, the enabled origins of the channel are polled in priority order and the first online origin is used for marker evaluation. [Marker rules](./marker-detection.mdx) decide whether a detected marker creates a break, and detection history records every decision. + +## Configuration reference + +| Field | Type | Default | Description | +| ---------------------- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------- | +| `timebase` | `wallclock` or `pts` | Required | Timeline used for break start times. See [Timebase](#timebase). | +| `dvrWindowMs` | integer | `300000` | Time-shifted viewing window. Determines which breaks the Break Manifest returns. See [DVR window](#dvr-window). | +| `liveOffsetMs` | integer | `0` | How far the player playhead is behind live. See [Live offset](#live-offset). | +| `pollingIdleSeconds` | integer | `10` | Break Manifest polling interval when no break is active. | +| `pollingActiveSeconds` | integer | `1` | Break Manifest polling interval during an active break. | +| `adPrefetchMs` | integer | `10000` | Lead time for announcing upcoming breaks to players. See [Ad prefetch window](#ad-prefetch-window). | +| `customAssetKey` | string | none | Google DAI custom asset key for pod serving. Unique within the organization. | +| `detectionEnabled` | boolean | `false` | Read-only state showing whether automatic marker detection is enabled. | + +For creating, updating, listing, and deleting channels programmatically, see the Ads API reference. diff --git a/ads/concepts/events.mdx b/ads/concepts/events.mdx new file mode 100644 index 000000000000..09b862dd7e8e --- /dev/null +++ b/ads/concepts/events.mdx @@ -0,0 +1,268 @@ +--- +sidebar_position: 4 +sidebar_label: Events +--- + +# Events + +An event is a channel-scoped time window that groups the ad breaks belonging to one scheduled occurrence, such as a live game, a show, or a tournament. It gives you a single handle for the breaks around that occurrence: the breaks share the event's window, and deleting the event removes them together. + +Events also anchor the operational cue/punch workflow. Ahead of a live occurrence you prepare vendor pod breaks under the event without a start time, and during the broadcast you fire them at the exact moment with the punch endpoint. + +Events are scoped to an organization and a channel. API calls identify the organization with the `X-Org-ID` header and authenticate with an API key and secret using HTTP Basic authentication. + +## Dashboard path + +In the OptiView Unified Dashboard, open **Ads → Channels**, open a channel, then select **Events** from the channel navigation. From there you can create, edit, and delete events, and inspect the breaks scheduled under each event. + +## Event identity + +Every event has an `id`. The API stores it together with the organization ID and the parent channel ID, so the unique identity is: + +```text +organizationId + channelId + eventId +``` + +Because the identity includes the channel, the same `id` can exist under different channels. Use stable, descriptive event IDs that match your operational names, such as `finals-2026` or `week-1-home`. If you omit `id` on creation, the API generates one. + +## Time window + +An event is defined by a `startDate` and an `endDate`, both UTC ISO 8601 timestamps. `startDate` must be before `endDate`. + +The window is enforced on the breaks scheduled under the event. When you create a break with an explicit start on a `wallclock` channel and attach it to an event, the API validates that the **entire** break interval fits inside the window: + +- The break start must be at or after the event `startDate`. +- The break end (`start` + `duration`) must be at or before the event `endDate`. + +If either check fails, the create request is rejected: + +| Condition | Response | +| --------------------------------- | ----------------------------------------------------------------------------- | +| Break start is outside the window | `Break start must be within the event's date range ( - )` | +| Break end is outside the window | `Break end time (start + duration) must be within the event's date range (…)` | + +The window validation applies to `wallclock` channels. On `pts` channels the event must still exist, but the break is not range-checked against the event dates. See [Channels](./channels.mdx) for the timebase model. + +A break created **without** a start time (a cued break, see below) is not range-checked at creation, because its start is not known yet. Its start is set when you punch it. + +## Relationship to breaks + +A break is attached to an event by setting `eventId` to the event's `id` on the break. `eventId` is optional: a break can exist on the channel without belonging to any event. + +:::warning Deleting an event deletes its breaks +Deleting an event cascades to every break whose `eventId` matches it. The event and its member breaks are removed together in a single transaction. Bulk-deleting events removes the breaks of all deleted events. There is no confirmation step in the API — delete an event only after confirming that none of its breaks are still needed. +::: + +To list only the breaks that belong to an event, use the event's breaks endpoint (see [API usage](#api-usage)). + +## Cue / punch workflow during an event + +For a live occurrence you usually do not know the exact break times in advance, but you want the ad decision ready so the break can fire instantly. Events are where this "prepare ahead, fire live" workflow lives. The full break state machine and the Google DAI (vendor pod) prerequisites are documented in the Breaks and Vendors / Google sections; the flow below focuses on running an event. + +### Ahead of the event: prepare cued breaks + +Create the vendor pod breaks under the event **without a `start`**. A vendor pod break with no start begins in `PREPARING`: OptiView Ads asks Google DAI to pre-decision the pod. Once the pod is decisioned, the break transitions to `CUED` and is ready to fire. + +A channel can hold **only one cued break at a time**. While a no-start break is `PREPARING` or `CUED` on a channel, creating another no-start break on the same channel is rejected: + +```text +Channel already has a CUED break +``` + +Punch (or delete) the outstanding break before cueing the next one. + +### During the event: punch the cued break + +When the moment arrives, fire the cued break with the punch endpoint: + +```text +POST /api/v1/channels/:channelId/breaks/:breakId/punch +``` + +The request body is optional. It may contain a single `start` (UTC ISO 8601). If `start` is omitted it defaults to now, and a `start` in the past is clamped to now. A successful punch sets the break's start and transitions it from `CUED` to `READY`, after which it is delivered. + +Punching has these constraints: + +- **Wallclock only.** The channel must use the `wallclock` timebase. Punching a `pts` channel is rejected with `Only channels with a 'wallclock' timebase are allowed to punch breaks.` +- **Must be cued.** The break must be in `CUED` status; otherwise the request fails with `Break '' is not in CUED status`. +- **Pod must be decisioned.** For a vendor pod break, the Google DAI pod decision must have completed (the break must have left `PREPARING`); otherwise the request fails with `Ad break '' is not yet decisioned by EABN`. + +Because a punch clamps the start to the current time, punch a cued break only while the event is in progress. This keeps the break's start inside the event's `startDate`/`endDate` window. + +### Worked example: half-time break in a live game + +1. Create the event for the game with a window that covers kickoff through the final whistle. +2. Ahead of kickoff, create a vendor pod break under the event with no `start`. It enters `PREPARING`, then `CUED` once Google DAI has pre-decisioned the pod. +3. At half-time, punch the break with no body. Its start is set to now and it transitions to `READY`, so the pod is delivered immediately. +4. To prepare the next in-game break, first punch or delete the current cued break, then cue the next one — a channel holds only one cued break at a time. + +## Relationship to templates + +Templates can be linked to one or more events through their `eventIds` array, so a reusable break preset can be surfaced for quick scheduling under those events. Listing templates for an event returns every template whose `eventIds` contains the event's `id`. + +## Configuration reference + +| Field | Type | Required | Description | +| ------------- | --------------- | -------- | ----------------------------------------------------------- | +| `id` | string | No | Customer-facing event ID. Generated if omitted on creation. | +| `name` | string | Yes | Human-readable event name. Must be non-empty. | +| `description` | string | No | Optional free-text description. | +| `startDate` | ISO 8601 string | Yes | Start of the event window. Must be before `endDate`. | +| `endDate` | ISO 8601 string | Yes | End of the event window. | + +The event response returns `id`, `name`, `description`, `startDate`, `endDate`, and `createdAt`. The organization and channel IDs are taken from the request context and are not part of the response body. + +## API usage + +Events live under a channel. Replace `sports-main` with your channel ID. + +### Create an event + +Dashboard: open the channel, then **Events → New**. + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/events' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "id": "finals-2026", + "name": "Finals 2026", + "description": "Championship final", + "startDate": "2026-07-20T18:00:00.000Z", + "endDate": "2026-07-20T22:00:00.000Z" + }' +``` + +Example response: + +```json +{ + "id": "finals-2026", + "name": "Finals 2026", + "description": "Championship final", + "startDate": "2026-07-20T18:00:00.000Z", + "endDate": "2026-07-20T22:00:00.000Z", + "createdAt": "2026-07-16T12:00:00.000Z" +} +``` + +### Get an event + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/events/finals-2026' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +### Update an event + +`id` cannot be changed. When you send `startDate` or `endDate`, the resulting window must still keep `startDate` before `endDate`. + +```bash +curl -X PATCH 'https://ads.example.com/api/v1/channels/sports-main/events/finals-2026' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Finals 2026 (delayed)", + "endDate": "2026-07-20T23:00:00.000Z" + }' +``` + +### List events + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/events?page=1&pageSize=20&sort=-createdAt' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +List endpoints share the same pagination shape. Events can be filtered by `name`, `description`, `startDate`, and `endDate`, and sorted by `name`, `description`, `startDate`, `endDate`, or `createdAt`. + +| Query parameter | Default | Description | +| --------------- | ------------ | -------------------------------------------------------------------------- | +| `page` | `1` | Page number. | +| `pageSize` | `20` | Items per page. Maximum `100`. | +| `filter` | none | Optional RSQL filter expression. | +| `sort` | `-createdAt` | Comma-separated sort fields. Prefix a field with `-` for descending order. | + +### List breaks for an event + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/events/finals-2026/breaks?pageSize=50' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +### Delete an event + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/events/finals-2026' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +This also deletes every break attached to the event. To delete several events (and their breaks) at once, send their IDs to the collection endpoint: + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/events' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ "ids": ["finals-2026", "semifinal-2026"] }' +``` + +### Cue and punch a break in an event context + +Cue a vendor pod break under the event by omitting `start`: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "id": "halftime-1", + "eventId": "finals-2026", + "duration": 90, + "variant": { + "format": "single", + "assets": [ + { + "type": "vendor", + "vendor": "gam", + "mediaType": "video", + "vendorParameters": { "type": "pod" } + } + ] + } + }' +``` + +At the right moment, punch it. With no body the start defaults to now: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks/halftime-1/punch' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +To punch with an explicit start (a past start is clamped to now): + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks/halftime-1/punch' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ "start": "2026-07-20T20:00:00.000Z" }' +``` + +## Related resources + +| Resource | Relationship | +| ---------------- | ----------------------------------------------------------------------------------------------- | +| Channels | The parent of an event. An event always belongs to one channel. See [Channels](./channels.mdx). | +| Breaks | Attached to an event via `eventId`; the Breaks section documents the full status state machine. | +| Templates | Linked to events via `eventIds` for quick scheduling. | +| Vendors / Google | Provide the pod pre-decisioning that moves a cued vendor pod break from `PREPARING` to `CUED`. | diff --git a/ads/concepts/marker-detection.mdx b/ads/concepts/marker-detection.mdx new file mode 100644 index 000000000000..5ae87abbbff6 --- /dev/null +++ b/ads/concepts/marker-detection.mdx @@ -0,0 +1,299 @@ +--- +sidebar_position: 6 +sidebar_label: Break Detection +--- + +# Break Detection + +Automatic marker detection turns ad markers found in an origin manifest into breaks by applying marker rules. Detection runs per channel when it is enabled. V2 detection currently supports HLS manifests only. See [Origins](./origins.mdx) for origin selection, priority ordering, and first-online behavior. + +Channels are scoped to an organization. API calls identify the organization with the `X-Org-ID` header and authenticate with an API key and secret using HTTP Basic authentication. + +## Dashboard path + +Open the channel and select **Break Detection**. Use this area to configure marker rules and review **Detection history**. + +Marker rules can be toggled with **Enable marker rule** and **Disable marker rule**. These Dashboard actions use the marker-rule update endpoint with the `enabled` field; there are no dedicated marker-rule enable or disable endpoints. + +## Detection lifecycle + +`detectionEnabled` is read-only on channel create and update requests. Toggle automatic detection with the dedicated channel actions: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/detection/enable' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/detection/disable' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +When detection is enabled, a scheduler polls the channel's enabled origins in priority order. The first online origin is selected for the cycle. The worker parses its markers, evaluates enabled marker rules, creates breaks for matching markers, and records the result in Detection history. + +## Supported markers + +V2 marker detection supports HLS only. It recognizes two marker kinds: + +| Marker rule type | HLS marker | Detection behavior | +| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------- | +| `CUE` | `#EXT-X-CUE-OUT` | Parses a marker start and optional duration. `CUE-IN` and `CUE-SPAN` are ignored. | +| `DATERANGE` | `#EXT-X-DATERANGE` | Requires a valid `START-DATE`. Duration comes from `DURATION`, `PLANNED-DURATION`, or `END-DATE`. | + +`DATERANGE` is not limited to Apple interstitials. Any `#EXT-X-DATERANGE` tag with a valid start is considered and can be matched by its attributes. + +## Marker rules + +A marker rule turns a detected marker into a break created from a template. The rule's `type` must match the marker kind, and every configured condition must match the marker attributes. Attribute keys are compared case-insensitively. + +### Configuration reference + +| Field | Type | Default | Description | +| ----------------- | -------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `streamType` | enum | Required | Currently only `HLS` is supported. | +| `type` | enum: `CUE` or `DATERANGE` | Required | Marker kind this rule matches. The value must be valid for the selected `streamType`. | +| `conditions` | object (map of string to string) | Required | Attribute key/value pairs that must all match on the marker for the rule to fire. An empty object matches any marker of that type. | +| `templateId` | string | Required | Non-empty ID of the break template to instantiate. The template must exist and be available to the channel. | +| `assetParameters` | object (map of string to string) | none | Optional parameters merged into the created break body, such as ad-targeting parameters passed downstream. | +| `enabled` | boolean | `true` | Whether the rule participates in detection. | + +For example, this rule matches DATERANGE markers whose `CLASS` attribute is `com.example.ad`: + +```json +{ + "streamType": "HLS", + "type": "DATERANGE", + "conditions": { "CLASS": "com.example.ad" }, + "templateId": "preroll-30s", + "assetParameters": { "adType": "midroll" }, + "enabled": true +} +``` + +There is no dedicated marker-rule enable or disable endpoint. The Dashboard **Enable marker rule** / **Disable marker rule** actions map to a normal update: + +```bash +curl -X PATCH 'https://ads.example.com/api/v1/channels/sports-main/markerRules/rule-123' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ "enabled": false }' +``` + +## Marker rule endpoints + +All marker-rule endpoints are scoped to a channel: + +| Operation | Method | Path | +| ----------- | -------- | ------------------------------------------------------- | +| List | `GET` | `/api/v1/channels/:channelId/markerRules` | +| Get | `GET` | `/api/v1/channels/:channelId/markerRules/:markerRuleId` | +| Create | `POST` | `/api/v1/channels/:channelId/markerRules` | +| Update | `PATCH` | `/api/v1/channels/:channelId/markerRules/:markerRuleId` | +| Delete | `DELETE` | `/api/v1/channels/:channelId/markerRules/:markerRuleId` | +| Bulk delete | `DELETE` | `/api/v1/channels/:channelId/markerRules` | + +### Create a marker rule + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/markerRules' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "streamType": "HLS", + "type": "DATERANGE", + "conditions": { "CLASS": "com.example.ad" }, + "templateId": "preroll-30s", + "assetParameters": { "adType": "midroll" }, + "enabled": true + }' +``` + +Example response: + +```json +{ + "id": "rule-123", + "streamType": "HLS", + "type": "DATERANGE", + "conditions": { "CLASS": "com.example.ad" }, + "templateId": "preroll-30s", + "assetParameters": { "adType": "midroll" }, + "enabled": true, + "createdAt": "2026-07-16T12:00:00.000Z" +} +``` + +### Update a marker rule + +Use the same endpoint to change rule configuration or enable/disable participation: + +```bash +curl -X PATCH 'https://ads.example.com/api/v1/channels/sports-main/markerRules/rule-123' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "conditions": { "CLASS": "com.example.ad", "X-CAMPAIGN": "sports" }, + "enabled": true + }' +``` + +### List marker rules + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/markerRules?page=1&pageSize=20&sort=-createdAt' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +List endpoints use the shared pagination shape: + +| Query parameter | Default | Description | +| --------------- | ------------ | -------------------------------------------------------------------------- | +| `page` | `1` | Page number. | +| `pageSize` | `20` | Items per page. Maximum `100`. | +| `filter` | none | Optional RSQL filter expression. | +| `sort` | `-createdAt` | Comma-separated sort fields. Prefix a field with `-` for descending order. | + +### Bulk delete marker rules + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/markerRules' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ "ids": ["rule-123", "rule-456"] }' +``` + +## Detection history + +Detection history is the audit trail of what automatic detection decided for each marker. + +| Operation | Method | Path | +| --------- | ------ | ------------------------------------------------------------------ | +| List | `GET` | `/api/v1/channels/:channelId/detection/history` | +| Get | `GET` | `/api/v1/channels/:channelId/detection/history/:markerDetectionId` | + +### List detection history + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/detection/history?page=1&pageSize=20&sort=-createdAt' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +### Detection history response fields + +| Field | Type | Description | +| -------------- | ------ | -------------------------------------------- | +| `id` | string | Detection-history record ID. | +| `originId` | string | Origin that supplied the marker. | +| `markerRuleId` | string | Present when an enabled marker rule matched. | +| `breakId` | string | Present when a break was created. | +| `action` | enum | `CREATED`, `SKIPPED`, or `FAILED`. | +| `marker` | string | The raw manifest tag line. | +| `reason` | string | Optional machine-readable reason. | +| `createdAt` | string | Creation timestamp. | + +Example response row: + +```json +{ + "id": "detection-789", + "originId": "origin-123", + "markerRuleId": "rule-123", + "breakId": "break-456", + "action": "CREATED", + "marker": "#EXT-X-DATERANGE:ID=\"ad-1\",CLASS=\"com.example.ad\",START-DATE=\"2026-07-16T12:00:00.000Z\",DURATION=30", + "createdAt": "2026-07-16T12:00:01.000Z" +} +``` + +### Action values + +| Action | Meaning | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CREATED` | A rule matched and a break was scheduled. `markerRuleId` and `breakId` are set. | +| `SKIPPED` | No fault: the marker was ineligible because it was unparseable or had no resolvable start; no rule matched; no rules were configured; or an expected scheduling condition prevented creation. | +| `FAILED` | An eligible, rule-matched marker could not be scheduled for an unexpected reason such as misconfiguration, invalid data, or infrastructure failure. | + +Common `reason` values include: + +- `NO_RULES_CONFIGURED` +- `NO_RULE_MATCHED` +- `MARKER_MISSING_START` +- `MARKER_MALFORMED` +- Scheduling-rejection reasons such as `BREAK_START_IN_PAST`, `DECISIONING_MARGIN`, and `BREAK_OVERLAP` + +History is deduplicated per channel. Repeated polling of the same marker, including seeing it on another origin, does not create duplicate rows. + +## Troubleshooting + +| Symptom | Checks | +| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| No breaks are created. | Is detection enabled on the channel? Is there at least one enabled HLS origin? Is the origin reachable and returning a parseable manifest? Is there an enabled marker rule whose `type` and `conditions` match the marker? Does the rule's template exist? | +| History contains `SKIPPED` with `NO_RULES_CONFIGURED`. | Create and enable a marker rule for the channel. | +| History contains `SKIPPED` with `NO_RULE_MATCHED`. | Check the rule `type` and all `conditions` against the marker attributes. Attribute keys are matched case-insensitively, but values must match. | +| DASH or HESP origin is not producing breaks. | DASH and HESP origins are accepted by the API but skipped by automatic detection. Use an enabled HLS origin. | +| History contains `SKIPPED` with a scheduling reason. | The marker was recognized, but the break was not scheduled in this cycle. Check reasons such as `BREAK_START_IN_PAST`, `DECISIONING_MARGIN`, or `BREAK_OVERLAP`. | +| History contains `FAILED`. | The rule matched, but an unexpected scheduling or configuration error prevented break creation. Inspect the `reason` and verify the template and break configuration. | + +## End-to-end example + +1. Add and enable an HLS origin for `sports-main`. See [Origins](./origins.mdx). + + ```bash + curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/origins' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Primary HLS origin", + "type": "HLS", + "url": "https://origin.example.com/live/sports-main/master.m3u8", + "enabled": true, + "priority": 0 + }' + ``` + +2. Create a break template and note its ID, such as `preroll-30s`. The marker rule references this value as `templateId`. + +3. Create an enabled marker rule for a matching HLS marker: + + ```bash + curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/markerRules' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "streamType": "HLS", + "type": "DATERANGE", + "conditions": { "CLASS": "com.example.ad" }, + "templateId": "preroll-30s", + "enabled": true + }' + ``` + +4. Enable detection on the channel: + + ```bash + curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/detection/enable' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' + ``` + +5. When the selected origin manifest advertises a matching `#EXT-X-DATERANGE` or `#EXT-X-CUE-OUT` marker, the worker evaluates the rule and creates an automatic break. + +6. Confirm the result in Detection history: + + ```bash + curl 'https://ads.example.com/api/v1/channels/sports-main/detection/history?page=1&pageSize=20&sort=-createdAt' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' + ``` + + A successful detection has `action: "CREATED"` and includes both `markerRuleId` and `breakId`. diff --git a/ads/concepts/origins.mdx b/ads/concepts/origins.mdx new file mode 100644 index 000000000000..78ce11409faa --- /dev/null +++ b/ads/concepts/origins.mdx @@ -0,0 +1,160 @@ +--- +sidebar_position: 5 +sidebar_label: Origins +--- + +# Origins + +An origin is a manifest URL that a channel monitors for ad markers. When automatic marker detection is enabled, the worker fetches the channel's enabled origins and parses their manifests for markers. A channel can have multiple origins so that detection keeps working when one source goes offline. + +Origins are scoped to an organization and to a channel. API calls identify the organization with the `X-Org-ID` header and authenticate with an API key and secret using HTTP Basic authentication. + +## Dashboard path + +In the OptiView Unified Dashboard, open the channel and select **Origins** from the channel navigation. From there you can add an origin, edit it, delete it, set its priority, and use **Enable origin** / **Disable origin** to control whether detection considers it. + +## How multiple origins are used + +Only origins with `enabled: true` are considered for detection. Enabled origins are ordered by `priority` ascending, then by creation time. The worker walks that ordered list and uses the **first online origin**: the first one whose manifest is fetched and parsed successfully. + +- **Lowest `priority` value first.** `priority` is an integer; lower values are tried before higher ones. Negative values are allowed, so `-1` is tried before `0`. +- **First online wins.** An origin counts as online when its manifest can be fetched and parsed. A manifest that is reachable but currently advertises no markers still counts as online and wins, so lower-priority origins are not consulted in the same cycle. If an origin cannot be fetched or parsed, detection falls back to the next enabled origin in priority order. + +:::note Supported origin types +The API accepts `HLS`, `DASH`, and `HESP` for `type`, but automatic marker detection currently parses **HLS** manifests only. `DASH` and `HESP` origins can be stored and prioritized, but they are skipped by detection today. Use `HLS` for origins you expect to drive automatic breaks. +::: + +## Configuration reference + +| Field | Type | Default | Description | +| ---------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------- | +| `url` | string | Required | Manifest URL to monitor. Must be a valid URL. | +| `type` | `HLS`, `DASH`, or `HESP` | Required | Manifest format. Only `HLS` is parsed by detection today; `DASH` and `HESP` are accepted but not yet detected. | +| `name` | string | none | Optional human-readable label shown in the Dashboard. | +| `enabled` | boolean | `false` | Whether detection considers this origin. Change it with the enable/disable actions, not with an update. | +| `priority` | integer | `0` | Selection order for detection. Lower values are tried first; negative values are allowed. | + +`enabled` cannot be changed through the update endpoint. Use the dedicated enable and disable actions instead. + +## Add an origin + +Dashboard: open the channel, then use **Origins → Add**. + +API: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/origins' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Primary HLS origin", + "type": "HLS", + "url": "https://origin.example.com/live/sports-main/master.m3u8", + "enabled": true, + "priority": 0 + }' +``` + +Example response: + +```json +{ + "id": "3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f", + "channelId": "sports-main", + "name": "Primary HLS origin", + "type": "HLS", + "url": "https://origin.example.com/live/sports-main/master.m3u8", + "enabled": true, + "priority": 0, + "createdAt": "2026-07-16T12:00:00.000Z" +} +``` + +Add a lower-priority backup origin so detection can fall back if the primary source is unreachable: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/origins' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Backup HLS origin", + "type": "HLS", + "url": "https://backup.example.com/live/sports-main/master.m3u8", + "enabled": true, + "priority": 1 + }' +``` + +## Get an origin + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/origins/3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +## Update an origin + +The update endpoint accepts `url`, `type`, `name`, and `priority`. It does not accept `enabled`. + +```bash +curl -X PATCH 'https://ads.example.com/api/v1/channels/sports-main/origins/3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Primary HLS origin (HD)", + "priority": 0 + }' +``` + +## Enable or disable an origin + +Dashboard: **Origins → Enable origin** / **Disable origin**. + +API: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/origins/3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f/enable' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/origins/3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f/disable' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +Disabling an origin removes it from detection immediately. The origin record is kept, so you can re-enable it later without recreating it. + +## List origins + +```bash +curl 'https://ads.example.com/api/v1/channels/sports-main/origins?page=1&pageSize=20&sort=priority' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +List endpoints use the shared pagination shape. + +| Query parameter | Default | Description | +| --------------- | ------------ | -------------------------------------------------------------------------- | +| `page` | `1` | Page number. | +| `pageSize` | `20` | Items per page. Maximum `100`. | +| `filter` | none | Optional RSQL filter expression. | +| `sort` | `-createdAt` | Comma-separated sort fields. Prefix a field with `-` for descending order. | + +## Delete an origin + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/channels/sports-main/origins/3f9c0f8e-1a2b-4c3d-8e9f-0a1b2c3d4e5f' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +## Next steps + +Origins supply the manifests; [marker detection](./marker-detection.mdx) decides which markers in those manifests become breaks. Configure at least one enabled `HLS` origin before enabling detection on the channel. diff --git a/ads/concepts/templates.mdx b/ads/concepts/templates.mdx new file mode 100644 index 000000000000..4e18c9b627e3 --- /dev/null +++ b/ads/concepts/templates.mdx @@ -0,0 +1,268 @@ +--- +sidebar_position: 3 +sidebar_label: Templates +--- + +# Templates + +A template is a reusable break preset for OptiView Ads. It stores a break payload once so you can schedule consistent breaks quickly, either manually from the dashboard and API or automatically through marker rules. + +Templates are scoped to an organization. API calls identify the organization with the `X-Org-ID` header and authenticate with an API key and secret using HTTP Basic authentication. + +## Dashboard path + +In the OptiView Unified Dashboard, templates are available in two places: + +| Path | Use it for | +| ------------------------------------------------------ | ----------------------------------------------------- | +| `/{organizationId}/ads/templates` | Manage every template in the organization. | +| `/{organizationId}/ads/channels/{channelId}/templates` | Manage the templates surfaced for a specific channel. | + +Both lists expose **New**, **Edit**, and **Delete** actions, plus a **Schedule now** action that immediately schedules a break on the channel from the selected template. + +## Template identity + +Every template has a customer-facing `id`. The API stores it together with the organization ID, so the unique identity is: + +```text +organizationId + templateId +``` + +Use stable template IDs that match your operational names, such as `midroll-30s` or `sponsor-lshape`. If you omit `id` on creation, the API generates one. + +## What a template contains + +A template holds the same payload as a break's `data`, so anything you can express on a break you can preset on a template: + +- `variant` — one variant, or a list of variants with device `targeting`, using the same variant formats (`single`, `double`, `lshape_ad`, `lshape_content`, `overlay`) and typed assets as a break. +- `resumeOffset` and `controls` (skip offset, snapback) — optional playback behaviour. +- `duration` — optional on a template (it is required on a break). When set, it is copied onto breaks scheduled from the template. + +The **Breaks** section is the canonical reference for variant formats, layouts, typed assets, and device targeting. This section cross-links there instead of repeating those details. + +Templates can also record associations that make them easier to organize and surface: + +| Field | Relationship | +| ------------ | ------------------------------------------------------------------------------------------ | +| `channelIds` | Channels the template is associated with (for example, in the per-channel dashboard list). | +| `eventIds` | **Events** the template is associated with. | + +## Snapshot semantics + +A template is a preset, not a live link. When a break is scheduled from a template: + +1. The template's payload is **copied onto the new break** at creation. +2. The break records the source `templateId` as provenance. +3. There is **no synchronization afterwards**. Editing or deleting the template later does not change breaks that were already created from it — they keep their copied payload. + +Templates are **hard-deleted**. Deleting a template removes it permanently; there is no soft-delete or archival state. Breaks previously created from the template are unaffected and still report their historical `templateId`, but that `templateId` no longer resolves to a template, and listing breaks by a deleted template returns a not-found error. + +## Scheduling a break from a template + +You can schedule a break from a template in three ways: + +- **Dashboard** — use the **Schedule now** action on a template in either template list to create a break on the channel immediately. +- **API** — create a break on a channel and reference the template with `templateId` (see [Schedule a break from a template](#schedule-a-break-from-a-template) below). +- **Marker rules** — each marker rule targets a template through its `templateId`. When automatic detection matches a marker, the worker schedules a break from that template. See the **Marker Detection** section for how rules are configured and evaluated. + +In every case the template payload is snapshotted onto the resulting break, as described in [Snapshot semantics](#snapshot-semantics). + +## Configuration reference + +| Field | Type | Default | Description | +| -------------- | -------------------- | --------- | ----------------------------------------------------------------------------------- | +| `id` | string | generated | Customer-facing template ID, unique within the organization. | +| `name` | string | none | Human-readable label shown in the dashboard. | +| `channelIds` | string[] | none | Channels the template is associated with. | +| `eventIds` | string[] | none | Events the template is associated with. | +| `duration` | integer | none | Optional break duration in seconds, copied onto breaks scheduled from the template. | +| `variant` | variant or variant[] | Required | Break variant(s). See the **Breaks** section for formats, assets, and targeting. | +| `resumeOffset` | integer | none | Optional resume offset applied to breaks scheduled from the template. | +| `controls` | object | none | Optional playback controls: `skipOffset` and `snapback`. | + +## Create a template + +Dashboard: **Ads → Templates → New**. + +API: + +```bash +curl -X POST 'https://ads.example.com/api/v1/templates' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "id": "midroll-30s", + "name": "Mid-roll 30s", + "channelIds": ["sports-main"], + "duration": 30, + "variant": { + "format": "single", + "assets": [ + { + "type": "vast", + "mediaType": "video", + "uri": "https://ads.example.com/vast/midroll.xml" + } + ] + } + }' +``` + +Example response: + +```json +{ + "id": "midroll-30s", + "name": "Mid-roll 30s", + "channelIds": ["sports-main"], + "duration": 30, + "variant": { + "format": "single", + "assets": [ + { + "type": "vast", + "mediaType": "video", + "uri": "https://ads.example.com/vast/midroll.xml" + } + ] + }, + "createdAt": "2026-07-16T12:00:00.000Z" +} +``` + +## Get a template + +```bash +curl 'https://ads.example.com/api/v1/templates/midroll-30s' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +## Update a template + +```bash +curl -X PATCH 'https://ads.example.com/api/v1/templates/midroll-30s' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "name": "Mid-roll 30s (VAST)", + "duration": 30 + }' +``` + +Updating a template does not change breaks already scheduled from it — see [Snapshot semantics](#snapshot-semantics). + +## List templates + +```bash +curl 'https://ads.example.com/api/v1/templates?page=1&pageSize=20&sort=-createdAt' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +List endpoints use the same pagination shape as the rest of the API. Templates can be sorted by `name`, `duration`, or `createdAt`. + +| Query parameter | Default | Description | +| --------------- | ------------ | -------------------------------------------------------------------------- | +| `page` | `1` | Page number. | +| `pageSize` | `20` | Items per page. Maximum `100`. | +| `filter` | none | Optional RSQL filter expression. | +| `sort` | `-createdAt` | Comma-separated sort fields. Prefix a field with `-` for descending order. | + +Templates maintain denormalized fields derived from their payload so you can filter without inspecting the full `variant`. The `filter` expression accepts these selectors: + +| Filter selector | Matches on | Operators | +| --------------- | --------------------------------------- | ------------------------------------------ | +| `name` | Template name | `==`, `!=`, `=like=`, `=in=` | +| `duration` | Template duration | `==`, `!=`, `=gt=`, `=ge=`, `=lt=`, `=le=` | +| `format` | Variant formats present on the template | `==`, `!=`, `=like=`, `=in=` | +| `assetType` | Asset types present on the template | `==`, `!=`, `=like=`, `=in=` | +| `vendor` | Vendors present on the template | `==`, `!=`, `=like=`, `=in=` | + +Examples: + +```bash +# Overlay templates only +curl 'https://ads.example.com/api/v1/templates?filter=format==overlay' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +```bash +# Short VAST templates (30s or less), sorted by duration +curl 'https://ads.example.com/api/v1/templates?filter=duration=le=30;assetType==vast&sort=duration' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +```bash +# Templates that use a vendor asset (for example, Google Ad Manager pods) +curl 'https://ads.example.com/api/v1/templates?filter=vendor==gam' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +Combine multiple conditions with `;`. + +## Delete a template + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/templates/midroll-30s' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'X-Org-ID: org_123' +``` + +Delete multiple templates in one request: + +```bash +curl -X DELETE 'https://ads.example.com/api/v1/templates' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ "ids": ["midroll-30s", "sponsor-lshape"] }' +``` + +Deletes are permanent (hard delete). Existing breaks scheduled from the template are not affected — see [Snapshot semantics](#snapshot-semantics). + +## Schedule a break from a template + +Create a break on a channel and reference the template with `templateId`. The template payload is snapshotted onto the break at creation. + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/breaks' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "templateId": "midroll-30s", + "start": "2026-07-16T13:00:00.000Z" + }' +``` + +`templateId` is the only required field. You can override the snapshotted payload per break with optional fields — `start`, `duration`, `variant`, `assetParameters`, `eventId`, and `id`. Start semantics depend on the channel timebase; see the **Channels** and **Breaks** sections for scheduling and lifecycle details. + +The created break records the source `templateId` alongside its own copied payload: + +```json +{ + "id": "b_9f2c", + "channelId": "sports-main", + "templateId": "midroll-30s", + "status": "PREPARING", + "start": "2026-07-16T13:00:00.000Z", + "duration": 30, + "variant": { + "format": "single", + "assets": [ + { + "type": "vast", + "mediaType": "video", + "uri": "https://ads.example.com/vast/midroll.xml" + } + ] + }, + "createdAt": "2026-07-16T12:30:00.000Z" +} +``` diff --git a/ads/getting-started/index.mdx b/ads/getting-started/index.mdx index 3bcac379b49a..f9ad5b59a0ec 100644 --- a/ads/getting-started/index.mdx +++ b/ads/getting-started/index.mdx @@ -14,3 +14,7 @@ These guides provide the steps required to get started with OptiView Ads. They c import DocCardList from '@theme/DocCardList'; + +## Integrate a player + +Choose an integration path in the [Player integration](/ads/player-integration/) section. The recommended OptiView Player guides cover Web, Android, iOS, Chromecast CAF, and React Native. You can also integrate an open-source or native player directly against the Break Manifest and SSAI cue contracts. diff --git a/ads/player-integration/bring-your-own-player/avplayer.mdx b/ads/player-integration/bring-your-own-player/avplayer.mdx new file mode 100644 index 000000000000..bb7d434e5e4c --- /dev/null +++ b/ads/player-integration/bring-your-own-player/avplayer.mdx @@ -0,0 +1,37 @@ +--- +sidebar_position: 5 +sidebar_label: AVPlayer +--- + +# Integrate with AVPlayer + +AVFoundation provides HLS date-range metadata through `AVPlayerItemMetadataCollector` and `AVDateRangeMetadataGroup`. Apple also provides HLS interstitial APIs through `AVPlayerInterstitialEvent`, `AVPlayerInterstitialEventController`, and `AVPlayerInterstitialEventMonitor`. See the [AVPlayerItemMetadataCollector documentation](https://developer.apple.com/documentation/avfoundation/avplayeritemmetadatacollector), [AVDateRangeMetadataGroup](https://developer.apple.com/documentation/avfoundation/avdaterangemetadatagroup), and [AVPlayerInterstitialEvent](https://developer.apple.com/documentation/avfoundation/avplayerinterstitialevent). + +```swift +let playerItem = AVPlayerItem(url: URL(string: "https://ADS-HOST/channel/monetized/master.m3u8")!) +let collector = AVPlayerItemMetadataCollector() + +collector.setDelegate(self, queue: .main) +playerItem.add(collector) + +func metadataCollector( + _ metadataCollector: AVPlayerItemMetadataCollector, + didCollect metadataGroups: [AVDateRangeMetadataGroup], + indexes: IndexSet, + for track: AVPlayerItemTrack +) { + for group in metadataGroups { + scheduleFullScreenAd( + start: group.startDate, + duration: group.endDate.timeIntervalSince(group.startDate) + ) + } +} + +let player = AVPlayer(playerItem: playerItem) +player.play() +``` + +For applications using Apple's interstitial flow, configure an `AVPlayerInterstitialEventController` with the HLS interstitial events and observe it with `AVPlayerInterstitialEventMonitor`. The Break Manifest remains an alternative: fetch `/manifest/v1/{orgId}/channels/{channelId}` immediately, retry failures after 10 seconds, and use its idle/active polling values. + +AVPlayer does not provide OptiView layout rendering or OptiView impression reporting; those remain application responsibilities. diff --git a/ads/player-integration/bring-your-own-player/exoplayer-media3.mdx b/ads/player-integration/bring-your-own-player/exoplayer-media3.mdx new file mode 100644 index 000000000000..ae4df48ecc3c --- /dev/null +++ b/ads/player-integration/bring-your-own-player/exoplayer-media3.mdx @@ -0,0 +1,48 @@ +--- +sidebar_position: 4 +sidebar_label: ExoPlayer / Media3 +--- + +# Integrate with ExoPlayer / Media3 + +Media3 exposes HLS metadata through `Player.Listener.onMetadata`. Depending on the Media3 version and stream configuration, HLS interstitials can also be handled with `HlsInterstitialsAdsLoader`. See the [Media3 HLS guide](https://developer.android.com/media/media3/exoplayer/hls), [`Player.Listener`](https://developer.android.com/reference/androidx/media3/common/Player.Listener), and [`HlsInterstitialsAdsLoader`](https://developer.android.com/reference/androidx/media3/exoplayer/hls/HlsInterstitialsAdsLoader). + +## Read HLS metadata + +```kotlin +class BreakMetadataListener( + private val onBreak: (id: String, durationSeconds: Double) -> Unit +) : Player.Listener { + override fun onMetadata(metadata: Metadata) { + for (index in 0 until metadata.length()) { + val entry: Metadata.Entry = metadata[index] + // Inspect the entry's SCTE-35 or date-range representation for your + // Media3 version, then schedule a full-screen ad in application code. + onBreak(entry.toString(), 0.0) + } + } +} + +val player = ExoPlayer.Builder(context).build() +player.addListener(BreakMetadataListener(::scheduleFullScreenAd)) +player.setMediaItem(MediaItem.fromUri("https://ADS-HOST/channel/monetized/master.m3u8")) +player.prepare() +player.play() +``` + +Use `HlsInterstitialsAdsLoader` when your application wants Media3's HLS interstitial support rather than inspecting metadata entries directly. It still does not provide OptiView layout rendering or OptiView impression reporting. + +## Poll the Break Manifest instead + +```kotlin +suspend fun loadBreakManifest(): BreakManifest { + val request = Request.Builder() + .url("https://ADS-HOST/manifest/v1/ORG-ID/channels/CHANNEL-ID") + .build() + val response = httpClient.newCall(request).execute() + if (!response.isSuccessful) error("Break Manifest HTTP ${response.code}") + return json.decodeFromString(response.body!!.string()) +} +``` + +Make the first request immediately, retry failures after 10 seconds, and use the response's `polling.idle` or `polling.active` value for continuous polling. diff --git a/ads/player-integration/bring-your-own-player/hls-js.mdx b/ads/player-integration/bring-your-own-player/hls-js.mdx new file mode 100644 index 000000000000..06d94091e59d --- /dev/null +++ b/ads/player-integration/bring-your-own-player/hls-js.mdx @@ -0,0 +1,64 @@ +--- +sidebar_position: 2 +sidebar_label: hls.js +--- + +# Integrate with hls.js + +hls.js exposes parsed HLS date ranges on the active `LevelDetails.dateRanges` map. Use the `LEVEL_UPDATED` event to inspect refreshed media-playlist details. See the [hls.js `LevelDetails` API](https://hlsjs.video-dev.org/api-docs/hls.js.leveldetails) and [events API](https://hlsjs.video-dev.org/api-docs/hls.js.events). + +## Option 1: poll the Break Manifest + +The following example makes an immediate request, retries failures after 10 seconds, and changes the polling interval based on whether the response contains an active break. It treats the first successful response as sufficient for a minimal integration; remove the `oneShot` guard for continuous scheduling. + +```javascript +const manifestUrl = 'https://ADS-HOST/manifest/v1/ORG-ID/channels/CHANNEL-ID'; +let timer; +let oneShot = true; + +async function loadBreakManifest() { + try { + const response = await fetch(manifestUrl); + if (!response.ok) throw new Error(`Break Manifest HTTP ${response.status}`); + + const manifest = await response.json(); + scheduleBreaks(manifest.breaks); + + if (oneShot) return; + const active = manifest.breaks.some((breakItem) => isActive(breakItem)); + timer = setTimeout(loadBreakManifest, (active ? manifest.polling.active : manifest.polling.idle) * 1000); + } catch (error) { + console.error('Break Manifest request failed', error); + timer = setTimeout(loadBreakManifest, 10_000); + } +} + +loadBreakManifest(); +``` + +`scheduleBreaks` is application code: render the `single` full-screen experience at each break's `start` for its `duration`, and apply any supported `controls`. + +## Option 2: read injected DATERANGE cues + +```javascript +const hls = new Hls(); +hls.loadSource('https://ADS-HOST/channel/monetized/master.m3u8'); +hls.attachMedia(video); + +hls.on(Hls.Events.LEVEL_UPDATED, (_event, data) => { + const dateRanges = data.details?.dateRanges ?? {}; + for (const [id, dateRange] of Object.entries(dateRanges)) { + if (!dateRange) continue; + const start = dateRange.startDate; + const duration = dateRange.duration ?? dateRange.plannedDuration; + startFullScreenAd({ id, start, duration }); + } +}); +``` + +The injected OUT and IN tags carry the break ID, wallclock dates, duration, and hexadecimal SCTE-35 attributes. hls.js does not render the ad experience for you; your application must coordinate the video element and ad UI. + +## More information + +- [hls.js `LevelDetails`](https://hlsjs.video-dev.org/api-docs/hls.js.leveldetails) +- [hls.js `Events`](https://hlsjs.video-dev.org/api-docs/hls.js.events) diff --git a/ads/player-integration/bring-your-own-player/index.mdx b/ads/player-integration/bring-your-own-player/index.mdx new file mode 100644 index 000000000000..1720e2336779 --- /dev/null +++ b/ads/player-integration/bring-your-own-player/index.mdx @@ -0,0 +1,77 @@ +--- +sidebar_position: 1 +sidebar_label: Bring your own player +--- + +# Bring your own player + +OptiView Ads does not require OptiView Player. Open-source and native players can integrate using either of these open contracts: + +1. **Break Manifest polling:** request `GET /manifest/v1/{orgId}/channels/{channelId}` directly and schedule the returned breaks. +2. **SSAI cue consumption:** play the channel's proxied monetized HLS URL and consume the `EXT-X-DATERANGE` cues injected into its media playlist. + +OptiView provides no first-party ad adapter or plugin for these players. You are responsible for rendering ads, scheduling transitions, and implementing ad tracking. + +For channel polling configuration and channel-level SSAI DAI integrations, see [Channels](../../concepts/channels). + +## Break Manifest contract + +The endpoint supports CORS for `GET` and `OPTIONS`: + +```text +GET /manifest/v1/{orgId}/channels/{channelId} +``` + +A response has this shape: + +```json +{ + "version": "1.0.0", + "timebase": "wallclock", + "polling": { + "idle": 10, + "active": 1 + }, + "breaks": [ + { + "id": "break-123", + "start": "2026-01-01T12:00:00.000Z", + "duration": 30, + "controls": { + "skipOffset": 5, + "snapback": true + }, + "variant": { + "format": "single", + "assets": [] + } + } + ] +} +``` + +`timebase` is `wallclock` or `pts`. A wallclock break uses an ISO-8601 `start`; a PTS break uses a numeric `start`. `variant` may be one variant object or an array. Each variant has a `format` of `single`, `double`, `lshape_ad`, `lshape_content`, or `overlay`. + +Consumers should accept any `1.x` manifest version. Make an immediate request when loading the stream. If a request fails, retry after 10 seconds. After a successful response, use `polling.idle` (default 10 seconds) when no break is active and `polling.active` (default 1 second) during an active break. The first successful response is sufficient for a minimal integration; continuous polling is what keeps upcoming breaks fresh. + +Successful responses are JSON and carry `Cache-Control: public, max-age=`. A missing channel returns `404` with a short negative cache. + +## SSAI `EXT-X-DATERANGE` cues + +The SSAI path requires an `SSAI_DAI` channel integration. It carries Google DAI asset keys, and the service fans each signaled break out to Google DAI pod serving for those keys. Only wallclock channels receive injected cues; PTS channels do not. + +The proxy rewrites the media playlist and inserts two tags per break immediately before the segment whose wallclock window contains the cue: + +```text +#EXT-X-DATERANGE:ID="",START-DATE="",PLANNED-DURATION=,SCTE35-OUT= +#EXT-X-DATERANGE:ID="",START-DATE="",END-DATE="",DURATION=,SCTE35-IN= +``` + +There is no `CLASS` attribute and no custom `X-` attribute. `SCTE35-OUT` and `SCTE35-IN` contain hexadecimal SCTE-35 payloads. + +The open-source player examples show both approaches: + +- [hls.js](./hls-js) +- [Shaka Player](./shaka-player) +- [ExoPlayer / Media3](./exoplayer-media3) +- [AVPlayer](./avplayer) diff --git a/ads/player-integration/bring-your-own-player/shaka-player.mdx b/ads/player-integration/bring-your-own-player/shaka-player.mdx new file mode 100644 index 000000000000..58d2fc0f6635 --- /dev/null +++ b/ads/player-integration/bring-your-own-player/shaka-player.mdx @@ -0,0 +1,35 @@ +--- +sidebar_position: 3 +sidebar_label: Shaka Player +--- + +# Integrate with Shaka Player + +Shaka Player parses HLS `EXT-X-DATERANGE` data into timeline regions. Listen for `timelineregionadded`, `timelineregionenter`, and `timelineregionexit`; the event detail is a `shaka.extern.TimelineRegionInfo` object. See the [Shaka `Player` event API](https://shaka-player-demo.appspot.com/docs/api/shaka.Player.html#event:timelineregionadded) and [`TimelineRegionInfo`](https://shaka-player-demo.appspot.com/docs/api/shaka.extern.html#TimelineRegionInfo). + +```javascript +const player = new shaka.Player(video); + +player.addEventListener('timelineregionadded', (event) => { + const region = event.detail; + if (region.schemeIdUri === 'urn:scte:scte35:2013:bin') { + startFullScreenAd({ + id: region.id, + start: region.startTime, + duration: region.endTime - region.startTime, + }); + } +}); + +player.addEventListener('timelineregionenter', (event) => { + handleBreakStart(event.detail); +}); + +player.addEventListener('timelineregionexit', (event) => { + handleBreakEnd(event.detail); +}); + +await player.load('https://ADS-HOST/channel/monetized/master.m3u8'); +``` + +Use the [Break Manifest endpoint](../../../concepts/channels) instead when you want to schedule breaks before their HLS cue reaches the player. Shaka does not provide OptiView layout rendering or OptiView impression reporting; those remain application responsibilities. diff --git a/ads/player-integration/index.mdx b/ads/player-integration/index.mdx new file mode 100644 index 000000000000..7c03333ed433 --- /dev/null +++ b/ads/player-integration/index.mdx @@ -0,0 +1,46 @@ +--- +sidebar_position: 1 +sidebar_label: Player integration +--- + +# Player integration + +OptiView Ads is designed to work with the [OptiView Player](./optiview-player/), but you are not required to use it. The [Break Manifest](./bring-your-own-player/) and SSAI `EXT-X-DATERANGE` cue injection are open contracts that you can integrate with your own player. + +## Choose an integration path + +- **[OptiView Player](./optiview-player/)** is the recommended path. The Web, Android, and iOS SDKs handle Break Manifest polling, ad scheduling, Google IMA/DAI integration, and OptiView impression reporting. Chromecast CAF and React Native have platform-specific capabilities described in their guides. +- **[Bring your own player](./bring-your-own-player/)** is for open-source and native players. Poll the Break Manifest yourself, or consume the SSAI cues injected into a proxied media playlist. OptiView does not provide first-party adapters for these players. + +## Capability matrix + +| Capability | OptiView Player Web / Android / iOS | Chromecast CAF | React Native | hls.js | Shaka Player | ExoPlayer / Media3 | AVPlayer | +| ---------------------------------- | ----------------------------------------------------------------- | ------------------------------- | ------------------------ | --------------------- | ------------------------ | ----------------------------------- | ------------------------------------------------ | +| Automatic Break Manifest handling | Yes | Via sender/receiver integration | Platform-dependent | No | No | No | No | +| `breakManifestUrl` support | Yes | No | No | N/A | N/A | N/A | N/A | +| SSAI `EXT-X-DATERANGE` consumption | Yes | Receiver handles supported cues | Platform-dependent | Yes, application code | Yes, as timeline regions | Yes, via metadata/interstitial APIs | Yes, via AVFoundation metadata/interstitial APIs | +| Single (full-screen) ad | Yes | Yes | Yes | Application code | Application code | Application code | Application code | +| Double box | Defined in schema; not currently selected by current schedulers\* | No | Not currently selected\* | Application code | Application code | Application code | Application code | +| L-shape | Defined in schema; not currently selected by current schedulers\* | No | Not currently selected\* | Application code | Application code | Application code | Application code | +| Overlay | Defined in schema; not currently selected by current schedulers\* | Image overlays only | Not currently selected\* | Application code | Application code | Application code | Application code | +| Google IMA / DAI integration | Built in | Built in for supported CAF flow | Platform-dependent | Application code | Application code | Application code | Application code | +| OptiView impression reporting | Yes | No | No | No | No | No | No | + +The Break Manifest schema defines `single`, `double`, `lshape_ad`, `lshape_content`, and `overlay`. The current Web, Android, and iOS OptiView Player schedulers select the `single` layout only. Chromecast CAF currently supports image overlays only. + +## How ads are delivered and tracked + +OptiView Ads uses **server-guided ad insertion (SGAI)**: + +1. A channel's Break Manifest, or injected HLS cues, signals when an ad break occurs. +2. Google DAI / Pod Serving performs server-side ad decisioning and stitching. The OptiView Player configuration identifies the Google account with `networkCode` and `customAssetKey`. +3. The client-side Google IMA SDK handles the ad request and tracking. On Web this is the `ima3_dai.js` library; on iOS it is the `GoogleInteractiveMediaAds` SDK. + +Google IMA and Google DAI are related but different: IMA is the client-side library, while DAI / Pod Serving is the server-side ad-decisioning and stitching service. + +There are two impression types: + +- **OptiView impression beacon:** sent by OptiView Player once per ad experience with the player's `adbreakbegin` event. It is linked to the player license, contains no viewer data, and is visible in the portal. See [Ad impression tracking](../how-to-guides/ad-impressions.md). +- **Google ad-server impressions:** the standard IMA/DAI creative impression and tracking pings sent to Google Ad Manager. + +BYO players do not emit the OptiView impression beacon. They can still generate Google impressions if they implement the applicable IMA or ad-tracking integration. diff --git a/ads/player-integration/optiview-player/android.mdx b/ads/player-integration/optiview-player/android.mdx new file mode 100644 index 000000000000..25d25048c513 --- /dev/null +++ b/ads/player-integration/optiview-player/android.mdx @@ -0,0 +1,78 @@ +--- +sidebar_position: 3 +sidebar_label: Android +sidebar_custom_props: { 'icon': 'android' } +--- + +# OptiView Ads on Android + +This guide configures OptiView Ads in the OptiView Player Android SDK 11.x. + +## Prerequisites + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +1. Obtain an OptiView Player license compatible with OptiView Ads from the [player portal](https://portal.theoplayer.com). +2. Add the OptiView Player Android SDK by following the [Android getting started guide](/theoplayer/getting-started/sdks/android/getting-started). +3. Add the integration in your module-level Gradle file. + + + + +```groovy +dependencies { + implementation "com.theoplayer.theoplayer-sdk-android:core:11.+" + implementation "com.theoplayer.theoplayer-sdk-android:integration-ads-theoads:11.+" +} +``` + + + + +```kotlin +dependencies { + implementation("com.theoplayer.theoplayer-sdk-android:core:11.+") + implementation("com.theoplayer.theoplayer-sdk-android:integration-ads-theoads:11.+") +} +``` + + + + +## Integration + +```kotlin +val theoAdsIntegration = TheoAdsIntegrationFactory.createTheoAdsIntegration(theoPlayerView) +theoPlayerView.player.addIntegration(theoAdsIntegration) + +theoPlayerView.player.source = SourceDescription.Builder( + TypedSource.Builder("CHANNEL-MONETIZED-HLS-URL") + .type(SourceType.HLS) + .hlsDateRange(true) + .build() +).ads( + TheoAdDescription( + networkCode = "NETWORK-CODE", + customAssetKey = "CUSTOM-ASSET-KEY", + breakManifestUrl = "https://ADS-HOST/manifest/v1/ORG-ID/channels/CHANNEL-ID", + adTagParameters = mapOf("key" to "value") + ) +).build() +``` + +`breakManifestUrl` points to the v2 Break Manifest. `hlsDateRange(true)` enables handling of the SSAI cues in the channel's monetized HLS media playlist. + +## Integrating with Open Video UI + +Create and add `TheoAdsIntegration` before creating your UI, then use the same `SourceDescription` configuration above with [Open Video UI for Android](/open-video-ui/android/). + +## Verify + +Load the channel's monetized HLS source and start playback. Confirm that the Break Manifest or SSAI cues schedule a break, the ad renders, and the OptiView impression appears in the portal. + +## More information + +- [Android `TheoAdDescription` API](https://optiview.dolby.com/docs/theoplayer/v11/api-reference/android/com/theoplayer/android/api/ads/theoads/TheoAdDescription.html) +- [Ad impression tracking](../../../how-to-guides/ad-impressions) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) diff --git a/ads/player-integration/optiview-player/chromecast.mdx b/ads/player-integration/optiview-player/chromecast.mdx new file mode 100644 index 000000000000..f54529dc13eb --- /dev/null +++ b/ads/player-integration/optiview-player/chromecast.mdx @@ -0,0 +1,48 @@ +--- +sidebar_position: 5 +sidebar_label: Chromecast CAF +sidebar_custom_props: { 'icon': 'web' } +--- + +# OptiView Ads on Chromecast CAF + +:::note +Chromecast CAF currently supports image overlays only from OptiView Ads. +::: + +## Prerequisites + +1. Obtain an OptiView Player license compatible with OptiView Ads from the [player portal](https://portal.theoplayer.com). +2. Install the sender and receiver packages: + + ```bash + npm install @opentelly/theoplayer-chromecast-sdk @opentelly/theoplayer-chromecast-receiver + ``` + +3. Have a Chromecast Developer account to deploy your custom CAF receiver. + +## Integration + +Use the receiver package with the CAF receiver framework and enable Shaka for HLS: + +```html + + +``` + +```javascript +const castReceiverOptions = new cast.framework.CastReceiverOptions(); +castReceiverOptions.useShakaForHls = true; +cast.framework.CastReceiverContext.getInstance().start(castReceiverOptions); +``` + +Configure the sender with the OptiView Player Chromecast SDK and the channel's monetized playback URL. Chromecast CAF uses the sender/receiver integration; it does not expose `breakManifestUrl` as a direct SDK configuration field. + +## Verify + +Cast the channel's monetized HLS source and start playback. Confirm that the scheduled break renders its supported image overlay, then verify the receiver completes the ad experience. + +## More information + +- [Chromecast integration](https://optiview.dolby.com/docs/theoplayer/how-to-guides/cast/chromecast/introduction/) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) diff --git a/ads/player-integration/optiview-player/index.mdx b/ads/player-integration/optiview-player/index.mdx new file mode 100644 index 000000000000..e02f1ff7d8c4 --- /dev/null +++ b/ads/player-integration/optiview-player/index.mdx @@ -0,0 +1,25 @@ +--- +sidebar_position: 1 +sidebar_label: OptiView Player +--- + +# OptiView Player + +OptiView Player is the recommended integration path for OptiView Ads. Its platform SDKs handle the Break Manifest and HLS cue details so your application can focus on playback and presentation. + +Built-in capabilities include: + +- Automatic Break Manifest polling through `breakManifestUrl` on Web, Android, and iOS. +- Google IMA / DAI integration using `networkCode`, `customAssetKey`, and optional `adTagParameters`. +- HLS `EXT-X-DATERANGE` handling for the channel's monetized playback URL. +- OptiView impression reporting on supported OptiView Player integrations. + +The Break Manifest schema defines five formats: `single`, `double`, `lshape_ad`, `lshape_content`, and `overlay`. The current Web, Android, and iOS schedulers select `single` only; the other formats are schema capabilities and are not currently selected by those schedulers. Chromecast CAF currently supports image overlays only. + +Choose a platform: + +- [Web](./web) +- [Android](./android) +- [iOS](./ios) +- [Chromecast CAF](./chromecast) +- [React Native](./react-native) diff --git a/ads/player-integration/optiview-player/ios.mdx b/ads/player-integration/optiview-player/ios.mdx new file mode 100644 index 000000000000..5355302f30a1 --- /dev/null +++ b/ads/player-integration/optiview-player/ios.mdx @@ -0,0 +1,81 @@ +--- +sidebar_position: 4 +sidebar_label: iOS +sidebar_custom_props: { 'icon': 'apple' } +--- + +# OptiView Ads on iOS + +This guide configures OptiView Ads in the OptiView Player iOS SDK 11.x. + +## Prerequisites + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +1. Obtain an OptiView Player license compatible with OptiView Ads from the [player portal](https://portal.theoplayer.com). +2. Add `THEOplayer-Integration-THEOads` to your project. + + + + +```ruby +pod 'THEOplayer-Integration-THEOads', '~> 11.6.1' +``` + + + + +Add `https://github.com/THEOplayer/theoplayer-sdk-apple` and select `THEOplayerTHEOadsIntegration`. + +![Screenshot of Xcode file menu](../../assets/img/ios-xcode-file-menu.png) + +![Screenshot of SwiftPM menu](../../assets/img/ios-swiftpm-menu.png) + +![Screenshot of SwiftPM product selection](../../assets/img/ios-swiftpm-product-selection.png) + + + + +3. Add Google IMA. CocoaPods uses `GoogleAds-IMA-iOS-SDK`; SwiftPM uses the `GoogleInteractiveMediaAds` product. + + ![Screenshot of Xcode file menu for Google IMA](../../assets/img/ios-xcode-file-menu.png) + + ![Screenshot of SwiftPM menu for Google IMA](../../assets/img/ios-swiftpm-menu-google.png) + + ![Screenshot of SwiftPM Google product selection](../../assets/img/ios-swiftpm-product-selection-google.png) + +## Integration + +```swift +import THEOplayerSDK +import THEOplayerTHEOadsIntegration + +let theoads = THEOadsIntegrationFactory.createIntegration(on: theoplayer) +theoplayer.addIntegration(theoads) + +let typedSource = TypedSource( + src: "CHANNEL-MONETIZED-HLS-URL", + type: "application/x-mpegurl", + hlsDateRange: true +) +let theoad = THEOAdDescription( + networkCode: "NETWORK-CODE", + customAssetKey: "CUSTOM-ASSET-KEY", + breakManifestUrl: URL(string: "https://ADS-HOST/manifest/v1/ORG-ID/channels/CHANNEL-ID"), + adTagParameters: ["key": "value"] +) +theoplayer.source = SourceDescription(source: typedSource, ads: [theoad]) +``` + +`breakManifestUrl` points to the v2 Break Manifest. `hlsDateRange` enables handling of the SSAI cues in the channel's monetized HLS media playlist. + +## Verify + +Load the channel's monetized HLS source and start playback. Confirm that the Break Manifest or SSAI cues schedule a break, the ad renders, and the OptiView impression appears in the portal. + +## More information + +- [iOS `THEOAdDescription` API](https://optiview.dolby.com/docs/theoplayer/v11/api-reference/ios/Structs/THEOAdDescription.html) +- [Ad impression tracking](../../../how-to-guides/ad-impressions) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) diff --git a/ads/player-integration/optiview-player/react-native.mdx b/ads/player-integration/optiview-player/react-native.mdx new file mode 100644 index 000000000000..7743f70f02b2 --- /dev/null +++ b/ads/player-integration/optiview-player/react-native.mdx @@ -0,0 +1,75 @@ +--- +sidebar_position: 6 +sidebar_label: React Native +sidebar_custom_props: { 'icon': 'react' } +--- + +# OptiView Ads on React Native + +The React Native integration is independently versioned. This guide targets `react-native-theoplayer` **11.4.0**, while the OptiView Player SDK root is 11.6.1. + +## Prerequisites + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +1. Obtain an OptiView Player license compatible with OptiView Ads from the [player portal](https://portal.theoplayer.com). +2. Install `react-native-theoplayer` 11.4.0 and enable the THEOads platform feature. + + + + +Set `THEOplayer_extensionTHEOads = true` in `gradle.properties`. + + + + +Add the `THEOADS` feature flag to `react-native-theoplayer.json` (or `theoplayer-config.json`). + + + + +```bash +npm install react-native-theoplayer@11.4.0 +``` + +Load the Google DAI library: + +```html + +``` + + + + +## Integration + +```javascript +player.source = { + sources: { + src: 'CHANNEL-MONETIZED-HLS-URL', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + adTagParameters: { key: 'value' }, + }, + ], +}; +``` + +React Native supports `adTagParameters`, but does not expose `breakManifestUrl`. Use the platform's OptiView Player integration for Break Manifest handling and the channel's monetized HLS URL. + +## Verify + +Load the channel's monetized HLS source and start playback. Confirm that the platform integration schedules a break from the Break Manifest or SSAI cues, the ad renders, and the OptiView impression appears in the portal. + +## More information + +- [React Native player getting started](/theoplayer/getting-started/frameworks/react-native/getting-started/) +- [Ad impression tracking](../../../how-to-guides/ad-impressions) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) diff --git a/ads/player-integration/optiview-player/web.mdx b/ads/player-integration/optiview-player/web.mdx new file mode 100644 index 000000000000..f1078a0a05bf --- /dev/null +++ b/ads/player-integration/optiview-player/web.mdx @@ -0,0 +1,72 @@ +--- +sidebar_position: 2 +sidebar_label: Web +sidebar_custom_props: { 'icon': 'web' } +--- + +# OptiView Ads on Web + +This guide configures OptiView Ads in the OptiView Player Web SDK 11.x. + +## Prerequisites + +1. Obtain an OptiView Player license compatible with OptiView Ads from the [player portal](https://portal.theoplayer.com). +2. Use the channel's monetized HLS playback URL (proxy/CDN). +3. Install the Web SDK: + + ```bash + npm install @opentelly/theoplayer + ``` + +## Integration + +Load the Google DAI library: + +```html + +``` + +Configure the player and source: + +```javascript +const player = new THEOplayer.Player(element, { + libraryLocation: 'YOUR-LIBRARY-LOCATION', + license: 'YOUR-LICENSE-WITH-OPTIVIEW-ADS', + ads: { theoads: true }, +}); + +player.source = { + sources: { + src: 'CHANNEL-MONETIZED-HLS-URL', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + breakManifestUrl: 'https://ADS-HOST/manifest/v1/ORG-ID/channels/CHANNEL-ID', + adTagParameters: { key: 'value' }, + }, + ], +}; +``` + +`breakManifestUrl` is the v2 Break Manifest endpoint. `hlsDateRange: true` enables handling of the SSAI cues in the monetized HLS media playlist. + +## Integrating with Open Video UI + +OptiView Ads works with [Open Video UI for Web](/open-video-ui/web/). Pass the same source and ad description to the UI's `source` property. + +![Screenshot of Open Video UI playing an OptiView Ads stream](../../assets/img/web-ui.png) + +## Verify + +Load the channel's monetized HLS source and start playback. Confirm that a break is scheduled from the Break Manifest or SSAI cues, the ad renders, and the OptiView impression appears in the portal. + +## More information + +- [Web `TheoAdDescription` API](https://optiview.dolby.com/docs/theoplayer/v11/api-reference/web/interfaces/TheoAdDescription.html) +- [Ad impression tracking](../../../how-to-guides/ad-impressions) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) diff --git a/ads/vendors/google.mdx b/ads/vendors/google.mdx new file mode 100644 index 000000000000..8861e5feade1 --- /dev/null +++ b/ads/vendors/google.mdx @@ -0,0 +1,159 @@ +--- +sidebar_position: 2 +sidebar_label: Google Ad Manager +--- + +# Google Ad Manager 360 + +Google Ad Manager 360 (GAM 360) is the first supported OptiView Ads vendor. It requires a GAM 360 account with Dynamic Ad Insertion (DAI) and pod serving enabled. + +## Organization configuration + +Google configuration is organization-level and administrator-managed. A Dolby OptiView administrator or account team configures these values; they are not configured through the self-serve Basic API. + +| Field | Type | Required | Effective service default when unset | +| -------------------------------- | ---------------- | --------------------------------------------------------------- | ------------------------------------ | +| `google.networkCode` | string | Optional in the organization schema; required for GAM signaling | None | +| `google.serviceAccountPath` | string | Optional in the organization schema; required for GAM signaling | None | +| `google.eabnLookForwardTimeMs` | positive integer | Optional | `300000` ms | +| `google.eabnDecisioningMarginMs` | positive integer | Optional | `5000` ms | + +The organization-level values override the service defaults. `networkCode` and `serviceAccountPath` must be present before EABN can signal a break. + +## SGAI pod serving + +SGAI is server-guided pod serving keyed by the channel's `customAssetKey`. A `customAssetKey` is unique within an organization when set. See [Channels](../../concepts/channels) for channel configuration. + +A GAM pod break uses a vendor asset with `vendorParameters.type` set to `"pod"`: + +```json +{ + "type": "vendor", + "vendor": "gam", + "uri": "placeholder", + "vendorParameters": { + "type": "pod" + } +} +``` + +### EABN lifecycle + +1. A GAM pod break is created with status `PREPARING`. +2. EABN waits until the look-forward window opens, then signals a Google DAI ad break through the channel's `customAssetKey`. +3. Google returns a `podId`. The vendor asset's `uri` is set to that pod ID. +4. The break becomes `READY` when it has a start time, or `CUED` when it has no start time. +5. For a delivered HLS manifest, the proxy injects the cue and changes `READY` to `SIGNALED`. + +The player then requests the pod manifest using the vendor asset `uri`, which is the decisioned `podId`. + +`google.eabnLookForwardTimeMs` controls when EABN signals a scheduled break: signaling begins when the effective live point reaches `start - eabnLookForwardTimeMs`. Its effective default is `300000` ms. + +`google.eabnDecisioningMarginMs` is the minimum lead time required for decisioning. If `start - effectiveNow` falls below this margin, the break is missed instead of being signaled. Its effective default is `5000` ms. + +### Cue-punch + +A `CUED` break has no start time and waits for a punch before it plays. Punching changes the status from `CUED` to `READY`. A GAM pod break cannot be punched until EABN has decisioned it: + +```text +Ad break '' is not yet decisioned by EABN +``` + +## SSAI_DAI + +`SSAI_DAI` is a channel integration. It carries one or more Google DAI asset keys: + +```json +{ + "type": "SSAI_DAI", + "daiAssetKeys": ["sports-main-1", "sports-main-2"] +} +``` + +Each DAI asset key can be used by at most one channel integration within an organization. Duplicate keys in one request are de-duplicated. A conflict with another channel integration returns HTTP `409`: + +```text +One or more daiAssetKeys are already used by another channel integration +``` + +Create an integration with the self-serve API: + +```bash +curl -X POST 'https://ads.example.com/api/v1/channels/sports-main/integrations' \ + -u "$ADS_API_KEY:$ADS_API_SECRET" \ + -H 'Content-Type: application/json' \ + -H 'X-Org-ID: org_123' \ + -d '{ + "type": "SSAI_DAI", + "daiAssetKeys": [ + "sports-main-1", + "sports-main-2" + ] + }' +``` + +### Best-effort fan-out + +At signal time, EABN snapshots the channel integration keys onto the break and fans the break out to each `daiAssetKey` through Google's by-asset-key ad break endpoint. The signals are best-effort: a failure for one key is logged and does not affect the primary signal or the other keys. An SSAI-only break has no `customAssetKey`, is not lifecycle-tracked, and never receives a `podId`. + +### Proxy cue injection + +For an HLS wallclock channel with an `SSAI_DAI` integration, the proxy finds active wallclock GAM pod breaks within the DVR window and injects `EXT-X-DATERANGE` cues into the media playlist. The injected cues contain `SCTE35-OUT` and `SCTE35-IN` data. After injection, the proxy changes the affected breaks from `READY` to `SIGNALED`. + +PTS channels receive passthrough manifests with no cue injection. A channel without an `SSAI_DAI` integration also receives a passthrough manifest with no ad cue injection. + +## Ad targeting parameters + +In V2, a vendor asset's optional `assetParameters` carry ad-tag and targeting parameters. OptiView Ads forwards them to Google as custom parameters during decisioning. + +For the player-side SDK `adTagParameters` usage, see [Ad tag parameters](../../how-to-guides/ad-tag-parameters). The player automatically adds `theoads_slot`. + +## Custom GAM creatives + +Dynamic backdrops and overlays require custom creative templates configured in the GAM console. See [Custom GAM creatives](../../how-to-guides/gam-custom-creatives). + +## Troubleshooting + +### Break status `ERROR` + +A break can be stored with status `ERROR` and: + +```text +Break passed its scheduling window before it could be signaled +``` + +This means the break missed its scheduling window because the remaining time fell below the decisioning margin, or the missed-break health check caught it. Schedule pod breaks at least the decisioning margin ahead of the live point and verify the EABN and Google configuration. + +### GAM configuration error + +Break creation returns HTTP `400` when the organization network code, service-account path, or channel custom asset key is missing: + +```text +Vendor asset of type GAM requires organization.google.networkCode, organization.google.serviceAccountPath and channel.customAssetKey to be configured +``` + +Verify all three values: + +- `organization.google.networkCode` +- `organization.google.serviceAccountPath` +- `channel.customAssetKey` + +### Pod break too close to live + +Break creation returns HTTP `400` when a pod break starts too close to the live point: + +```text +POD ad breaks must start at least ms after the live point to allow time for ad decisioning +``` + +The default `` is `5000`. + +### Break remains `PREPARING` + +If a break never leaves `PREPARING`, EABN may be skipping the signal because the organization is missing `networkCode` or `serviceAccountPath` at signal time. Check the organization Google configuration and confirm that the channel has the required delivery key: `customAssetKey` for SGAI, or an `SSAI_DAI` integration with `daiAssetKeys`. + +## Related resources + +- [Channels](../../concepts/channels) +- [Scheduling breaks](../../how-to-guides/scheduling-breaks) +- [API reference](/ads/api) diff --git a/ads/vendors/index.mdx b/ads/vendors/index.mdx new file mode 100644 index 000000000000..72d770cdfc52 --- /dev/null +++ b/ads/vendors/index.mdx @@ -0,0 +1,63 @@ +--- +sidebar_position: 1 +sidebar_label: Vendors +--- + +# Vendors + +A vendor is the ad decisioning or serving integration that OptiView Ads signals breaks to. Google Ad Manager 360 is the first supported vendor. + +Vendors are not standalone REST resources in Ads V2. A break variant carries a **vendor asset**, while vendor configuration is applied at the organization and channel levels. Self-serve channel, break, and integration APIs use HTTP Basic authentication with an API key and secret, plus the `X-Org-ID` header. Organization-level Google configuration is administrator-managed; see [Google Ad Manager](./google). + +## Vendor assets + +An asset with `"type": "vendor"` represents a vendor-delivered ad. The current vendor enum contains only `"gam"`. + +| Field | Type | Required/default | Description | +| ------------------ | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------ | +| `type` | string literal | Required: `"vendor"` | Selects the vendor asset type. Other asset types are `"static"` and `"vast"`. | +| `vendor` | enum | Required: `"gam"` | Identifies the ad vendor. | +| `uri` | string | Defaults to `"placeholder"` | Holds the vendor result. For a decisioned GAM pod, it is replaced with the Google `podId`. | +| `vendorParameters` | `Record` | Required | Vendor-specific parameters. GAM assets must include a `type` key whose current value is `"pod"`. | +| `assetParameters` | `Record` | Optional | Ad-tag and targeting parameters forwarded during decisioning. | + +Example GAM pod asset: + +```json +{ + "type": "vendor", + "vendor": "gam", + "uri": "placeholder", + "vendorParameters": { + "type": "pod" + } +} +``` + +## Supported vendors + +| Vendor | Enum value | Delivery | +| --------------------------------- | ---------- | ------------------------------------- | +| [Google Ad Manager 360](./google) | `gam` | SGAI pod serving and SSAI_DAI fan-out | + +The vendor model is extensible. When another vendor is supported, its documentation will be added as a separate page in this section and listed in the Vendors sidebar. + +## How vendors relate to the Ads V2 model + +| Resource | Relationship | +| -------------------------------------------- | -------------------------------------------------------------------------------------------- | +| [Channels](../concepts/channels) | Hold the channel-level `customAssetKey` used for Google server-guided pod serving. | +| [Breaks](../how-to-guides/scheduling-breaks) | Carry the vendor asset in a break variant. | +| Templates | Reusable break presets that can be scheduled on channels. See the [API reference](/ads/api). | +| Integrations | Configure channel-level delivery integrations such as `SSAI_DAI` and its `daiAssetKeys`. | + +Vendor assets are validated as part of break and template requests. A GAM vendor asset must use: + +```json +{ + "vendor": "gam", + "vendorParameters": { + "type": "pod" + } +} +``` diff --git a/ads_versioned_docs/version-v1/api/ads-client.swagger.json b/ads_versioned_docs/version-v1/api/ads-client.swagger.json new file mode 100644 index 000000000000..a56eacc2ae37 --- /dev/null +++ b/ads_versioned_docs/version-v1/api/ads-client.swagger.json @@ -0,0 +1,2005 @@ +{ + "components": { + "examples": {}, + "headers": {}, + "parameters": {}, + "requestBodies": {}, + "responses": {}, + "schemas": { + "IStatus": { + "properties": { + "status": { + "type": "string" + }, + "error": { + "type": "string", + "nullable": true + }, + "uptime": { + "type": "number", + "format": "double" + }, + "timestamp": { + "type": "number", + "format": "double" + }, + "version": { + "type": "string" + } + }, + "required": [ + "status", + "error", + "uptime", + "timestamp", + "version" + ], + "type": "object", + "additionalProperties": false + }, + "MonetizedStreamEvent": { + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "id": { + "type": "string" + }, + "monetizedStream": { + "type": "string" + } + }, + "required": [ + "name", + "description", + "startDate", + "endDate", + "id", + "monetizedStream" + ], + "type": "object", + "additionalProperties": false + }, + "PaginationDetails": { + "properties": { + "total_records": { + "type": "number", + "format": "double" + }, + "current_page": { + "type": "number", + "format": "double" + }, + "total_pages": { + "type": "number", + "format": "double" + }, + "next_page": { + "type": "number", + "format": "double", + "nullable": true + }, + "prev_page": { + "type": "number", + "format": "double", + "nullable": true + } + }, + "required": [ + "total_records", + "current_page", + "total_pages", + "next_page", + "prev_page" + ], + "type": "object", + "additionalProperties": false + }, + "StreamEventResponse": { + "properties": { + "monetizedStream": { + "type": "string" + }, + "data": { + "items": { + "$ref": "#/components/schemas/MonetizedStreamEvent" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "monetizedStream", + "data", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "ReturnType_typeofStreamEventUpdateSchema.validateSync_": { + "properties": { + "endDate": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "StreamEventUpdate": { + "$ref": "#/components/schemas/ReturnType_typeofStreamEventUpdateSchema.validateSync_" + }, + "IStreamEvent": { + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string" + } + }, + "required": [ + "name", + "description", + "startDate", + "endDate" + ], + "type": "object", + "additionalProperties": false + }, + "ResourceType.MONETIZED_STREAM": { + "enum": [ + "monetized-stream" + ], + "type": "string" + }, + "MonetizedStreamType": { + "enum": [ + "LIVE", + "VOD" + ], + "type": "string" + }, + "MonetizedStreamLayout": { + "enum": [ + "SINGLE", + "DOUBLE", + "LSHAPE_AD", + "LSHAPE_CONTENT" + ], + "type": "string" + }, + "DynamicDetail": { + "properties": {}, + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "AssetKeyDetails": { + "properties": { + "podDuration": { + "type": "number", + "format": "double" + }, + "eabnAdTagParameters": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "adBreakSelectionParameters": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "assetKey": { + "type": "string" + } + }, + "required": [ + "assetKey" + ], + "type": "object" + }, + "IGamProperties": { + "properties": { + "iu": { + "type": "string" + }, + "sz": { + "properties": { + "tv": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "default": { + "type": "string" + } + }, + "required": [ + "default" + ], + "type": "object" + } + }, + "required": [ + "iu", + "sz" + ], + "type": "object", + "additionalProperties": false + }, + "EABNVersion": { + "enum": [ + "V1", + "V2" + ], + "type": "string" + }, + "BumperAdDetails": { + "properties": { + "eabnAdTagParameters": { + "$ref": "#/components/schemas/DynamicDetail" + } + }, + "type": "object" + }, + "IMonetizedStream": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "streamId": { + "type": "string" + }, + "streamType": { + "$ref": "#/components/schemas/MonetizedStreamType" + }, + "origin": { + "type": "string" + }, + "segmentOrigin": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "assetKey": { + "type": "string" + }, + "networkCode": { + "type": "string" + }, + "originPath": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "backdropURIGamProperties": { + "$ref": "#/components/schemas/IGamProperties" + } + }, + "required": [ + "streamId", + "layout" + ], + "type": "object", + "additionalProperties": false + }, + "IMonetizedStreamApiResponse": { + "properties": { + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted" + ] + }, + "type": { + "$ref": "#/components/schemas/ResourceType.MONETIZED_STREAM" + }, + "payload": { + "$ref": "#/components/schemas/IMonetizedStream" + } + }, + "required": [ + "id", + "state", + "type", + "payload" + ], + "type": "object", + "additionalProperties": false + }, + "ResourceType.MONETIZED_STREAM_REFERENCE_TIME": { + "enum": [ + "monetized-stream-reference-time" + ], + "type": "string" + }, + "IMonetizedStreamReferenceTime": { + "properties": { + "date": { + "type": "string" + }, + "pts": { + "type": "number", + "format": "double" + }, + "engineId": { + "type": "string" + } + }, + "required": [ + "date", + "pts" + ], + "type": "object", + "additionalProperties": false + }, + "IMonetizedStreamReferenceTimeApiResponse": { + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ResourceType.MONETIZED_STREAM_REFERENCE_TIME" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted", + "updated" + ] + }, + "payload": { + "$ref": "#/components/schemas/IMonetizedStreamReferenceTime" + } + }, + "required": [ + "id", + "type", + "state", + "payload" + ], + "type": "object", + "additionalProperties": false + }, + "ScteParams": { + "properties": { + "uniqueProgramId": { + "type": "number", + "format": "double" + }, + "segmentationTypeId": { + "type": "number", + "format": "double" + }, + "eabnAdTagParameters": { + "$ref": "#/components/schemas/DynamicDetail" + } + }, + "type": "object", + "additionalProperties": false + }, + "IMonetizedStreamEarlyReturn": { + "properties": { + "date": { + "type": "string" + }, + "pts": { + "type": "number", + "format": "double" + }, + "id": { + "type": "string" + }, + "duration": { + "type": "string" + }, + "spliceEvent": { + "$ref": "#/components/schemas/ScteParams" + }, + "engineId": { + "type": "string" + } + }, + "required": [ + "date" + ], + "type": "object", + "additionalProperties": false + }, + "IMonetizedStreamUpdate": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "streamType": { + "$ref": "#/components/schemas/MonetizedStreamType" + }, + "origin": { + "type": "string" + }, + "segmentOrigin": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "originPath": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "backdropURIGamProperties": { + "$ref": "#/components/schemas/IGamProperties" + } + }, + "type": "object", + "additionalProperties": false + }, + "ResourceType.AD_BREAK": { + "enum": [ + "adbreak" + ], + "type": "string" + }, + "AdBreakInsertionType": { + "enum": [ + "REPLACE", + "INSERT" + ], + "type": "string" + }, + "SignaledAdBreakStatus": { + "enum": [ + "new", + "future", + "signaled", + "finished", + "error", + "continuous" + ], + "type": "string" + }, + "AdbreakInternalType": { + "enum": [ + "BUMPER", + "ORIGINAL" + ], + "type": "string" + }, + "LifeCycle": { + "enum": [ + "NEW", + "FUTURE", + "ERROR", + "SCHEDULED", + "DECISIONED", + "COMPLETED", + "ENDED" + ], + "type": "string" + }, + "IAdBreak": { + "properties": { + "id": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "duration": { + "type": "number", + "format": "double" + }, + "source": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "assetURI": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SignaledAdBreakStatus" + }, + "asset_key": { + "type": "string" + }, + "network_code": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "backdropURIGamProperties": { + "$ref": "#/components/schemas/IGamProperties" + }, + "startPts": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "startDate" + ], + "type": "object", + "additionalProperties": false + }, + "IAdBreakApiResponse": { + "properties": { + "payload": { + "$ref": "#/components/schemas/IAdBreak" + }, + "type": { + "$ref": "#/components/schemas/ResourceType.AD_BREAK" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted" + ] + }, + "id": { + "type": "string" + } + }, + "required": [ + "payload", + "type", + "state", + "id" + ], + "type": "object" + }, + "IAdBreaksApiResponse": { + "properties": { + "payload": { + "items": { + "$ref": "#/components/schemas/IAdBreak" + }, + "type": "array" + }, + "type": { + "$ref": "#/components/schemas/ResourceType.AD_BREAK" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted" + ] + }, + "id": { + "type": "string" + } + }, + "required": [ + "payload", + "type", + "state", + "id" + ], + "type": "object" + }, + "IAdBreakUpdate": { + "properties": { + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "duration": { + "type": "number", + "format": "double" + }, + "source": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "assetURI": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SignaledAdBreakStatus" + }, + "asset_key": { + "type": "string" + }, + "network_code": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "backdropURIGamProperties": { + "$ref": "#/components/schemas/IGamProperties" + }, + "startPts": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "OverlayTypes": { + "enum": [ + "image" + ], + "type": "string" + }, + "OverlayURITypes": { + "enum": [ + "png", + "svg" + ], + "type": "string" + }, + "IOverlay": { + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "type": { + "$ref": "#/components/schemas/OverlayTypes" + }, + "position": { + "properties": { + "bottom": { + "type": "number", + "format": "double" + }, + "top": { + "type": "number", + "format": "double" + }, + "right": { + "type": "number", + "format": "double" + }, + "left": { + "type": "number", + "format": "double" + } + }, + "type": "object" + }, + "size": { + "properties": { + "height": { + "type": "number", + "format": "double" + }, + "width": { + "type": "number", + "format": "double" + } + }, + "required": [ + "height", + "width" + ], + "type": "object" + }, + "opacity": { + "type": "number", + "format": "double" + }, + "gamProperties": { + "$ref": "#/components/schemas/IGamProperties" + }, + "resourceURI": { + "items": { + "properties": { + "tv": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "default": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OverlayURITypes" + } + }, + "required": [ + "default", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "clickThroughURI": { + "type": "string" + } + }, + "required": [ + "id", + "startDate", + "duration", + "type", + "position", + "size" + ], + "type": "object", + "additionalProperties": false + }, + "IOverlayApiResponse": { + "properties": { + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted" + ] + }, + "type": { + "type": "string", + "enum": [ + "overlay" + ], + "nullable": false + }, + "payload": { + "$ref": "#/components/schemas/IOverlay" + } + }, + "required": [ + "id", + "state", + "type", + "payload" + ], + "type": "object", + "additionalProperties": false + }, + "IOverlaysApiResponse": { + "properties": { + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "created", + "deleted" + ] + }, + "type": { + "type": "string", + "enum": [ + "overlay" + ], + "nullable": false + }, + "payload": { + "items": { + "$ref": "#/components/schemas/IOverlay" + }, + "type": "array" + } + }, + "required": [ + "id", + "state", + "type", + "payload" + ], + "type": "object", + "additionalProperties": false + }, + "ScteParamsResponse": { + "properties": { + "uniqueProgramId": { + "type": "number", + "format": "double" + }, + "segmentationTypeId": { + "type": "number", + "format": "double" + }, + "eabnAdTagParameters": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "IScteParamMappingApiResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/ScteParamsResponse" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object", + "additionalProperties": false + }, + "MonetizedStreamApiResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/IMonetizedStream" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "data", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "AdbreakApiResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/IAdBreak" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "data", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "OverlayApiResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/IOverlay" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "data", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "ReturnType_typeofISharedTemplateSchema.validateSync_": { + "properties": { + "metadata": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "backdropURI": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "source": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "ISharedTemplateType": { + "$ref": "#/components/schemas/ReturnType_typeofISharedTemplateSchema.validateSync_" + }, + "ReturnType_typeofTemplateAssignmentSchema.validateSync_": { + "properties": { + "templateId": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "TemplateAssignmentType": { + "$ref": "#/components/schemas/ReturnType_typeofTemplateAssignmentSchema.validateSync_" + }, + "ReturnType_typeofAssignedTemplateDataSchema.validateSync_": { + "properties": { + "notReadyReason": { + "type": "string" + }, + "isReady": { + "type": "boolean" + }, + "metadata": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "backdropURI": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "source": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "AssignedTemplateDataType": { + "$ref": "#/components/schemas/ReturnType_typeofAssignedTemplateDataSchema.validateSync_" + }, + "TemplateAssignmentMultipleResponse": { + "properties": { + "eventId": { + "type": "string" + }, + "monetizedStream": { + "type": "string" + }, + "templates": { + "items": { + "$ref": "#/components/schemas/AssignedTemplateDataType" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "eventId", + "monetizedStream", + "templates", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "ReturnType_typeofTemplateAssignmentSingleResponseSchema.validateSync_": { + "properties": { + "template": { + "properties": { + "id": { + "type": "string" + }, + "notReadyReason": { + "type": "string" + }, + "isReady": { + "type": "boolean" + }, + "metadata": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "backdropURI": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "source": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "monetizedStream": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "TemplateAssignmentSingleResponseType": { + "$ref": "#/components/schemas/ReturnType_typeofTemplateAssignmentSingleResponseSchema.validateSync_" + }, + "ReturnType_typeofAssignedTemplateDataUpdateSchema.validateSync_": { + "properties": { + "metadata": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "backdropURI": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "source": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "AssignedTemplateDataUpdateType": { + "$ref": "#/components/schemas/ReturnType_typeofAssignedTemplateDataUpdateSchema.validateSync_" + }, + "ReturnType_typeofSharedTemplateSchema.validateSync_": { + "properties": { + "id": { + "type": "string" + }, + "notReadyReason": { + "type": "string" + }, + "isReady": { + "type": "boolean" + }, + "metadata": { + "$ref": "#/components/schemas/DynamicDetail" + }, + "backdropURI": { + "type": "string" + }, + "assetURI": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "source": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "description": "Obtain the return type of a function type" + }, + "SharedTemplateType": { + "$ref": "#/components/schemas/ReturnType_typeofSharedTemplateSchema.validateSync_" + }, + "SharedTemplateResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/SharedTemplateType" + }, + "type": "array" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationDetails" + } + }, + "required": [ + "data", + "pagination" + ], + "type": "object", + "additionalProperties": false + }, + "ISharedTemplate": { + "properties": { + "name": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "source": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "assetURI": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "metadata": { + "properties": {}, + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "ISharedTemplateUpdate": { + "properties": { + "duration": { + "type": "number", + "format": "double" + }, + "source": { + "type": "string" + }, + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + }, + "assetURI": { + "type": "string" + }, + "backdropURI": { + "type": "string" + }, + "metadata": { + "properties": {}, + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "securitySchemes": { + "apiKeyAuth": { + "type": "apiKey", + "name": "X-API-KEY", + "in": "header" + } + } + }, + "info": { + "title": "THEOads Api", + "version": "25.9.0", + "description": "The THEOads REST API allows you to create and manage monetized streams, which are deployments of signaling services. Each monetized stream corresponds to an origin stream that is monetized using THEOads (SGAI). For detailed information on the various parameters and their usage, please refer to the getting started guide.", + "license": { + "name": "UNLICENSED" + }, + "contact": {} + }, + "openapi": "3.0.0", + "paths": { + "/v1/monetized-streams": { + "get": { + "operationId": "GetMonetizedStreams", + "responses": { + "200": { + "description": "List of monetized streams", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + }, + "type": "array" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Retrieves all created and deployed monetized streams (signaling services) along with their configurations.", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [] + }, + "post": { + "operationId": "CreateMonetizedStream", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + } + } + } + } + }, + "description": "Creates and deploys a new monetized stream in the environment, resulting in a deployment of a signaling service.", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [], + "requestBody": { + "description": "The monetized stream configuration", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStream", + "description": "The monetized stream configuration" + } + } + } + } + } + }, + "/v1/monetized-streams/{monetizedStreamId}": { + "get": { + "operationId": "GetMonetizedStream", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + } + } + } + } + }, + "description": "Retrieves a single monetized stream (signaling service) based on its identifier, along with its configuration details.", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "patch": { + "operationId": "UpdateMonetizedStream", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + } + } + } + } + }, + "description": "Updates a single monetized stream (signaling service) based on its identifier, along with its configuration update.", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The monetized stream configuration update", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamUpdate", + "description": "The monetized stream configuration update" + } + } + } + } + }, + "delete": { + "operationId": "DeleteMonetizedStream", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + } + } + } + } + }, + "description": "Deletes a single monetized stream (signaling service) based on its identifier.", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/v1/monetized-streams/{monetizedStreamId}/layout": { + "patch": { + "operationId": "UpdateMonetizedStreamLayout", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMonetizedStreamApiResponse" + } + } + } + } + }, + "description": "Updates the default layout for an existing monetized stream (signaling service).", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The layout configuration", + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "layout": { + "$ref": "#/components/schemas/MonetizedStreamLayout" + } + }, + "required": [ + "layout" + ], + "type": "object", + "description": "The layout configuration" + } + } + } + } + } + }, + "/v1/monetized-streams/{monetizedStreamId}/break": { + "post": { + "operationId": "CreateMonetizedStreamBreak", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/IAdBreakApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Creates an ad break on an existing monetized stream (signaling service).", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The ad break configuration", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IAdBreak", + "description": "The ad break configuration" + } + } + } + } + }, + "get": { + "operationId": "GetMonetizedStreamBreaks", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/IAdBreaksApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Retrieves all adbreaks linked to the monetized sctream", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/v1/monetized-streams/{monetizedStreamId}/break/{adbreakId}": { + "delete": { + "operationId": "DeleteMonetizedStreamBreaks", + "responses": { + "200": { + "description": "An abreak", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IAdBreakApiResponse" + } + } + } + } + }, + "description": "Deletes an adbreak linked to the monetized sctream", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The adbreak identifier.", + "in": "path", + "name": "adbreakId", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "patch": { + "operationId": "UpdateMonetizedStreamBreaks", + "responses": { + "200": { + "description": "An adbreak", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IAdBreakApiResponse" + } + } + } + } + }, + "description": "Patches an adbreak linked to the monetized sctream", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The adbreak identifier.", + "in": "path", + "name": "adbreakId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IAdBreakUpdate" + } + } + } + } + } + }, + "/v1/monetized-streams/{monetizedStreamId}/overlay": { + "post": { + "operationId": "CreateMonetizedStreamOverlay", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IOverlayApiResponse" + } + } + } + } + }, + "description": "Creates an overlay on an existing monetized stream (signaling service).", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The overlay configuration", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IOverlay", + "description": "The overlay configuration" + } + } + } + } + }, + "get": { + "operationId": "GetMonetizedStreamOverlays", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/IOverlaysApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Retrieves all overlays linked to the monetized sctream", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/v1/monetized-streams/{monetizedStreamId}/overlay/{overlayId}": { + "delete": { + "operationId": "DeleteMonetizedStreamOverlay", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IOverlayApiResponse" + } + } + } + } + }, + "description": "Deletes an overlay linked to the monetized sctream", + "tags": [ + "Monetized-Stream" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "overlayId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/v2/monetized-streams": { + "get": { + "operationId": "GetMonetizedStreamsPaginated", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/MonetizedStreamApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "tags": [ + "Monetized-Stream-V2" + ], + "security": [], + "parameters": [ + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "skip", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/v2/monetized-streams/{monetizedStreamId}/break": { + "get": { + "operationId": "GetMonetizedStreamBreaksPaginated", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/AdbreakApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Retrieves all adbreaks linked to the monetized stream", + "tags": [ + "Monetized-Stream-V2" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "skip", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/v2/monetized-streams/{monetizedStreamId}/overlay": { + "get": { + "operationId": "GetMonetizedStreamOverlaysPaginated", + "responses": { + "200": { + "description": "A monetized stream", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/OverlayApiResponse" + }, + { + "type": "string" + } + ] + } + } + } + } + }, + "description": "Retrieves all overlays linked to the monetized sctream", + "tags": [ + "Monetized-Stream-V2" + ], + "security": [], + "parameters": [ + { + "description": "The Monetized stream identifier.", + "in": "path", + "name": "monetizedStreamId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "skip", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + } + }, + "servers": [ + { + "url": "/ads-client/api" + } + ] +} \ No newline at end of file diff --git a/ads_versioned_docs/version-v1/api/signaling/.gitignore b/ads_versioned_docs/version-v1/api/signaling/.gitignore new file mode 100644 index 000000000000..7c5ca8c38d5f --- /dev/null +++ b/ads_versioned_docs/version-v1/api/signaling/.gitignore @@ -0,0 +1,3 @@ +# Auto-generated by `docusaurus gen-api-docs` +**/*.mdx +**/sidebar.ts diff --git a/ads_versioned_docs/version-v1/assets/img/ads_formats.svg b/ads_versioned_docs/version-v1/assets/img/ads_formats.svg new file mode 100644 index 000000000000..b9fe8733afe1 --- /dev/null +++ b/ads_versioned_docs/version-v1/assets/img/ads_formats.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ads_versioned_docs/version-v1/assets/img/backdrop-doublebox-template.png b/ads_versioned_docs/version-v1/assets/img/backdrop-doublebox-template.png new file mode 100644 index 000000000000..59b5a2e07ccd Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/backdrop-doublebox-template.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/backdrop-lshape-template.png b/ads_versioned_docs/version-v1/assets/img/backdrop-lshape-template.png new file mode 100644 index 000000000000..f960848aae70 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/backdrop-lshape-template.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/gam_image_1.png b/ads_versioned_docs/version-v1/assets/img/gam_image_1.png new file mode 100644 index 000000000000..6dcde35ad0ee Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/gam_image_1.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/gam_image_2.png b/ads_versioned_docs/version-v1/assets/img/gam_image_2.png new file mode 100644 index 000000000000..2cdf534adf21 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/gam_image_2.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/gam_image_3.png b/ads_versioned_docs/version-v1/assets/img/gam_image_3.png new file mode 100644 index 000000000000..ec0b4c6288cc Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/gam_image_3.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/gam_image_4.png b/ads_versioned_docs/version-v1/assets/img/gam_image_4.png new file mode 100644 index 000000000000..7cbe66748fbc Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/gam_image_4.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/gam_image_5.png b/ads_versioned_docs/version-v1/assets/img/gam_image_5.png new file mode 100644 index 000000000000..8a819acf8a63 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/gam_image_5.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/impressions.png b/ads_versioned_docs/version-v1/assets/img/impressions.png new file mode 100644 index 000000000000..b914182bc340 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/impressions.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu-google.png b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu-google.png new file mode 100644 index 000000000000..924ad0000c48 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu-google.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu.png b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu.png new file mode 100644 index 000000000000..4a2528560f16 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-menu.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection-google.png b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection-google.png new file mode 100644 index 000000000000..04a01bcd3644 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection-google.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection.png b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection.png new file mode 100644 index 000000000000..350c0e91ddaf Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/ios-swiftpm-product-selection.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/ios-xcode-file-menu.png b/ads_versioned_docs/version-v1/assets/img/ios-xcode-file-menu.png new file mode 100644 index 000000000000..fe6a617c34ed Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/ios-xcode-file-menu.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/optiview_ads_architecture.png b/ads_versioned_docs/version-v1/assets/img/optiview_ads_architecture.png new file mode 100644 index 000000000000..d17ff66cc8f7 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/optiview_ads_architecture.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/web-ui.png b/ads_versioned_docs/version-v1/assets/img/web-ui.png new file mode 100644 index 000000000000..2b24e27dd9dc Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/web-ui.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-after.png b/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-after.png new file mode 100644 index 000000000000..70f376574b2a Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-after.png differ diff --git a/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-before.png b/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-before.png new file mode 100644 index 000000000000..8f2f57dd07b0 Binary files /dev/null and b/ads_versioned_docs/version-v1/assets/img/workflow-optiview-ads-before.png differ diff --git a/ads_versioned_docs/version-v1/callouts/_rebranding_notice.md b/ads_versioned_docs/version-v1/callouts/_rebranding_notice.md new file mode 100644 index 000000000000..fc5195a85b22 --- /dev/null +++ b/ads_versioned_docs/version-v1/callouts/_rebranding_notice.md @@ -0,0 +1,5 @@ +:::info OptiView Rebranding + +OptiView Ads is the new name for THEOads as part of the OptiView product suite. During the transition, you may still see references to THEOads. OptiView Ads and THEOads refer to the same product. + +::: diff --git a/ads/getting-started/android.mdx b/ads_versioned_docs/version-v1/getting-started/android.mdx similarity index 100% rename from ads/getting-started/android.mdx rename to ads_versioned_docs/version-v1/getting-started/android.mdx diff --git a/ads/getting-started/chromecast.mdx b/ads_versioned_docs/version-v1/getting-started/chromecast.mdx similarity index 100% rename from ads/getting-started/chromecast.mdx rename to ads_versioned_docs/version-v1/getting-started/chromecast.mdx diff --git a/ads_versioned_docs/version-v1/getting-started/index.mdx b/ads_versioned_docs/version-v1/getting-started/index.mdx new file mode 100644 index 000000000000..3bcac379b49a --- /dev/null +++ b/ads_versioned_docs/version-v1/getting-started/index.mdx @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +sidebar_label: Getting Started +--- + +# Getting started with OptiView Ads + +import RebrandingNotice from '../callouts/_rebranding_notice.md'; + + + +These guides provide the steps required to get started with OptiView Ads. They cover the deployment and API integration of the Signaling Service into your existing workflow, as well as the integration of OptiView Ads into your application using various OptiView Player SDKs. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/ads/getting-started/ios.mdx b/ads_versioned_docs/version-v1/getting-started/ios.mdx similarity index 100% rename from ads/getting-started/ios.mdx rename to ads_versioned_docs/version-v1/getting-started/ios.mdx diff --git a/ads/getting-started/react-native.mdx b/ads_versioned_docs/version-v1/getting-started/react-native.mdx similarity index 100% rename from ads/getting-started/react-native.mdx rename to ads_versioned_docs/version-v1/getting-started/react-native.mdx diff --git a/ads_versioned_docs/version-v1/getting-started/signaling-service.mdx b/ads_versioned_docs/version-v1/getting-started/signaling-service.mdx new file mode 100644 index 000000000000..af904930bb28 --- /dev/null +++ b/ads_versioned_docs/version-v1/getting-started/signaling-service.mdx @@ -0,0 +1,263 @@ +--- +sidebar_position: 2 +sidebar_label: Signaling service +sidebar_custom_props: { 'icon': '🛜' } +--- + +# Signaling service + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +The Signaling Service is provided as a service and can be integrated into any existing content management workflow through its APIs, enabling seamless adoption without significant changes to your current setup. + +Developed and provided by Dolby OptiView, this service operates on your infrastructure, integrated between your CDN and media origin. It performs manifest manipulation to insert ad breaks and additional metadata for the player. Additionally, it ensures scalability of the end-to-end system through early ad break notifications to ad decisioning servers. + + + +## Infrastructure integration + +The service is deployed and operated by Dolby OptiView within the customer’s infrastructure. This setup ensures smooth integration into the existing video workflow with minimal disruption. During onboarding, Dolby OptiView provides the specific infrastructure and network requirements. Once set up, Dolby OptiView bootstraps the service, after which the customer can manage their streams and monitor the service through the REST API. + +In collaboration with the customer and Dolby OptiView's solutions team, the integration of the service into the existing video workflow is designed. Deploying the Signaling Service between the CDN and Origin ensures that regionalization, security, and localization features remain unaffected. + +> To ensure high availability, we recommend maintaining the original origin stream on a CDN as a backup, while the Signaling Service provides redundancy and failover capabilities to further enhance reliability. + +## Monetized streams + +After deployment of the Signaling Service has been completed, the next step is the creation of monetized streams. A monetized stream represents an instance of a origin stream that is processed by the Signaling Service to enable OptiView Ads for this origin stream. Created via the locally deployed REST API, it exposes a standardized HTTPS (or HTTP) endpoint for the CDN to fetch the augmented manifest. This setup ensures seamless ad insertion without needing CDN reconfiguration, even if the monetized stream is stopped and recreated. + +The monetized stream holds the following information: + +- `streamId`: Unique identifier for the monetized stream within the environment. +- `name`: Self defined descriptive name for the monetized stream. +- `description`: Optional descriptive information for the monetized stream. +- `labels`: Array of self defined labels (string). +- `layout`: Default experience layout, see [ad experience layout](/ads/how-to-guides/override-layout/). +- `origin`: Your media origin host from where the origin manifests are loaded. +- `segmentOrigin`: Your publicly available segments origin host from where the stream's video and audio segments are hosted. In most cases this is identical to the `origin` parameter. +- `assetKey`: Optional Google DAI Asset-Key linked to this stream, see [Google DAI](https://support.google.com/admanager/topic/7062524?hl=en). +- `networkCode`: Optional Google DAI Network-Code, see [Google DAI](https://support.google.com/admanager/topic/7062524?hl=en). +- `assetURI`: Optional default custom asset URI which is to be used during ad breaks. If not set it will request an ad break through Google Pod Serving using the `assetKey` and `networkCode` parameters. +- `backdropURI`: Optional URI containing the default backdrop to be used during the Double Box or L-shape ads. +- `backdropURIGamProperties`: Optional property with configuration values for a dynamic backdrop loaded via GAM to be used during the Double Box or L-shape ads. This property has priority over `backdropURI`. +- `streamType`: Optional property to specify this stream's type to be either 'LIVE' (the default) or 'VOD'. Note: this is only relevant for scheduling overlays for now and mostly takes care of expiry of scheduled ad breaks for 'LIVE'. + +```json +{ + "streamId": "optiview-ads-demo", + "name": "OptiView Ads Demo", + "description": "SGAI OptiView Ads Demo", + "labels": [], + "layout": "DOUBLE", + "origin": "https://domain.com", + "segmentOrigin": "https://segment-domain.com", + "assetKey": "google-sgai-demo", + "networkCode": "12345", + "assetURI": "https://asset.m3u8", + "backdropURI": "https://backdrop.svg" +} +``` + +### Creating a new monetized stream + +The following API endpoint creates a bare minimal new monetized stream with GAM360 Pod serving for the ad breaks using the 'SINGLE' layout: + +```bash +curl -L 'https:///ads-client/api/v1/monetized-streams' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d '{ + "streamId": "string", + "name": "string", + "layout": "SINGLE", + "origin": "string", + "segmentOrigin": "string", + "assetKey": "string", + "networkCode": "string", + }' +``` + +Alternatively, you can use the assetURI variant when GAM360 Pod serving is not required: + +```bash +curl -L 'https:///ads-client/api/v1/monetized-streams' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d '{ + "streamId": "string", + "name": "string", + "layout": "SINGLE", + "origin": "string", + "segmentOrigin": "string", + "assetURI": "string" + }' +``` + +### Updating an existing monetized stream + +The following API endpoint updates the properties of an existing monetized stream based on its `streamId`: + +```bash +curl -L -X PATCH 'https:///ads-client/api/v1/monetized-streams/:streamId' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d '{ + "streamId": "string", + "name": "string", + "description": "string", + "labels": [ + "string" + ], + "origin": "string", + "segmentOrigin": "string", + "assetKey": "string", + "networkCode": "string", + "assetURI": "string" + }' +``` + +### Deleting an existing monetized stream + +The following API endpoint deletes an existing monetized stream based on its `streamId`. + +```bash +curl -L -X DELETE 'https:///ads-client/api/v1/monetized-streams/:streamId' \ + -H 'Accept: application/json' +``` + +### Retrieving all monetized streams + +The following API endpoint returns all existing monetized streams in the deployed environment. + +```bash +curl -X GET 'https:///ads-client/api/v1/monetized-streams' \ + -H 'accept: application/json' +``` + +The response is an array of the existing monetized stream resources. + +```json +[ + { + "id": "optiview-ads-demo", + "payload": { + "streamId": "optiview-ads-demo", + "name": "OptiView Ads Demo", + "description": "SGAI OptiView Ads Demo", + "labels": [], + "layout": "DOUBLE", + "origin": "https://domain.com", + "segmentOrigin": "https://segment-domain.com", + "assetKey": "google-sgai-demo", + "networkCode": "12345" + }, + "state": "created", + "type": "monetized-stream" + } +] +``` + +### Retrieving an individual monetized stream + +The following API endpoint returns an existing monetized stream based on its identifier (`streamId`). + +```bash +curl -L 'https:///ads-client/api/v1/monetized-streams/:streamId' \ + -H 'Accept: application/json' +``` + +The response is the monetized stream resource. + +```json +{ + "id": "optiview-ads-demo", + "payload": { + "streamId": "optiview-ads-demo", + "name": "OptiView Ads Demo", + "description": "SGAI OptiView Ads Demo", + "labels": [], + "layout": "DOUBLE", + "origin": "https://domain.com", + "segmentOrigin": "https://segment-domain.com", + "assetKey": "google-sgai-demo", + "networkCode": "12345" + }, + "state": "created", + "type": "monetized-stream" +} +``` + +Please refer to the [API reference](/ads/api/signaling/theoads-api/) for even more detailed information on the REST API. + +### Player source + +When playing an OptiView Ads source corresponding to a monetized stream, it is expected to pass a source that looks like this: + +```js +src: 'https:///signaling-service/api/v1//hls/MANIFEST-URI'; +``` + +In this URI, the `` points to the network endpoint where the OptiView Ads service is deployed, preferably a DNS entry pointing to the service IP endpoint and reachable from the CDN. +Secondly, the `` corresponds to the `streamId` for the monetized stream. +Finally, the `MANIFEST-URI` part points to the origin's manifest relative to the configured `origin` property of the monetized stream. The signaling service will concatenate the `origin` property and the MANIFEST-URI to build the origin manifest URI. +For example: + +```js +src: 'https:///signaling-service/api/v1//hls/manifest.m3u8'; +``` + +Segment URLs in the media playlists should be absolute URLs however. Segment requests don't need to pass through the Signaling Service but should be fetched directly from the CDN to the origin so as to keep the benefit of scaling via the CDN. +The `segmentOrigin` parameter should contain this publicly available endpoint to fetch the segments directly. The signaling service will concatenate the `segmentOrigin` parameter with the segment URIs in the playlists to build an absolute segment URI. + +## Scheduling ad breaks + +Once all the required monetized streams are configured, the next step is scheduling ad breaks for these monetized streams. +To accurately schedule ad breaks, the origin manifest must be valid and include date and time indications. +For HLS, this means the `EXT-X-PROGRAM-DATE-TIME` tag must be present. +Ad breaks can be signaled through either the provided REST API or by including the relevant information in the manifest itself. + +### Manifest + +When using manifest signaling, the following tags are supported: + +- `#EXT-X-DATERANGE` tag + - this is recommended because of its standardization and ability to provide more comprehensive data for improved integration. +- `#EXT-X-OATCLS-SCTE35` tag +- `#EXT-X-CUE-OUT` and `#EXT-X-CUE-OUT-CONT` tags + +Optionally, SCTE markers can be included with the tags for extra metadata. + +### REST API + +For scheduling ad breaks through the REST API, please refer to the [API definitions](/ads/api/signaling/create-monetized-stream-break/) and the example below. + +```bash +curl --location 'https:///ads-client/api/v1/monetized-streams/stream-1/break' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--data '{ + "id": "626cd35a-4fbf-48b8-b0cd-acc246266f88", + "startDate": "2024-09-03T08:00:00.000Z", + "duration": 60, + "source": "", + "layout": "LSHAPE_AD" +}' +``` + +For more in depth information on scheduling ad breaks we refer to our [How-to guide: Scheduling breaks](/ads/how-to-guides/scheduling-breaks/) + +For more information on this topic we refer to our [workflow integration](/ads/how-to-guides/workflow-integration/). + +# More information + +- [API reference](/ads/api/signaling/theoads-api/) +- [What is OptiView Ads?](https://optiview.dolby.com/products/server-guided-ad-insertion/) +- [The Advantages of Server-Guided Ad Insertion](https://optiview.dolby.com/solutions/personalized-advertising/) +- [Is Server-Guided Ad-Insertion (SGAI) revolutionizing streaming monetization? (blog)](https://optiview.dolby.com/resources/blog/advertising/what-is-sgai-server-guided-ad-insertion-in-streaming/) diff --git a/ads/getting-started/web.mdx b/ads_versioned_docs/version-v1/getting-started/web.mdx similarity index 100% rename from ads/getting-started/web.mdx rename to ads_versioned_docs/version-v1/getting-started/web.mdx diff --git a/ads_versioned_docs/version-v1/how-to-guides/ad-impressions.md b/ads_versioned_docs/version-v1/how-to-guides/ad-impressions.md new file mode 100644 index 000000000000..0eb26f798c83 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/ad-impressions.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 2 +--- + +# Ad impression tracking + +When inserting an ad experience using OptiView Ads, the player sends a beacon to OptiView's server to track the ad impression usage. An ad impression refers to each ad experience inserted into the content and presented to the viewer using OptiView Ads, counted from the first presentation of the experience. + +:::note +The ad impression is beaconed simultaneously with the firing of the player's `adbreakbegin` event. +::: + +## Information tracked + +The information passed only contains an identifier of the OptiView Player license build so the impressions are linked to the customer. It also includes the ad experience type. +No information about the viewers is passed along. + +## View my impression usage + +You can view your ad impressions on the portal dashboard underneath the OptiView Player license impressions. + +![Portal dashboard](../assets/img/impressions.png) diff --git a/ads_versioned_docs/version-v1/how-to-guides/ad-tag-parameters.md b/ads_versioned_docs/version-v1/how-to-guides/ad-tag-parameters.md new file mode 100644 index 000000000000..5cb6d7e534b5 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/ad-tag-parameters.md @@ -0,0 +1,109 @@ +--- +sidebar_position: 3 +--- + +# Ad targeting parameters + +With the Google DAI Pod Serving, there is the possibility to pass [a limited set of ad targeting parameters](https://support.google.com/admanager/answer/7320899) on your stream request to personalize your streams. +In the player you can easily pass these parameters as key-value pairs via the `TheoAdDescription`. + +:::note +The player will automatically add the custom parameter `theoads_slot` in the `cust_params` field when requesting an ad. +The value of this parameter will be determined by the layout mode. +::: + +## Web SDK + +For the Web SDK, you can set your desired ad tag parameters via `adTagParameters` in the `TheoAdDescription` as followed: + +```javascript +player.source = { + sources: { + src: 'PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + adTagParameters: { + "YOUR-AD-TAG-PARAMETER-1" : "VALUE-1", // e.g. "cust_params" : "YOUR-CUSTOM-PARAMETERS", + "YOUR-AD-TAG-PARAMETER-2" : "VALUE-2", + ... + } + }, + ], +}; +``` + +## React Native SDK + +For the React Native SDK, you can set your desired ad tag parameters via `adTagParameters` in the `TheoAdDescription` as followed: + +```javascript +player.source = { + sources: { + src: 'PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + adTagParameters: { + "YOUR-AD-TAG-PARAMETER-1" : "VALUE-1", // e.g. "cust_params" : "YOUR-CUSTOM-PARAMETERS", + "YOUR-AD-TAG-PARAMETER-2" : "VALUE-2", + ... + } + }, + ], +}; +``` + +## Android SDK + +For the Android SDK, you can set your desired ad tag parameters via `adTagParameters` in the `TheoAdDescription` as followed: + +```kotlin +theoPlayerView.player.source = SourceDescription.Builder( + TypedSource.Builder("PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI") + .type(SourceType.HLS) + .hlsDateRange(true) + .build() +).ads( + TheoAdDescription( + networkCode = "NETWORK-CODE", + customAssetKey = "CUSTOM-ASSET-KEY", + adTagParameters = mapOf( + "YOUR-AD-TAG-PARAMETER-1" to "VALUE-1", + "YOUR-AD-TAG-PARAMETER-2" to "VALUE-2" + ) + ) +) +.build() +``` + +## iOS SDK + +For the iOS SDK, you can set your desired ad tag parameters via `adTagParameters` in the `THEOAdDescription` as followed: + +```swift +let theoad = THEOAdDescription( + networkCode: "NETWORK-CODE", + customAssetKey: "CUSTOM-ASSET-KEY", + adTagParameters: [ + "YOUR-AD-TAG-PARAMETER-1" : "VALUE-1", + "YOUR-AD-TAG-PARAMETER-2" : "VALUE-2" + ] +) +let sourceDescription = SourceDescription(source: typedSource, ads: [theoad]) +self.theoplayer.source = sourceDescription +``` + +## More information + +- [Supply targeting parameters to your stream](https://support.google.com/admanager/answer/7320899) diff --git a/ads_versioned_docs/version-v1/how-to-guides/create-backdrop-layout.md b/ads_versioned_docs/version-v1/how-to-guides/create-backdrop-layout.md new file mode 100644 index 000000000000..d3d56d2c7253 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/create-backdrop-layout.md @@ -0,0 +1,47 @@ +--- +sidebar_position: 7 +--- + +# Create backdrop images + +A backdrop is an companion image that is shown along with the player during an ad. +This image is intended to provide either some additional information about an advertisement or a companion advertisement next to the main content. + +## Backdrop Layout Options + +There are two primary backdrop layout options, each serving a specific purpose in presenting ads effectively: + +### 1. Double Box Layout + +The **Double Box** layout places the video content and advertisement side by side, allowing viewers to see both elements simultaneously. + +- **Screen Ratio**: 16:9 for optimal viewing on widescreen displays. +- **Border**: A fixed 20px border is applied around the video elements, ensuring a clean, defined separation between the content and the ad. + +#### Example Template + +Below is a template for the Double Box layout tailored for 1080p resolution: + +![Double box Template.png](../assets/img/backdrop-doublebox-template.png) + +### 2. L-shape Layouts + +The **L-shape** layout positions the video player in the top-left corner of the screen, scaling it down to create space for the backdrop image. +This configuration allows viewers to see the content or an ad and the backdrop image simultaneously. + +- **Screen Ratio**: 16:9, ensuring compatibility with most screens. +- **Positioning**: The player is moved to the top-left corner, with the backdrop image filling the remaining space. + +#### Example Template + +Below is a template for the L-shape layout: + +![L-shape Template.png](../assets/img/backdrop-lshape-template.png) + +## Considerations and Limitations + +While both layouts serve distinct purposes, certain limitations should be considered: + +- **Device Compatibility**: Most Smart TVs do not support the Double Box layout due to the lack of capability to show multiple video streams at the same time. For these devices, the L-shape layout is a better option, as it displays only one active video stream along with the backdrop image. We currently disable Double Box for Smart TVs for this reason and fallback to Single layout. +- **Resolution Adaptability**: Templates are made for 1080p resolution, but scaling considerations should be taken into account for lower or higher resolutions to maintain the quality and layout proportions. +- **File Size and Load Times**: To ensure smooth playback and quick loading times, backdrop images should be optimized, especially for mobile and lower-capability devices. diff --git a/ads_versioned_docs/version-v1/how-to-guides/dynamic-backdrops.md b/ads_versioned_docs/version-v1/how-to-guides/dynamic-backdrops.md new file mode 100644 index 000000000000..6fef2a7b4f21 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/dynamic-backdrops.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 8 +--- + +# Dynamic backdrops + +Some layouts, such as L-shape and Double Box, make use of a backdrop. These can be specified on both the monetized stream and the ad break in 2 ways: + +- `backdropURI`: Optional URI containing the default backdrop to be used during the Double Box or L-shape ads. +- `backdropURIGamProperties`: Optional property with configuration values for a dynamic backdrop loaded via GAM to be used during the Double Box or L-shape ads. This property has priority over `backdropURI`. + +## Using the OptiView Ads service REST API + +The way to specify backdrops is through the OptiView Ads service API when creating or updating both monetized streams and adbreaks. + +The body of the post request for both monetized streams and ad breaks can have the following properties: + +```ts +{ + ... + backdropURI: string | undefined; + backdropURIGamProperties: { + iu: string; + sz: { + default: string; + phone: string | undefined; + tv: string | undefined; + } + } | undefined; + ... +} +``` + +- `backdropURI` can optionally be used to specify a URI that returns the actual backdrop image to be used. +- `backdropURIGamProperties` can optionally be used to specify the values required for requesting a pre-configured custom creative from GAM (See below). It follows the [specifications](https://support.google.com/admanager/answer/2623168#zippy=%2Crequired-parameters) for a tagless request to load a creative. If `backdropURIGamProperties` is not defined, you need to specify `backdropURI`. + - `iu` specifies the Google Ad Manager ad unit code, including your Google Ad Manager network code. + - `sz` specifies the creative size configured in GAM. Aside from the required `default` value, you can optionally specify alternative values for either `phone` or `tv` devices. + +Please refer to [Custom GAM Creatives](../gam-custom-creatives) for information on how to setup your own custom creatives in GAM. + +That's it! You should now be able to configure dynamic backdrops for L-shape and Double Box ad breaks. diff --git a/ads_versioned_docs/version-v1/how-to-guides/gam-custom-creatives.md b/ads_versioned_docs/version-v1/how-to-guides/gam-custom-creatives.md new file mode 100644 index 000000000000..afa8d2174f13 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/gam-custom-creatives.md @@ -0,0 +1,51 @@ +--- +sidebar_position: 9 +--- + +# Custom GAM Creatives + +When using GAM for delivering dynamic backdrops or overlays respectively through the `backdropURIGamProperties` or `gamProperties` properties, you need to first configure corresponding Creatives in the GAM console. + +First off, you need to define a custom creative template specifically for OptiView Ads as documented [here](https://support.google.com/admanager/answer/1138308#custom-templates). + +You can either import this [pre-defined template](pathname:///ads/templates/OptiView_Ads_gam_template.json), or follow the steps below. + +Apart from assigning a `Name` and optional `Description`, you need to perform the following changes: + +- Configure a File type variable `Image`, optionally limiting the allowed file types: + +![Configure a File type variable `Image`](../assets/img/gam_image_1.png) + +- Optionally configure a URL type variable `ClickThrough`: + +![Configure a URL type variable `ClickThrough`](../assets/img/gam_image_2.png) + +- Configure the `Code snippet` as follows: + +```json +{ + "image": "[%Image%]", + "clickThrough": "[%ClickThrough%]", + "impression": "%%VIEW_URL_ESC%%" +} +``` + +Note the `impression` field which is needed for the player to properly handle impression tracking. The special macro `%%VIEW_URL_ESC%%` is supplied by GAM and will contain a GAM specific impression URL which is called when the backdrop is shown on the client. + +In the end it should look like this: + +![Create custom creative template](../assets/img/gam_image_3.png) + +Now that the template is set up, you can start adding creatives as you would otherwise when using standard image creatives, except you need to select this Custom creative template when adding the new creatives: + +![Create custom creative template](../assets/img/gam_image_4.png) + +When editing the creative itself, make sure to select an appropriate `Target ad unit size` as you would with a standard image creative also. This size corresponds to the `sz` property values inside the `gamProperties`. + +Besides that, you need to at least also upload or select a suitable image for display. + +![Create custom creative template](../assets/img/gam_image_5.png) + +Now that the creatives are defined, make sure to add them to a line item that is at least in status 'Ready' so delivery can commence (Ref GAM [documentation](https://support.google.com/admanager/answer/82991?hl=en&ref_topic=7506394&sjid=17311913585360515246-EU)). + +That's it! You should now be able to add dynamic backdrops and image overlays which should show up inside the player at the appropriate time and location. diff --git a/ads_versioned_docs/version-v1/how-to-guides/index.mdx b/ads_versioned_docs/version-v1/how-to-guides/index.mdx new file mode 100644 index 000000000000..ee230a5c7b5f --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/index.mdx @@ -0,0 +1,12 @@ +--- +sidebar_position: 2 +sidebar_label: How-to guides +--- + +# How-to guides + +These guides provide detailed information on specific features of OptiView Ads (SGAI). They cover how ad impressions are tracked, how custom ad tag parameters can be included in the ad decisioning request, how ad formats can be overridden client-side, and how breaks can be signaled. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/ads_versioned_docs/version-v1/how-to-guides/override-layout.md b/ads_versioned_docs/version-v1/how-to-guides/override-layout.md new file mode 100644 index 000000000000..4eaf568ab2cc --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/override-layout.md @@ -0,0 +1,154 @@ +--- +sidebar_position: 4 +--- + +# Override ad experience layout + +OptiView Ads is able to insert ad breaks into your content via the signaling server. The layout that is used while playing is usually determined by the signaling server. Currently, you can have the following layout modes : + +- `SINGLE`: Replaces the content with an advertisement. +- `DOUBLE`: Allows content to continue playing side-by-side with an advertisement and its companion background. +- `LSHAPE_AD`: Displays advertisements in a scaled window alongside a companion background. +- `LSHAPE_CONTENT`: Displays the original content video in a scaled window alongside a companion background. + +When your signaling service is created, the default layout should be defined. Look at our [API references](/ads/api/signaling/create-monetized-stream/) for more information. + +The rest of this page will go over how to override this initial chosen layout in the [signaling service](#override-layout-through-the-signaling-service) itself, +or override it on the [player side](#override-the-layout-through-the-player). + +## Override layout through the signaling service + +There are two ways to update the layout in the signaling service. + +### Setting a new default layout + +It is possible to update the default layout value on the signaling service. This can be done by making an [UpdateMonetizedStreamLayout](/ads/api/signaling/update-monetized-stream-layout/) API request to the signaling service with the new layout. The updated layout value will be the new default for all ad breaks. + +### Signaling an ad break through the API + +When scheduling ad breaks through the signaling service API it is possible to choose the layout of the ad break. The passed layout will be used, unless it is left `undefined`. In this case the default layout is still used. + +For more information on the scheduling API refer to the following section: [Scheduling breaks through the signaling service](/ads/how-to-guides/scheduling-breaks/#using-the-optiview-ads-service-rest-api). + +## Override the layout through the player + +You might not always want to show the default layout mode as provided by the signaling server. For example, if you are watching content on a mobile device, the `DOUBLE` layout mode might not be the ideal user experience. + +Via the player, you can pass the following values to `TheoAdDescription.overrideLayout` to override the layout: + +- `single`: This will override the default layout and plays all the ad breaks using the "single" layout mode. +- `l-shape`: This will override the default layout mode and plays all the ad breaks using the "l-shape" layout mode. +- `double`: This will override the default layout mode and plays all the ad breaks using the "double" layout mode. +- `single-if-mobile`: This will override the default layout mode to play all ad breaks using the "single" layout mode only when on mobile. + +### Web SDK + +For the Web SDK, you can override the default layout by setting `overrideLayout` in the `TheoAdDescription` object as followed: + +```javascript +player.source = { + sources: { + src: 'PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + overrideLayout: 'YOUR-DESIRED-LAYOUT', + }, + ], +}; +``` + +:::note +There is a limitation that Double Box (`DOUBLE`) is not supported on Chromecast and older smart TVs. These revert to the single layout automatically. +::: + +### React Native SDK + +For the React Native SDK, you can override the default layout by setting `overrideLayout` in the `TheoAdDescription` object as followed: + +```javascript +player.source = { + sources: { + src: 'PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI', + type: 'application/x-mpegurl', + hlsDateRange: true, + }, + ads: [ + { + integration: 'theoads', + networkCode: 'NETWORK-CODE', + customAssetKey: 'CUSTOM-ASSET-KEY', + overrideLayout: 'single', // "l-shape", "double", "single-if-mobile" + }, + ], +}; +``` + +### Android SDK + +For the Android SDK, you can override the default layout by setting `overrideLayout` in the `TheoAdDescription` object as follows: + +```kotlin +import com.theoplayer.android.api.ads.theoads.TheoAdsDescription +import com.theoplayer.android.api.source.SourceDescription +import com.theoplayer.android.api.source.SourceType +import com.theoplayer.android.api.source.TypedSource + +theoPlayerView.player.source = SourceDescription.Builder( + TypedSource.Builder("PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI") + .type(SourceType.HLS) + .hlsDateRange(true) + .build() +).ads( + TheoAdsDescription( + networkCode = "NETWORK-CODE", + customAssetKey = "CUSTOM-ASSET-KEY", + overrideLayout = "single", // "l-shape", "double" + ) +).build() +``` + +### iOS SDK + +For the iOS SDK, you can override the default layout by setting `overrideLayout` in the `THEOAdDescription` object as follows: + +```swift +import UIKit +import THEOplayerSDK +import THEOplayerTHEOadsIntegration + +class ViewController: UIViewController { + var theoplayer: THEOplayer! + var theoads: THEOadsIntegration! + + override func viewDidLoad() { + super.viewDidLoad() + self.theoplayer = THEOplayer(configuration: THEOplayerConfigurationBuilder().build()) + self.theoplayer.frame = view.bounds + self.theoplayer.addAsSubview(of: view) + self.theoads = THEOadsIntegrationFactory.createIntegration(on: self.theoplayer) + self.theoplayer.addIntegration(self.theoads) + + let source = "PATH-TO-SIGNALING-SERVER/hls/MANIFEST-URI" + let typedSource = TypedSource( + src: source, + type: "application/x-mpegurl", + hlsDateRange: true + ) + let theoad = THEOAdDescription( + networkCode: "NETWORK-CODE", + customAssetKey: "CUSTOM-ASSET-KEY", + overrideLayout: .single // .double or .lshape + ) + let sourceDescription = SourceDescription(source: typedSource, ads: [theoad]) + self.theoplayer.source = sourceDescription + self.theoplayer.play() + } + +} +``` diff --git a/ads_versioned_docs/version-v1/how-to-guides/scheduling-breaks.md b/ads_versioned_docs/version-v1/how-to-guides/scheduling-breaks.md new file mode 100644 index 000000000000..5a13e63c8415 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/scheduling-breaks.md @@ -0,0 +1,130 @@ +--- +sidebar_position: 5 +--- + +# Scheduling breaks + +OptiView Ads can insert ad breaks into your content, but it requires placement information to do so. This page will outline the various options for scheduling ad breaks. + +## Using the manifest + +There are multiple ways to provide the general ad break information in the manifest itself. +In this section we'll go through the different options and the minimal requirements. + +:::note +For HLS live streams, we require the `#EXT-X-PROGRAM-DATE-TIME` tag to be present. +This is necessary to have a clear timeline for each viewer and allows accurate scheduling of the ad breaks. +::: + +As mentioned it is possible to schedule the start time and duration of the ad breaks inside of the manifest. +The signaling service will recognize this information and will translate it so that the player can request the correct ads and layout for the ad break. + +There are several options for achieving this: + +### DATERANGE (Manifest) {#daterange} + +The `#EXT-X-DATERANGE` tag is the recommended approach to signal an ad break due to its standardization. +OptiView Ads will recognize any `#EXT-X-DATERANGE` tag as long as the `CLASS` attribute is undefined. + +The required attributes for this are: + +- `ID`: a unique identifier of the daterange in the playlist. +- `START-DATE`: A quoted string containing the date/time at which the daterange begins. +- `DURATION` or `PLANNED-DURATION` of the daterange. + - Optionally `SCTE35-OUT` could contain the duration instead. + +An example snippet can be found below: + +``` +#EXTINF:10 +1.ts +#EXT-X-DATERANGE:ID="1",START-DATE="2022-09-27T18:00:00.000Z",PLANNED-DURATION=30 +#EXTINF:10 +2.ts +``` + +### CUE-OUT (Manifest) {#cue-out} + +Inside the manifest the ad break start can be signaled by adding an `#EXT-X-CUE-OUT` tag. The duration of the ad break should be specified after it. The end of the ad break and start of content would be signaled by an `#EXT-X-CUE-IN` tag. The `CUE-OUT` tag can be accompanied by a [daterange](#daterange). This is relevant if the ad break start does not correspond with the start of a new segment. + +It is also recommended to add `#EXT-X-CUE-OUT-CONT` tags for each segment inside of the ad break. +This enables the tune-in feature which is able to show parts of the ad break even if the viewer joins in the middle of one. + +Similar to the daterange, `SCTE35` markers could optionally be added containing the duration and the ID of the ad break. + +An example manifest with these tags can be found below: + +``` +#EXTINF:10 +1.ts +#EXT-X-CUE-OUT:30 +#EXTINF:10 +2.ts +#EXT-X-CUE-OUT-CONT:ElapsedTime=10,Duration=30 +#EXTINF:10 +3.ts +#EXT-X-CUE-OUT-CONT:ElapsedTime=20,Duration=30 +#EXTINF:10 +4.ts +#EXT-X-CUE-IN +#EXTINF:10 +5.ts +``` + +### OATCLS-SCTE35 (Manifest) {#oatcls-scte35} + +The `#EXT-OATCLS-SCTE35` tag is a non-standard tag which contains a SCTE marker. +The Signaling Service will parse the information in the SCTE marker such as the duration and ID. + +An example snippet for an `#EXT-OATCLS-SCTE35` tag can be found below: + +``` +#EXTINF:10, +1.ts +#EXT-OATCLS-SCTE35:/DBFAAAAAAAAAP/wFAUAAAI+f+/+yuN1kH4Azf5gAAAAAAAgAh5DVUVJAAABy3//AADN/mAOCk5GTF9saW5lYXIwAACyYCjX +#EXTINF:10, +2.ts +``` + +## Using the OptiView Ads service REST API + +If the ad information won't be present in the provided manifest, it is possible to schedule one through the OptiView Ads service API. + +This can be done by a POST request to `{path}/break` where path is the usual url to interact with the OptiView Ads service for a specific monetized stream: `{optiview-ads-host}/ads-client/api/v1/monetized-streams/:monetizedStreamId`. Please see [CreateMonetizedStreamBreak](/ads/api/signaling/create-monetized-stream-break/). + +The body of the post request can have the following properties: + +```ts +{ + id: string; + startDate: string; + duration: string; + source: string | undefined; + layout: string | undefined; + assetURI: string | undefined; + backdropURI: string | undefined; + backdropURIGamProperties: string | undefined; +} +``` + +- The `id` corresponds to the ID of the DATERANGE which corresponds to the ad break request. +- `startDate` denotes when the ad break should start. It should be in a Program Date Time format such as such as `YYYY-MM-DDThh:mm:ss.SSSZ`. +- `duration` is the total duration of the ad break (seconds). +- `source` is the optional identifier of the source for which the ad needs to be scheduled. The signaling service will only insert the ad for playlists containing part of this in their URL. If not set the ad is scheduled in every playlist. +- `layout` is the optional OptiView Ads layout, which can be set to `SINGLE`, `DOUBLE`, `LSHAPE_AD` or `LSHAPE_CONTENT`. If it is `undefined` the default layout of the signaling service will be chosen. +- `assetURI` is the optional manifest URI of an alternative ad source. If set it will use this URI during the scheduled ad break instead of Google Pod Serving. +- `backdropURI`: Optional URI containing the backdrop to be used for this ad break. +- `backdropURIGamProperties`: Optional property with configuration values for a dynamic backdrop loaded via GAM to be used for this ad break. This property has priority over `backdropURI`. + +Note: for properties that are set on both the monetized stream and the ad break, the values on the ad break have priority. + +An example of a schedule ad break request body can be found below: + +```json +{ + "id": "1", + "startDate": "YYYY-MM-DDTHH:MM:SS.000Z", + "duration": "10", + "layout": "DOUBLE" +} +``` diff --git a/ads_versioned_docs/version-v1/how-to-guides/scheduling-overlays.md b/ads_versioned_docs/version-v1/how-to-guides/scheduling-overlays.md new file mode 100644 index 000000000000..6bd3dc330522 --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/scheduling-overlays.md @@ -0,0 +1,137 @@ +--- +sidebar_position: 6 +--- + +# Scheduling overlays + +OptiView Ads can insert overlays into your content, but it requires placement information to do so. This page will outline the various options for scheduling overlays. + +## Using the OptiView Ads service REST API + +The recommended way to schedule overlays is through the OptiView Ads service API. + +This can be done by a POST request to `{path}/overlay` where path is the usual url to interact with the OptiView Ads service for a specific monetized stream: `{optiview-ads-host}/ads-client/api/v1/monetized-streams/:monetizedStreamId`. Please see [CreateMonetizedStreamOverlay](/ads/api/signaling/create-monetized-stream-overlay/). + +The body of the post request can have the following properties: + +```ts +{ + id: string; + name: string | undefined; + startDate: string; + duration: string; + type: string; + position: { + left: number | undefined; + right: number | undefined; + top: number | undefined; + bottom: number | undefined; + }; + size: { + width: number; + height: number; + }; + opacity: number | undefined; + gamProperties: { + iu: string; + sz: { + default: string; + phone: string | undefined; + tv: string | undefined; + } + } | undefined; + resourceURI: { + type: string; + default: string; + phone: string | undefined; + tv: string | undefined; + }[] | undefined; + clickThroughURI: string | undefined; +} +``` + +- The `id` uniquely identifies the overlay inside the monetized stream. +- `name` is used to optionally identify the overlay presentation with a human-friendly name. +- `startDate` denotes when the overlay presentation should start. It should be in a Program Date Time format such as `YYYY-MM-DDThh:mm:ss.SSSZ`. +- `duration` is the total duration of the overlay presentation (in seconds). +- `type` can only be 'image' for now. In the future we might extend this. +- `position` is required to know where the overlay needs to be presented on top of the video content. It requires a combination of either one of `left` or `right` and `top` or `bottom`. All values are percentage based and non-zero. The values denote the location from the specified side. For example: `left: 10` denotes `10% from the left edge of the video`. +- `size` is required to know what the overlay presentation's width and height need to be relative to the video content display size. All values are percentage based and non-zero. For example: `width: 20` denotes 20% of the current video display width. +- `opacity` can optionally be used to make the overlay less opaque. This value is percentage based and non-zero. The default is `100`. +- `gamProperties` can optionally be used to specify the values required for requesting a pre-configured custom creative from GAM (See below). It follows the [specifications](https://support.google.com/admanager/answer/2623168#zippy=%2Crequired-parameters) for a tagless request to load a creative. If `gamProperties` is not defined, you need to specify at least one `resourceURI`. + - `iu` specifies the Google Ad Manager ad unit code, including your Google Ad Manager network code. + - `sz` specifies the creative size configured in GAM. Aside from the required `default` value, you can optionally specify alternative values for either `phone` or `tv` devices. +- `resourceURI` can optionally be used to specify one or more types required for requesting a custom overlay via a URI. If `resourceURI` is not defined, you need to specify `gamProperties`. + - `type` is used to specify the resource type. Currently, only `png` and `svg` are supported. Based on the underlying platform, the suitable format will be chosen. + - `default`, `phone` and `tv` can be used to specify the corresponding resource URI to load when presenting the overlay. While `default` is required, `phone` and `tv` are optional. +- `clickThroughURI` is an optional field used to enable the clickthrough function on the overlay for both the `gamProperties` and `resourceURI` options. + +An example of a schedule overlay request body for a GAM based image overlay can be found below: + +```json +{ + "id": "1", + "name": "Test overlay 1", + "startDate": "2024-12-09T07:30:11.274Z", + "duration": "30", + "type": "image", + "position": { + "left": 5, + "top": 5 + }, + "size": { + "width": 10, + "height": 10 + }, + "opacity": 50, + "gamProperties": { + "iu": "/12345678/overlay_1", + "sz": { + "default": "400x225", + "phone": "400x225", + "tv": "400x225" + } + }, + "clickThroughURI": "https://optiview.dolby.com" +} +``` + +An example of a schedule overlay request body for a custom image overlay can be found below: + +```json +{ + "id": "2", + "name": "Test overlay 2", + "startDate": "2024-12-09T07:30:11.274Z", + "duration": "30", + "type": "image", + "position": { + "left": 5, + "top": 5 + }, + "size": { + "width": 10, + "height": 10 + }, + "opacity": 50, + "resourceURI": [ + { + "type": "svg", + "default": "https:///image.svg", + "phone": "https:///image_phone.svg", + "tv": "https:///image_tv.svg" + }, + { + "type": "png", + "default": "https:///image.png", + "phone": "https:///image_phone.png", + "tv": "https:///image_tv.png" + } + ], + "clickThroughURI": "https://optiview.dolby.com" +} +``` + +Please refer to [Custom GAM Creatives](../gam-custom-creatives) for information on how to setup your own custom creatives in GAM. + +That's it! You should now be able to add image overlays which should show up inside the player at the appropriate time and location. diff --git a/ads_versioned_docs/version-v1/how-to-guides/workflow-integration.md b/ads_versioned_docs/version-v1/how-to-guides/workflow-integration.md new file mode 100644 index 000000000000..fb93c260ed0e --- /dev/null +++ b/ads_versioned_docs/version-v1/how-to-guides/workflow-integration.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 1 +--- + +# Workflow integration + +For implementing OptiView Ads, the Signaling Service must be integrated within your existing video workflow. This service acts as an intermediary between the media origin and the CDN, enabling ad insertion and metadata enrichment. + +## Existing Video Workflow + +In a typical video delivery workflow, the CDN fetches the manifest directly from the media origin, and all subsequent requests for video segments are also routed from the CDN to the origin. + +![Video workflow before](../assets/img/workflow-optiview-ads-before.png). + +## Integrating the Signaling Service + +By introducing the Signaling Service, the manifest requests from the CDN are intercepted before reaching the origin. The Signaling Service augments the manifest with ad break signals and any necessary metadata. Notably, video segment requests bypass the Signaling Service and continue to be fetched directly from the CDN to the origin, ensuring minimal impact on content delivery performance. + +![Video workflow after](../assets/img/workflow-optiview-ads-after.png). + +## Configuring the Signaling Service + +To integrate the Signaling Service with your workflow, the properties origin and segmentOrigin are crucial when setting up a new monetized stream: + +- `origin`: This property specifies where the original manifest should be fetched from. It points to the media origin that provides the main content. +- `segmentOrigin`: This property ensures that the segment URLs within the augmented manifest are absolute. It defines the path from which the segments should be requested, typically directly from the origin or CDN. + By configuring these properties, the Signaling Service seamlessly inserts ad breaks without affecting the video segment delivery flow. + +## More information + +- [Getting Started](/ads/getting-started/index.mdx) +- [API reference](/ads/api/signaling/theoads-api/) diff --git a/ads_versioned_docs/version-v1/index.mdx b/ads_versioned_docs/version-v1/index.mdx new file mode 100644 index 000000000000..c1e2ca8c9997 --- /dev/null +++ b/ads_versioned_docs/version-v1/index.mdx @@ -0,0 +1,55 @@ +--- +sidebar_position: 0 +sidebar_label: Introduction +--- + +# Dolby OptiView Ads Introduction + +import SidebarDocCardList from '@site/src/components/SidebarDocCardList'; +import RebrandingNotice from './callouts/_rebranding_notice.md'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + + + +OptiView Ads is an ad insertion service for LIVE content (VoD support coming soon), utilizing Server-Guided Ad Insertion (SGAI). On these pages, you'll learn how to get started with OptiView Ads, how to configure the player and integrate the signaling service APIs. + +By logically redistributing responsibilities in the advertisement workflow, OptiView Ads: + +- Creates a seamless viewer experience across platforms. +- Simplifies the workflow by reducing complexity, which results in easier implementation and lower operational risk. +- Introduces innovative ad formats, creating more diverse and less intrusive ad opportunities. +- Maximizes workflow efficiency by reducing prefetching and over-allocation of inventory. +- Enables more relevant ads through a high degree of personalization. + +OptiView Ads is an advanced ad insertion service consisting of two key components: + +1. **Signaling Service**: This back-end component enriches the manifest from your existing origin with advanced ad break signaling. It integrates seamlessly with OptiView Player, the second component, to create a smooth and cohesive workflow. + +2. **OptiView Player**: The player fetches and replaces ads within the content, working closely with the Signaling Service to optimize the viewer experience across platforms. + + + +The Signaling Service is provided as a service and can be integrated into any existing content management workflow through its APIs, allowing for seamless adoption without significant changes to your current setup. + +To ensure high availability, we recommend maintaining the original origin stream on a CDN as a backup, while the Signaling Service provides redundancy and failover capabilities to further enhance reliability. + +OptiView Ads is tightly integrated with [Google Ad Manager](https://developers.google.com/ad-manager/dynamic-ad-insertion 'Google DAI') for ad decisioning, transcoding, and serving, ensuring a streamlined process for ad delivery and management. + +OptiView Ads enables innovative ad formats through OptiView Player, providing new ways to monetize content in a less intrusive manner. The out-of-the-box formats include, but are not limited to: + +- **Default Full Screen Ad Insertion**: Replaces the content with an advertisement. +- **Double Box**: Allows content to continue playing side-by-side with an advertisement and its companion background. +- **L-shape**: Displays content or advertisements in a scaled window alongside a companion background. + +![OptiView Ads experiences](./assets/img/ads_formats.svg) + +**Note**: Every OptiView Player comes with the functionality to enable OptiView Ads by default. However, an OptiView Ads-enabled OptiView Player license is required to activate this functionality. + + diff --git a/ads_versioned_docs/version-v1/limitations/index.mdx b/ads_versioned_docs/version-v1/limitations/index.mdx new file mode 100644 index 000000000000..180b63468104 --- /dev/null +++ b/ads_versioned_docs/version-v1/limitations/index.mdx @@ -0,0 +1,15 @@ +--- +sidebar_position: 3 +sidebar_label: Limitations and known issues +--- + +# Limitations and known issues + +This page lists the current limitations and known issues of OptiView Ads. + +## Using OptiView Ads with Picture-in-Picture (PiP) on Firefox for Web + +Using OptiView Ads with Picture-in-Picture (PiP) on Firefox for Web is currently not supported. During playback, the default PiP button +will be hidden to discourage usage. + +Note that users will still be able to trigger PiP using the button in the browser's address bar. diff --git a/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-dark.svg b/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-dark.svg new file mode 100644 index 000000000000..69cb4fc4107d --- /dev/null +++ b/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-dark.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-light.svg b/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-light.svg new file mode 100644 index 000000000000..702ccf6c1e47 --- /dev/null +++ b/ads_versioned_docs/version-v1/static/ads/img/how_ads_works-light.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ads_versioned_docs/version-v1/static/ads/templates/OptiView_Ads_gam_template.json b/ads_versioned_docs/version-v1/static/ads/templates/OptiView_Ads_gam_template.json new file mode 100644 index 000000000000..e13d88d29a4d --- /dev/null +++ b/ads_versioned_docs/version-v1/static/ads/templates/OptiView_Ads_gam_template.json @@ -0,0 +1,31 @@ +{ + "name": "OptiView Ads Image", + "description": "OptiView Ads Image", + "variables": [ + { + "mimeTypes": [], + "label": "Image", + "uniqueName": "Image", + "description": "The image to use", + "isRequired": true, + "variableType": "ASSET" + }, + { + "isTrackingUrl": false, + "urlType": "STANDARD_HTTP", + "label": "ClickThrough", + "uniqueName": "ClickThrough", + "description": "The clickthrough URL for this creative", + "isRequired": false, + "variableType": "URL" + } + ], + "formatter": "{\n\t\"image\": \"[%Image%]\",\n\t\"clickThrough\": \"[%ClickThrough%]\",\n \"impression\": \"%%VIEW_URL_ESC%%\"\n}", + "omidPartnerName": "", + "type": "USER_DEFINED", + "isInterstitial": false, + "isNativeEligible": false, + "isNativeVideoEligible": false, + "isNewsletterEligible": false, + "isSafeFrameCompatible": true +} diff --git a/ads_versioned_sidebars/version-v1-sidebars.json b/ads_versioned_sidebars/version-v1-sidebars.json new file mode 100644 index 000000000000..27c4bc2f9e39 --- /dev/null +++ b/ads_versioned_sidebars/version-v1-sidebars.json @@ -0,0 +1,225 @@ +{ + "ads": [ + "index", + { + "type": "category", + "label": "Getting started", + "description": "Set up your first stream with OptiView Ads!", + "collapsed": false, + "customProps": { + "icon": "🚀" + }, + "link": { + "type": "doc", + "id": "getting-started/index" + }, + "items": [ + { + "type": "autogenerated", + "dirName": "getting-started" + } + ] + }, + { + "type": "category", + "label": "How-to guides", + "description": "Learn how to integrate OptiView Ads with your setup", + "customProps": { + "icon": "📖" + }, + "link": { + "type": "doc", + "id": "how-to-guides/index" + }, + "items": [ + { + "type": "autogenerated", + "dirName": "how-to-guides" + } + ] + }, + { + "type": "category", + "label": "Limitations and known issues", + "description": "Take notice of the limitations and known issues when using OptiView Ads.", + "customProps": { + "icon": "⚠️" + }, + "link": { + "type": "doc", + "id": "limitations/index" + }, + "items": [ + { + "type": "autogenerated", + "dirName": "limitations" + } + ] + }, + { + "type": "category", + "label": "API references", + "description": "Discover all functions of OptiView Ads.", + "customProps": { + "icon": "*️⃣" + }, + "link": { + "type": "generated-index", + "slug": "api" + }, + "items": [ + { + "type": "link", + "label": "Signaling Service", + "customProps": { + "icon": "🛜" + }, + "href": "/ads/v1/api/signaling/theoads-api" + }, + { + "type": "link", + "label": "Web", + "customProps": { + "icon": "web" + }, + "href": "pathname:///theoplayer/v9/api-reference/web/interfaces/TheoAdDescription.html" + }, + { + "type": "link", + "label": "Android", + "customProps": { + "icon": "android" + }, + "href": "pathname:///theoplayer/v9/api-reference/android/com/theoplayer/android/api/ads/theoads/package-summary.html" + }, + { + "type": "link", + "label": "React Native", + "customProps": { + "icon": "react" + }, + "href": "https://theoplayer.github.io/react-native-theoplayer/api/interfaces/TheoAdDescription.html" + } + ] + } + ], + "signalingApi": [ + { + "type": "link", + "label": "« Back", + "href": "/ads/v1/" + }, + { + "type": "doc", + "id": "api/signaling/theoads-api" + }, + { + "type": "category", + "label": "Monetized-Stream", + "items": [ + { + "type": "doc", + "id": "api/signaling/get-monetized-streams", + "label": "GetMonetizedStreams", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/create-monetized-stream", + "label": "CreateMonetizedStream", + "className": "api-method post" + }, + { + "type": "doc", + "id": "api/signaling/get-monetized-stream", + "label": "GetMonetizedStream", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/update-monetized-stream", + "label": "UpdateMonetizedStream", + "className": "api-method patch" + }, + { + "type": "doc", + "id": "api/signaling/delete-monetized-stream", + "label": "DeleteMonetizedStream", + "className": "api-method delete" + }, + { + "type": "doc", + "id": "api/signaling/update-monetized-stream-layout", + "label": "UpdateMonetizedStreamLayout", + "className": "api-method patch" + }, + { + "type": "doc", + "id": "api/signaling/create-monetized-stream-break", + "label": "CreateMonetizedStreamBreak", + "className": "api-method post" + }, + { + "type": "doc", + "id": "api/signaling/get-monetized-stream-breaks", + "label": "GetMonetizedStreamBreaks", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/delete-monetized-stream-breaks", + "label": "DeleteMonetizedStreamBreaks", + "className": "api-method delete" + }, + { + "type": "doc", + "id": "api/signaling/update-monetized-stream-breaks", + "label": "UpdateMonetizedStreamBreaks", + "className": "api-method patch" + }, + { + "type": "doc", + "id": "api/signaling/create-monetized-stream-overlay", + "label": "CreateMonetizedStreamOverlay", + "className": "api-method post" + }, + { + "type": "doc", + "id": "api/signaling/get-monetized-stream-overlays", + "label": "GetMonetizedStreamOverlays", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/delete-monetized-stream-overlay", + "label": "DeleteMonetizedStreamOverlay", + "className": "api-method delete" + } + ] + }, + { + "type": "category", + "label": "Monetized-Stream-V2", + "items": [ + { + "type": "doc", + "id": "api/signaling/get-monetized-streams-paginated", + "label": "GetMonetizedStreamsPaginated", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/get-monetized-stream-breaks-paginated", + "label": "GetMonetizedStreamBreaksPaginated", + "className": "api-method get" + }, + { + "type": "doc", + "id": "api/signaling/get-monetized-stream-overlays-paginated", + "label": "GetMonetizedStreamOverlaysPaginated", + "className": "api-method get" + } + ] + } + ] +} diff --git a/ads_versions.json b/ads_versions.json new file mode 100644 index 000000000000..868a38e32ae6 --- /dev/null +++ b/ads_versions.json @@ -0,0 +1 @@ +["v1"] diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 3f02d4335010..1751e67546c8 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -214,6 +214,16 @@ const config: Config = { routeBasePath: '/ads', sidebarPath: './sidebarsAds.ts', docItemComponent: '@theme/ApiItem', + lastVersion: 'current', + versions: { + current: { + label: 'v2', + }, + v1: { + label: 'v1', + banner: 'none', + }, + }, async sidebarItemsGenerator(args) { const sidebarItems = await sidebarItemsGenerator(args); return removeDocIndexItems(sidebarItems); @@ -335,6 +345,8 @@ const config: Config = { docsPluginId: 'ads', config: { signaling: { + version: 'v2', + label: 'v2', specPath: 'ads/api/ads-client.swagger.json', outputDir: 'ads/api/signaling', hideSendButton: true, @@ -342,6 +354,16 @@ const config: Config = { groupPathsBy: 'tag', }, }, + signalingV1: { + version: 'v1', + label: 'v1', + specPath: 'ads_versioned_docs/version-v1/api/ads-client.swagger.json', + outputDir: 'ads_versioned_docs/version-v1/api/signaling', + hideSendButton: true, + sidebarOptions: { + groupPathsBy: 'tag', + }, + }, }, } satisfies OpenApiPlugin.PluginOptions, ], @@ -414,6 +436,8 @@ const config: Config = { return [existingPath.replace('/theoplayer/how-to-guides/web/uplynk/', '/theoplayer/how-to-guides/miscellaneous/verizon-media/')]; } else if (existingPath.startsWith('/theolive/v1/api/')) { return [existingPath.replace('/theolive/v1/api/', '/theolive/api/')]; + } else if (existingPath.startsWith('/ads/v1/')) { + return [existingPath.replace('/ads/v1/', '/ads/')]; } else if (existingPath === '/theolive/channel/metadata-insertion') { return ['/theolive/contribution/sei-messages']; } @@ -623,6 +647,11 @@ const config: Config = { docsPluginId: 'theoplayer', position: 'right', }, + { + type: 'docsVersionDropdown', + docsPluginId: 'ads', + position: 'right', + }, { type: 'docsVersionDropdown', docsPluginId: 'theolive', diff --git a/package.json b/package.json index 164e2ff8f53e..8474dd9a61b0 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "serve": "node serve.js", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", - "gen-api-docs": "docusaurus gen-api-docs all --plugin-id ads-api && docusaurus gen-api-docs all --plugin-id ad-engine-api && docusaurus gen-api-docs all --plugin-id millicast-api && docusaurus gen-api-docs all --plugin-id theolive-api --all-versions", - "clean-api-docs": "docusaurus clean-api-docs all --plugin-id ads-api && docusaurus clean-api-docs all --plugin-id ad-engine-api && docusaurus clean-api-docs all --plugin-id millicast-api && docusaurus clean-api-docs all --plugin-id theolive-api --all-versions", + "gen-api-docs": "docusaurus gen-api-docs all --plugin-id ads-api --all-versions && docusaurus gen-api-docs all --plugin-id ad-engine-api && docusaurus gen-api-docs all --plugin-id millicast-api && docusaurus gen-api-docs all --plugin-id theolive-api --all-versions", + "clean-api-docs": "docusaurus clean-api-docs all --plugin-id ads-api --all-versions && docusaurus clean-api-docs all --plugin-id ad-engine-api && docusaurus clean-api-docs all --plugin-id millicast-api && docusaurus clean-api-docs all --plugin-id theolive-api --all-versions", "typecheck": "tsc", "check-format": "prettier --check .", "format": "prettier --write .", diff --git a/redirectsAds.json b/redirectsAds.json index de04cf674b56..ed0023a6fdc5 100644 --- a/redirectsAds.json +++ b/redirectsAds.json @@ -65,15 +65,15 @@ }, { "from": "/theoads/getting-started/android/", - "to": "/ads/getting-started/android/" + "to": "/ads/player-integration/optiview-player/android/" }, { "from": "/theoads/getting-started/ios/", - "to": "/ads/getting-started/ios/" + "to": "/ads/player-integration/optiview-player/ios/" }, { "from": "/theoads/getting-started/react-native/", - "to": "/ads/getting-started/react-native/" + "to": "/ads/player-integration/optiview-player/react-native/" }, { "from": "/theoads/getting-started/signaling-service/", @@ -81,6 +81,26 @@ }, { "from": "/theoads/getting-started/web/", - "to": "/ads/getting-started/web/" + "to": "/ads/player-integration/optiview-player/web/" + }, + { + "from": "/ads/getting-started/web/", + "to": "/ads/player-integration/optiview-player/web/" + }, + { + "from": "/ads/getting-started/android/", + "to": "/ads/player-integration/optiview-player/android/" + }, + { + "from": "/ads/getting-started/ios/", + "to": "/ads/player-integration/optiview-player/ios/" + }, + { + "from": "/ads/getting-started/chromecast/", + "to": "/ads/player-integration/optiview-player/chromecast/" + }, + { + "from": "/ads/getting-started/react-native/", + "to": "/ads/player-integration/optiview-player/react-native/" } ] diff --git a/sidebarsAds.ts b/sidebarsAds.ts index c0ca37d24082..3de9ca7c019c 100644 --- a/sidebarsAds.ts +++ b/sidebarsAds.ts @@ -15,6 +15,83 @@ const sidebars: SidebarsConfig = { link: { type: 'doc', id: 'getting-started/index' }, items: [{ type: 'autogenerated', dirName: 'getting-started' }], }, + { + type: 'category', + label: 'Core concepts', + description: 'Understand the resources that power OptiView Ads.', + collapsed: false, + customProps: { + icon: '📚', + }, + items: [{ type: 'autogenerated', dirName: 'concepts' }], + }, + { + type: 'category', + label: 'Vendors', + description: 'Configure vendor integrations for server-guided and server-side ad insertion.', + customProps: { + icon: '🏷️', + }, + link: { type: 'doc', id: 'vendors/index' }, + items: [{ type: 'autogenerated', dirName: 'vendors' }], + }, + { + type: 'category', + label: 'Player integration', + description: 'Connect OptiView Ads to a player.', + collapsed: false, + customProps: { + icon: '▶️', + }, + link: { type: 'doc', id: 'player-integration/index' }, + items: [ + { + type: 'category', + label: 'OptiView Player', + customProps: { icon: '🎬' }, + link: { type: 'doc', id: 'player-integration/optiview-player/index' }, + items: [ + 'player-integration/optiview-player/web', + 'player-integration/optiview-player/android', + 'player-integration/optiview-player/ios', + 'player-integration/optiview-player/chromecast', + 'player-integration/optiview-player/react-native', + { + type: 'link', + label: 'Web TheoAdDescription API', + href: 'https://optiview.dolby.com/docs/theoplayer/v11/api-reference/web/interfaces/TheoAdDescription.html', + }, + { + type: 'link', + label: 'Android TheoAdDescription API', + href: 'https://optiview.dolby.com/docs/theoplayer/v11/api-reference/android/com/theoplayer/android/api/ads/theoads/TheoAdDescription.html', + }, + { + type: 'link', + label: 'iOS THEOAdDescription API', + href: 'https://optiview.dolby.com/docs/theoplayer/v11/api-reference/ios/Structs/THEOAdDescription.html', + }, + { + type: 'link', + label: 'React Native TheoAdDescription API', + href: 'https://theoplayer.github.io/react-native-theoplayer/api/interfaces/TheoAdDescription.html', + }, + ], + }, + { + type: 'category', + label: 'Bring your own player', + customProps: { icon: '🧩' }, + link: { type: 'doc', id: 'player-integration/bring-your-own-player/index' }, + items: [ + 'player-integration/bring-your-own-player/hls-js', + 'player-integration/bring-your-own-player/shaka-player', + 'player-integration/bring-your-own-player/exoplayer-media3', + 'player-integration/bring-your-own-player/avplayer', + ], + }, + ], + }, { type: 'category', label: 'How-to guides', @@ -54,19 +131,7 @@ const sidebars: SidebarsConfig = { type: 'link', label: 'Web', customProps: { icon: 'web' }, - href: 'pathname:///theoplayer/v9/api-reference/web/interfaces/TheoAdDescription.html', - }, - { - type: 'link', - label: 'Android', - customProps: { icon: 'android' }, - href: 'pathname:///theoplayer/v9/api-reference/android/com/theoplayer/android/api/ads/theoads/package-summary.html', - }, - { - type: 'link', - label: 'React Native', - customProps: { icon: 'react' }, - href: 'https://theoplayer.github.io/react-native-theoplayer/api/interfaces/TheoAdDescription.html', + href: 'https://optiview.dolby.com/docs/theoplayer/v11/api-reference/web/interfaces/TheoAdDescription.html', }, ], }, diff --git a/theoplayer/getting-started/01-sdks/02-android/01-features.md b/theoplayer/getting-started/01-sdks/02-android/01-features.md index a8c1fe027a57..def7acc28b4f 100644 --- a/theoplayer/getting-started/01-sdks/02-android/01-features.md +++ b/theoplayer/getting-started/01-sdks/02-android/01-features.md @@ -98,7 +98,7 @@ With Android THEOplayer SDK we currently support the following feature integrati | Chromecast | `integration-cast:+` | 3.4.0+ | [Chromecast Documentation](../../../how-to-guides/03-cast/01-chromecast/00-introduction.md#android-sdk) | | AWS MediaTailor | `integration-ads-mediatailor:+` | 4.0.0+ | [AWS MediaTailor Documentation](../../../how-to-guides/android/ads/mediatailor.mdx) | | Google DAI | `integration-ads-dai:+` | 4.3.0+ | [Google DAI Documentation](../../../how-to-guides/android/ads/google-dai.mdx) | -| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/getting-started/android/) | +| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/player-integration/optiview-player/android/) | | Millicast | `integration-millicast:+` | 8.2.0+ | [Millicast Documentation](pathname:///millicast/android-player/) | | Jetpack Media3 | `integration-media3:+` | 8.10.0+ | [Media3 Documentation](../../../how-to-guides/android/media3/getting-started.mdx) | diff --git a/theoplayer_versioned_docs/version-v10/getting-started/01-sdks/02-android/01-features.md b/theoplayer_versioned_docs/version-v10/getting-started/01-sdks/02-android/01-features.md index 052414866181..dc780d9ac89a 100644 --- a/theoplayer_versioned_docs/version-v10/getting-started/01-sdks/02-android/01-features.md +++ b/theoplayer_versioned_docs/version-v10/getting-started/01-sdks/02-android/01-features.md @@ -98,7 +98,7 @@ With Android THEOplayer SDK we currently support the following feature integrati | Chromecast | `integration-cast:+` | 3.4.0+ | [Chromecast Documentation](../../../how-to-guides/03-cast/01-chromecast/00-introduction.md#android-sdk) | | AWS MediaTailor | `integration-ads-mediatailor:+` | 4.0.0+ | [AWS MediaTailor Documentation](../../../how-to-guides/01-ads/12-mediatailor.md#android-sdk) | | Google DAI | `integration-ads-dai:+` | 4.3.0+ | [Google DAI Documentation](../../../how-to-guides/01-ads/08-google-dai.md#android-sdk) | -| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/getting-started/android/) | +| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/player-integration/optiview-player/android/) | | Millicast | `integration-millicast:+` | 8.2.0+ | [Millicast Documentation](pathname:///millicast/android-player/) | | Jetpack Media3 | `integration-media3:+` | 8.10.0+ | [Media3 Documentation](../../../how-to-guides/android/media3/getting-started.mdx) | diff --git a/theoplayer_versioned_docs/version-v8/getting-started/01-sdks/02-android/01-features.md b/theoplayer_versioned_docs/version-v8/getting-started/01-sdks/02-android/01-features.md index 22ac07897eb3..0a5dde20aa97 100644 --- a/theoplayer_versioned_docs/version-v8/getting-started/01-sdks/02-android/01-features.md +++ b/theoplayer_versioned_docs/version-v8/getting-started/01-sdks/02-android/01-features.md @@ -98,7 +98,7 @@ With Android THEOplayer SDK we currently support the following feature integrati | Chromecast | `integration-cast:+` | 3.4.0+ | [Chromecast Documentation](../../../how-to-guides/03-cast/01-chromecast/00-introduction.md#android-sdk) | | AWS MediaTailor | `integration-ads-mediatailor:+` | 4.0.0+ | [AWS MediaTailor Documentation](../../../how-to-guides/01-ads/12-mediatailor.md#android-sdk) | | Google DAI | `integration-ads-dai:+` | 4.3.0+ | [Google DAI Documentation](../../../how-to-guides/01-ads/08-google-dai.md#android-sdk) | -| THEOads | `integration-ads-theoads:+` | 8.2.0+ | [THEOads Documentation](/ads/getting-started/android/) | +| THEOads | `integration-ads-theoads:+` | 8.2.0+ | [THEOads Documentation](/ads/player-integration/optiview-player/android/) | | Millicast | `integration-millicast:+` | 8.2.0+ | [Millicast Documentation](../../../how-to-guides/android/millicast/getting-started.mdx) | | Jetpack Media3 | `integration-media3:+` | 8.10.0+ | [Media3 Documentation](../../../how-to-guides/android/media3/getting-started.mdx) | diff --git a/theoplayer_versioned_docs/version-v9/getting-started/01-sdks/02-android/01-features.md b/theoplayer_versioned_docs/version-v9/getting-started/01-sdks/02-android/01-features.md index f62e5969cdfd..696c9d1097fe 100644 --- a/theoplayer_versioned_docs/version-v9/getting-started/01-sdks/02-android/01-features.md +++ b/theoplayer_versioned_docs/version-v9/getting-started/01-sdks/02-android/01-features.md @@ -98,7 +98,7 @@ With Android THEOplayer SDK we currently support the following feature integrati | Chromecast | `integration-cast:+` | 3.4.0+ | [Chromecast Documentation](../../../how-to-guides/03-cast/01-chromecast/00-introduction.md#android-sdk) | | AWS MediaTailor | `integration-ads-mediatailor:+` | 4.0.0+ | [AWS MediaTailor Documentation](../../../how-to-guides/01-ads/12-mediatailor.md#android-sdk) | | Google DAI | `integration-ads-dai:+` | 4.3.0+ | [Google DAI Documentation](../../../how-to-guides/01-ads/08-google-dai.md#android-sdk) | -| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/getting-started/android/) | +| Optiview Ads | `integration-ads-theoads:+` | 8.2.0+ | [Optiview Ads Documentation](/ads/player-integration/optiview-player/android/) | | Millicast | `integration-millicast:+` | 8.2.0+ | [Millicast Documentation](pathname:///millicast/android-player/) | | Jetpack Media3 | `integration-media3:+` | 8.10.0+ | [Media3 Documentation](../../../how-to-guides/android/media3/getting-started.mdx) |