From 79777492dc82487b2e9b3589a8f6b8753ac44608 Mon Sep 17 00:00:00 2001 From: Maarten Rimaux Date: Fri, 9 Oct 2026 12:51:29 +0200 Subject: [PATCH 1/4] ADS-631 Ads: how-to guide to serve the Break Manifest from your CDN Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../break-manifest/served-from-your-cdn.svg | 23 ++ ads/concepts/break-manifest.mdx | 17 +- ads/how-to-guides/index.mdx | 2 +- .../serve-break-manifest-from-your-cdn.mdx | 198 ++++++++++++++++++ 4 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 ads/assets/img/break-manifest/served-from-your-cdn.svg create mode 100644 ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx diff --git a/ads/assets/img/break-manifest/served-from-your-cdn.svg b/ads/assets/img/break-manifest/served-from-your-cdn.svg new file mode 100644 index 000000000000..93e4b3ce8bbd --- /dev/null +++ b/ads/assets/img/break-manifest/served-from-your-cdn.svg @@ -0,0 +1,23 @@ + + + + + + + + + Origin + + OptiView Ads + + Your CDN + example.com + + Player + Ads SDK + + media stream + + break manifest + + one domain + diff --git a/ads/concepts/break-manifest.mdx b/ads/concepts/break-manifest.mdx index 9c0f5f383194..960bffdc8ef9 100644 --- a/ads/concepts/break-manifest.mdx +++ b/ads/concepts/break-manifest.mdx @@ -39,6 +39,10 @@ The example uses the US region (`https://us.markers.optiview.dolby.com`). For th Responses carry a `Cache-Control` header aligned with the channel's active polling interval, so a cached copy is never held longer than the fastest polling cadence the channel advertises. +:::tip[Serve it from your own domain] +You can serve the Break Manifest from your own CDN domain, next to your stream, by forwarding a path to this endpoint. See [Serve the Break Manifest from your CDN](../how-to-guides/serve-break-manifest-from-your-cdn.mdx). +::: + ## Manifest envelope The Break Manifest document contains the following top-level properties: @@ -73,9 +77,10 @@ The Break Manifest reflects the breaks that are currently relevant for delivery, ## Related resources -| Resource | Relationship | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------- | -| [Getting started](/ads/getting-started/) | Integrating the OptiView Player that polls the Break Manifest. | -| [Channels](./channels.mdx) | The parent of the Break Manifest. The timebase and delivery window shape which breaks are included. | -| [Breaks](./breaks.mdx) | Scheduled or detected ad opportunities announced through the manifest. | -| [Integrations](../integrations/google/index.mdx) | Channel-level delivery integrations, such as Server-Side Ad Insertion with Google DAI. | +| Resource | Relationship | +| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| [Getting started](/ads/getting-started/) | Integrating the OptiView Player that polls the Break Manifest. | +| [Channels](./channels.mdx) | The parent of the Break Manifest. The timebase and delivery window shape which breaks are included. | +| [Breaks](./breaks.mdx) | Scheduled or detected ad opportunities announced through the manifest. | +| [Integrations](../integrations/google/index.mdx) | Channel-level delivery integrations, such as Server-Side Ad Insertion with Google DAI. | +| [Serve the Break Manifest from your CDN](../how-to-guides/serve-break-manifest-from-your-cdn.mdx) | Serving the Break Manifest from your own domain, next to your stream. | diff --git a/ads/how-to-guides/index.mdx b/ads/how-to-guides/index.mdx index 0ec2d9f43119..4d063a7b633c 100644 --- a/ads/how-to-guides/index.mdx +++ b/ads/how-to-guides/index.mdx @@ -5,7 +5,7 @@ sidebar_label: How-to guides # How-to guides -These guides cover common tasks when working with OptiView Ads (SGAI & SSAI). They explain how OptiView Ads fits into your existing video workflow, how ad impressions are tracked, how to pass ad targeting parameters from the backend and the client, how to serve a different ad experience per device with variants, and how to create backdrop images for the advanced layouts. +These guides cover common tasks when working with OptiView Ads (SGAI & SSAI). They explain how OptiView Ads fits into your existing video workflow, how ad impressions are tracked, how to pass ad targeting parameters from the backend and the client, how to serve a different ad experience per device with variants, how to create backdrop images for the advanced layouts, and how to serve the Break Manifest from your own CDN domain. import DocCardList from '@theme/DocCardList'; diff --git a/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx new file mode 100644 index 000000000000..73050e21a66c --- /dev/null +++ b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx @@ -0,0 +1,198 @@ +--- +sidebar_position: 6 +sidebar_label: Serve the Break Manifest from your CDN +--- + +# Serve the Break Manifest from your CDN + +By default, your player fetches your stream from your CDN, while the OptiView Ads SDK fetches the [Break Manifest](../concepts/break-manifest.mdx) from the OptiView Ads domain (`us.markers.optiview.dolby.com`). This guide shows how to serve the Break Manifest from **your own domain** instead, by letting your CDN forward a path to OptiView Ads. Your app then fetches everything from one domain you control: + +| Resource | Default | Served from your CDN | +| -------------- | ------------------------------------------------------------------------------ | --------------------------------------------- | +| Stream | `https://example.com/channel-5/index.m3u8` | `https://example.com/channel-5/index.m3u8` | +| Break Manifest | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://example.com/channel-5/timeline.json` | + +No change is needed on the OptiView Ads side: the setup is a routing rule on your CDN and a different `manifestUrl` in your player. + +## Why serve the Break Manifest from your domain + +When the Break Manifest comes from a separate, recognizable domain, a few things are outside your control: + +- **Ad blockers can block it.** Blockers maintain lists of ad-related domains. When the Break Manifest request is blocked, the SDK learns about no breaks and the viewer sees no ads, while the stream keeps playing normally. +- **Every app and network needs to allow a second domain.** Content Security Policies, firewalls, and network rules on managed networks must include the OptiView Ads domain next to your own. +- **You have no view of the traffic.** Requests to the OptiView Ads domain do not show up in your CDN logs, monitoring, or analytics. + +Serving the Break Manifest from your own domain solves this: + +- **Harder to block.** The Break Manifest arrives on the same domain as your stream. A blocker can no longer block it by domain without also blocking your content. See [Ad blockers: what this solves and what it does not](#ad-blockers-what-this-solves-and-what-it-does-not). +- **One domain to allow.** Your apps and your viewers' networks only need to reach your domain. +- **One place to observe.** Break Manifest requests appear in your own CDN logs and dashboards, next to your stream. + +## How it works + +Your CDN gets one extra routing rule. Requests for the stream go to your origin as today; requests for the Break Manifest path are forwarded to OptiView Ads. Your app only sees your domain. + +![Served from your CDN diagram](../assets/img/break-manifest/served-from-your-cdn.svg) + +OptiView Ads keeps producing the Break Manifest exactly as before, including its caching headers and signature. Your CDN only needs to pass the response through unchanged. + +## Before you start + +You need: + +- **Your organization ID and the channel ID.** They are part of the channel's Break Manifest URL; see [retrieving the required values](../player-integration/optiview-ads-sdk/index.mdx#retrieving-the-required-values). +- **Your region's OptiView Ads domain.** `us.markers.optiview.dolby.com` for the US region, or `eu.markers.optiview.dolby.com` for the EU region. +- **Access to your CDN's routing configuration.** Your CDN must be able to route a path to a different origin and **rewrite the path** on the way. Most CDNs support this through path rewrite rules or edge functions. + +## Step 1: Choose the Break Manifest path + +Pick the URL the SDK will use for the Break Manifest. Two patterns work well: + +| Pattern | Example | Best for | +| ------------------------------------ | --------------------------------------------------- | ------------------------------------------------------- | +| **Next to the stream** (per channel) | `https://example.com/channel-5/timeline.json` | Making the Break Manifest as hard to block as possible. | +| **One prefix for all channels** | `https://example.com/bm/ORG-ID/channels/CHANNEL-ID` | A single rule that serves every channel. | + +When ad blocking matters to you, place the Break Manifest **under the same path as the stream** and give it a **neutral name**: + +- **Use the stream's path prefix.** A rule that blocks the whole prefix (`example.com/channel-5/*`) also blocks your stream, so blocking only the Break Manifest takes a rule written for that exact URL. +- **Avoid recognizable words.** Names containing `ad`, `ads`, `break`, `marker`, or `manifest` are likely to match generic filter lists. Neutral names such as `timeline.json` or `state.json` are not. +- **Keep it stable.** The URL goes into your player configuration, so choose a name you do not need to change. + +## Step 2: Forward the path to OptiView Ads + +Add a rule on your CDN that sends the Break Manifest path to OptiView Ads and rewrites it to the channel's Break Manifest endpoint, `/manifest/v1/{orgId}/channels/{channelId}`: + +| Pattern | Incoming request | Forwarded to | +| ------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------- | +| **Next to the stream** | `/channel-5/timeline.json` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | +| **One prefix for all channels** | `/bm/{orgId}/channels/{channelId}` | `https://us.markers.optiview.dolby.com/manifest/v1/{orgId}/channels/{channelId}` | + +With the per-channel pattern, add one rule per channel. With the prefix pattern, a single rule replaces the `/bm` prefix with `/manifest/v1` and keeps the rest of the path. + +Configure the OptiView Ads origin in your CDN as follows: + +- **Protocol:** HTTPS on port 443. +- **Host header and TLS server name (SNI):** your region's OptiView Ads domain, for example `us.markers.optiview.dolby.com`, not your own domain. OptiView Ads routes requests by host name. +- **Methods:** `GET`, `HEAD`, and `OPTIONS`. +- **Forwarded request headers:** none are required. Do not forward cookies or authorization headers; the Break Manifest endpoint is public and ignores them. + +## Step 3: Keep the response intact + +Your CDN must deliver the Break Manifest response to the player as OptiView Ads sends it. Check each of the following, because a miss can stop ads silently while your stream keeps playing: + +| Requirement | Why | If it is missed | +| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| **Forward the `X-Manifest-Signature` header** | Every Break Manifest response is signed. The SDK verifies the signature before it uses the manifest. | The SDK rejects the manifest and schedules no breaks. | +| **Do not modify the body** | The signature covers the exact response body. Compression (gzip, Brotli) is fine; minifying, reformatting, or injecting content is not. | The signature no longer matches; the SDK rejects the manifest and schedules no breaks. | +| **Respect the origin's `Cache-Control`** | For live channels, the cache lifetime equals the channel's active polling interval (1 second by default), so players learn about new breaks and changes in time. Errors are cached for a few seconds only. | Players receive breaks late or miss them, or keep seeing an error after it is resolved. | +| **Cache on the path only** | Every viewer of a channel receives the same Break Manifest. Leave query strings, cookies, and device headers out of the cache key, but keep honoring `Vary: Accept-Encoding`. | Your cache hit ratio drops, or clients receive a compressed response they cannot decode. | +| **Pass the CORS headers through** (web only) | Browsers need `Access-Control-Allow-Origin` to read the response, and `Access-Control-Expose-Headers: X-Manifest-Signature` to let the SDK read the signature. Only needed when your web page runs on another domain. | The browser blocks the request, or hides the signature so the SDK rejects the manifest. | + +:::tip[Adding your own CORS headers] +If your CDN sets its own CORS headers instead of passing through those from OptiView Ads, include `X-Manifest-Signature` in `Access-Control-Expose-Headers`. Without it, browsers hide the signature from the SDK. +::: + +## Step 4: Point the SDK at your URL + +Pass your own URL as the `manifestUrl` when you start the session. Nothing else in your integration changes. See the [OptiView Ads SDK](../player-integration/optiview-ads-sdk/index.mdx) documentation for the full integration. + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +```typescript +await sdk.startSession({ + manifestUrl: 'https://example.com/channel-5/timeline.json', +}); +``` + + + + +```kotlin +sdk.startSession( + SessionConfig( + manifestUrl = "https://example.com/channel-5/timeline.json", + ) +) +``` + + + + +```swift +try await sdk.startSession( + SessionConfig( + manifestUrl: "https://example.com/channel-5/timeline.json" + ) +) +``` + + + + +```typescript +await ads.startSession({ + manifestUrl: 'https://example.com/channel-5/timeline.json', +}); +``` + + + + +## Verify your setup + +Request the Break Manifest through your CDN and check the response headers: + +```bash +curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' 'https://example.com/channel-5/timeline.json' +``` + +A working setup returns: + +```text +HTTP/2 200 +content-type: application/json; charset=utf-8 +cache-control: public, max-age=1 +access-control-allow-origin: * +access-control-expose-headers: X-Manifest-Signature +x-manifest-signature: eyJhbGciOiJFZERTQSIs... +``` + +Check that: + +- the status is `200` and the body is the channel's Break Manifest, with the expected `channelId`; +- `x-manifest-signature` is present; +- `cache-control` shows the same `max-age` as a request made directly to the OptiView Ads domain; +- on a second request within that `max-age`, your CDN reports a cache hit, and after it expires the response is refreshed. + +Then play the channel with the new `manifestUrl` and confirm that a break plays. If the SDK rejects the manifest, it reports the `DA-MANIFEST-SIGNATURE-INVALID` diagnostic. + +## Ad blockers: what this solves and what it does not + +Serving the Break Manifest from your domain makes it considerably harder to block, but it is not a guarantee: + +- **DNS-level blockers** — such as Pi-hole, NextDNS, or blocking on the router — only see domain names. They cannot block the Break Manifest without blocking your stream. +- **Browser extensions** — such as uBlock Origin — match full URLs. Generic filter lists no longer catch the Break Manifest, but a rule written for your exact URL can still block it. Following the path advice in [step 1](#step-1-choose-the-break-manifest-path) makes such a rule a deliberate, site-specific effort. +- **The ads themselves** — the ad creatives, ad server responses, ad SDKs (such as the Google IMA SDK), and tracking requests — come from the ad server's domains. Blockers can block those regardless of where the Break Manifest is served from. + +## Troubleshooting + +| Symptom | Likely cause | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `404 Not found` from your URL | The path rewrite is wrong (check the organization and channel IDs), or the rule does not match the incoming path. | +| Error page from OptiView Ads, or a TLS error | The `Host` header or TLS server name is set to your domain instead of the OptiView Ads domain. | +| Stream plays, no breaks, `DA-MANIFEST-SIGNATURE-INVALID` | The `X-Manifest-Signature` header is stripped, the body is modified, or (on web) the header is not exposed through CORS. | +| Breaks start late or changes arrive late | Your CDN caches the Break Manifest longer than the origin's `Cache-Control` allows. | +| Works in the apps but not in the browser | CORS headers are missing or replaced on the way through your CDN. | + +## Related resources + +| Resource | Description | +| -------------------------------------------------------------------- | ------------------------------------------------------------ | +| [Break Manifest](../concepts/break-manifest.mdx) | The side-loaded contract between the backend and the player. | +| [Workflow integration](./workflow-integration.md) | How OptiView Ads fits into your existing delivery workflow. | +| [OptiView Ads SDK](../player-integration/optiview-ads-sdk/index.mdx) | Integrating the SDK with your player. | From e2297a84ccfbaf25f1d257ef32e8de3a8799f14a Mon Sep 17 00:00:00 2001 From: Maarten Rimaux Date: Fri, 9 Oct 2026 16:20:47 +0200 Subject: [PATCH 2/4] ADS-631 Ads: drop the Break Manifest tip, keep the related resource link --- ads/concepts/break-manifest.mdx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ads/concepts/break-manifest.mdx b/ads/concepts/break-manifest.mdx index 960bffdc8ef9..494c6afe6ec9 100644 --- a/ads/concepts/break-manifest.mdx +++ b/ads/concepts/break-manifest.mdx @@ -39,10 +39,6 @@ The example uses the US region (`https://us.markers.optiview.dolby.com`). For th Responses carry a `Cache-Control` header aligned with the channel's active polling interval, so a cached copy is never held longer than the fastest polling cadence the channel advertises. -:::tip[Serve it from your own domain] -You can serve the Break Manifest from your own CDN domain, next to your stream, by forwarding a path to this endpoint. See [Serve the Break Manifest from your CDN](../how-to-guides/serve-break-manifest-from-your-cdn.mdx). -::: - ## Manifest envelope The Break Manifest document contains the following top-level properties: From 1f5de27bb25e3f6b6f405beb12f4b619a6270434 Mon Sep 17 00:00:00 2001 From: Maarten Rimaux Date: Mon, 12 Oct 2026 13:40:34 +0200 Subject: [PATCH 3/4] ADS-631 Ads: apply review feedback on the Break Manifest CDN guide --- .../break-manifest/served-from-your-cdn.svg | 35 ++--- .../serve-break-manifest-from-your-cdn.mdx | 122 ++++++++---------- 2 files changed, 70 insertions(+), 87 deletions(-) diff --git a/ads/assets/img/break-manifest/served-from-your-cdn.svg b/ads/assets/img/break-manifest/served-from-your-cdn.svg index 93e4b3ce8bbd..f952528abf5d 100644 --- a/ads/assets/img/break-manifest/served-from-your-cdn.svg +++ b/ads/assets/img/break-manifest/served-from-your-cdn.svg @@ -1,23 +1,26 @@ - + - - Origin - - OptiView Ads - - Your CDN - example.com - - Player + Ads SDK - - media stream - - break manifest - - one domain + + Origin + origin.example.com + + OptiView Ads + us.markers.optiview.dolby.com + + Your CDN + example.com + + Player + Ads SDK + + media stream + + break manifest + + /channel-5/index.m3u8 + /channel-5/timeline/… diff --git a/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx index 73050e21a66c..1c6b2fa7d21c 100644 --- a/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx +++ b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx @@ -5,28 +5,18 @@ sidebar_label: Serve the Break Manifest from your CDN # Serve the Break Manifest from your CDN -By default, your player fetches your stream from your CDN, while the OptiView Ads SDK fetches the [Break Manifest](../concepts/break-manifest.mdx) from the OptiView Ads domain (`us.markers.optiview.dolby.com`). This guide shows how to serve the Break Manifest from **your own domain** instead, by letting your CDN forward a path to OptiView Ads. Your app then fetches everything from one domain you control: +By default, your player fetches your stream from your CDN, while the OptiView Ads SDK fetches the [Break Manifest](../concepts/break-manifest.mdx) from the OptiView Ads domain of your region. This guide shows how to serve the Break Manifest from **your own domain** instead, by letting your CDN forward a path to OptiView Ads. Your app then fetches everything from one domain you control: -| Resource | Default | Served from your CDN | -| -------------- | ------------------------------------------------------------------------------ | --------------------------------------------- | -| Stream | `https://example.com/channel-5/index.m3u8` | `https://example.com/channel-5/index.m3u8` | -| Break Manifest | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://example.com/channel-5/timeline.json` | +| Resource | Default | Served from your CDN | +| -------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| Stream | `https://example.com/channel-5/index.m3u8` | `https://example.com/channel-5/index.m3u8` | +| Break Manifest | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | No change is needed on the OptiView Ads side: the setup is a routing rule on your CDN and a different `manifestUrl` in your player. -## Why serve the Break Manifest from your domain - -When the Break Manifest comes from a separate, recognizable domain, a few things are outside your control: - -- **Ad blockers can block it.** Blockers maintain lists of ad-related domains. When the Break Manifest request is blocked, the SDK learns about no breaks and the viewer sees no ads, while the stream keeps playing normally. -- **Every app and network needs to allow a second domain.** Content Security Policies, firewalls, and network rules on managed networks must include the OptiView Ads domain next to your own. -- **You have no view of the traffic.** Requests to the OptiView Ads domain do not show up in your CDN logs, monitoring, or analytics. - -Serving the Break Manifest from your own domain solves this: - -- **Harder to block.** The Break Manifest arrives on the same domain as your stream. A blocker can no longer block it by domain without also blocking your content. See [Ad blockers: what this solves and what it does not](#ad-blockers-what-this-solves-and-what-it-does-not). -- **One domain to allow.** Your apps and your viewers' networks only need to reach your domain. -- **One place to observe.** Break Manifest requests appear in your own CDN logs and dashboards, next to your stream. +:::note[Regional domains] +The examples on this page use the US region (`https://us.markers.optiview.dolby.com`). For the EU region, replace `us.` with `eu.` (`https://eu.markers.optiview.dolby.com`). +::: ## How it works @@ -40,40 +30,44 @@ OptiView Ads keeps producing the Break Manifest exactly as before, including its You need: -- **Your organization ID and the channel ID.** They are part of the channel's Break Manifest URL; see [retrieving the required values](../player-integration/optiview-ads-sdk/index.mdx#retrieving-the-required-values). -- **Your region's OptiView Ads domain.** `us.markers.optiview.dolby.com` for the US region, or `eu.markers.optiview.dolby.com` for the EU region. -- **Access to your CDN's routing configuration.** Your CDN must be able to route a path to a different origin and **rewrite the path** on the way. Most CDNs support this through path rewrite rules or edge functions. +- **Your organization ID and the channel ID.** They are part of the channel's Break Manifest URL; see [retrieving the required values](../player-integration/optiview-ads-sdk/index.mdx#retrieving-the-required-values). On this page, `ORG-ID` and `CHANNEL-ID` are placeholders for these values. +- **The OptiView Ads domain of your organization's region.** Use the same region (`us.` or `eu.`) as the OptiView Ads API you work with today. + + | Region | OptiView Ads domain | + | ------ | ------------------------------- | + | US | `us.markers.optiview.dolby.com` | + | EU | `eu.markers.optiview.dolby.com` | + +- **Access to your CDN's routing configuration.** Your CDN must be able to route a path to a different origin and remove a path prefix on the way. Most CDNs support this through path rewrite rules or edge functions. -## Step 1: Choose the Break Manifest path +## Step 1: Choose the Break Manifest URL -Pick the URL the SDK will use for the Break Manifest. Two patterns work well: +Pick the URL the SDK will use for the Break Manifest. In both approaches below, the OptiView Ads path (`/manifest/v1/ORG-ID/channels/CHANNEL-ID`) is kept as is at the end of the URL. Your CDN forwards it unchanged, so one rule serves every channel and future versions of the endpoint need no CDN change. -| Pattern | Example | Best for | -| ------------------------------------ | --------------------------------------------------- | ------------------------------------------------------- | -| **Next to the stream** (per channel) | `https://example.com/channel-5/timeline.json` | Making the Break Manifest as hard to block as possible. | -| **One prefix for all channels** | `https://example.com/bm/ORG-ID/channels/CHANNEL-ID` | A single rule that serves every channel. | +| Approach | Example | CDN rule | +| ----------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| **Next to each stream** (recommended) | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Remove everything up to and including `/timeline`, forward the rest. | +| **One domain for all channels** (simpler) | `https://timeline.example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Forward every request on this domain unchanged. | -When ad blocking matters to you, place the Break Manifest **under the same path as the stream** and give it a **neutral name**: +`timeline` is only an example: choose any name for the segment or the domain. Keep these points in mind: -- **Use the stream's path prefix.** A rule that blocks the whole prefix (`example.com/channel-5/*`) also blocks your stream, so blocking only the Break Manifest takes a rule written for that exact URL. -- **Avoid recognizable words.** Names containing `ad`, `ads`, `break`, `marker`, or `manifest` are likely to match generic filter lists. Neutral names such as `timeline.json` or `state.json` are not. +- **Next to each stream** places the Break Manifest under the same path as the stream it belongs to, so both share your stream's routing and logging. +- **Choose a neutral name.** Avoid recognizable words such as `ad`, `ads`, or `break`. - **Keep it stable.** The URL goes into your player configuration, so choose a name you do not need to change. ## Step 2: Forward the path to OptiView Ads -Add a rule on your CDN that sends the Break Manifest path to OptiView Ads and rewrites it to the channel's Break Manifest endpoint, `/manifest/v1/{orgId}/channels/{channelId}`: +Add a rule on your CDN that sends the Break Manifest requests to OptiView Ads, with everything from `/manifest/v1` onwards forwarded unchanged: -| Pattern | Incoming request | Forwarded to | -| ------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------- | -| **Next to the stream** | `/channel-5/timeline.json` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | -| **One prefix for all channels** | `/bm/{orgId}/channels/{channelId}` | `https://us.markers.optiview.dolby.com/manifest/v1/{orgId}/channels/{channelId}` | - -With the per-channel pattern, add one rule per channel. With the prefix pattern, a single rule replaces the `/bm` prefix with `/manifest/v1` and keeps the rest of the path. +| Approach | Incoming request | Forwarded to | +| ------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| **Next to each stream** | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | +| **One domain for all channels** | `https://timeline.example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Configure the OptiView Ads origin in your CDN as follows: - **Protocol:** HTTPS on port 443. -- **Host header and TLS server name (SNI):** your region's OptiView Ads domain, for example `us.markers.optiview.dolby.com`, not your own domain. OptiView Ads routes requests by host name. +- **Host header and TLS server name (SNI):** the OptiView Ads domain of your region, not your own domain. OptiView Ads routes requests by host name. - **Methods:** `GET`, `HEAD`, and `OPTIONS`. - **Forwarded request headers:** none are required. Do not forward cookies or authorization headers; the Break Manifest endpoint is public and ignores them. @@ -81,17 +75,12 @@ Configure the OptiView Ads origin in your CDN as follows: Your CDN must deliver the Break Manifest response to the player as OptiView Ads sends it. Check each of the following, because a miss can stop ads silently while your stream keeps playing: -| Requirement | Why | If it is missed | -| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| **Forward the `X-Manifest-Signature` header** | Every Break Manifest response is signed. The SDK verifies the signature before it uses the manifest. | The SDK rejects the manifest and schedules no breaks. | -| **Do not modify the body** | The signature covers the exact response body. Compression (gzip, Brotli) is fine; minifying, reformatting, or injecting content is not. | The signature no longer matches; the SDK rejects the manifest and schedules no breaks. | -| **Respect the origin's `Cache-Control`** | For live channels, the cache lifetime equals the channel's active polling interval (1 second by default), so players learn about new breaks and changes in time. Errors are cached for a few seconds only. | Players receive breaks late or miss them, or keep seeing an error after it is resolved. | -| **Cache on the path only** | Every viewer of a channel receives the same Break Manifest. Leave query strings, cookies, and device headers out of the cache key, but keep honoring `Vary: Accept-Encoding`. | Your cache hit ratio drops, or clients receive a compressed response they cannot decode. | -| **Pass the CORS headers through** (web only) | Browsers need `Access-Control-Allow-Origin` to read the response, and `Access-Control-Expose-Headers: X-Manifest-Signature` to let the SDK read the signature. Only needed when your web page runs on another domain. | The browser blocks the request, or hides the signature so the SDK rejects the manifest. | - -:::tip[Adding your own CORS headers] -If your CDN sets its own CORS headers instead of passing through those from OptiView Ads, include `X-Manifest-Signature` in `Access-Control-Expose-Headers`. Without it, browsers hide the signature from the SDK. -::: +| Requirement | Why | If it is missed | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| **Forward the `X-Manifest-Signature` header** | Every Break Manifest response is signed. The SDK verifies the signature before it uses the manifest. | The SDK rejects the manifest and schedules no breaks. | +| **Do not modify the body** | The signature covers the exact response body. Compression (gzip, Brotli) is fine; minifying, reformatting, or injecting content is not. | The signature no longer matches; the SDK rejects the manifest and schedules no breaks. | +| **Respect the origin's `Cache-Control`** | For live channels, the cache lifetime equals the channel's active polling interval (1 second by default), so players learn about new breaks and changes in time. | Players receive breaks late or miss them. | +| **Keep the CORS headers** (web) | Browsers need `Access-Control-Allow-Origin` to read the response, and `Access-Control-Expose-Headers: X-Manifest-Signature` to let the SDK read the signature. CDNs pass them through by default; if yours sets its own, expose `X-Manifest-Signature`. | The browser blocks the request, or hides the signature so the SDK rejects the manifest. | ## Step 4: Point the SDK at your URL @@ -105,7 +94,7 @@ import TabItem from '@theme/TabItem'; ```typescript await sdk.startSession({ - manifestUrl: 'https://example.com/channel-5/timeline.json', + manifestUrl: 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID', }); ``` @@ -115,7 +104,7 @@ await sdk.startSession({ ```kotlin sdk.startSession( SessionConfig( - manifestUrl = "https://example.com/channel-5/timeline.json", + manifestUrl = "https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID", ) ) ``` @@ -126,7 +115,7 @@ sdk.startSession( ```swift try await sdk.startSession( SessionConfig( - manifestUrl: "https://example.com/channel-5/timeline.json" + manifestUrl: "https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID" ) ) ``` @@ -136,7 +125,7 @@ try await sdk.startSession( ```typescript await ads.startSession({ - manifestUrl: 'https://example.com/channel-5/timeline.json', + manifestUrl: 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID', }); ``` @@ -148,7 +137,7 @@ await ads.startSession({ Request the Break Manifest through your CDN and check the response headers: ```bash -curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' 'https://example.com/channel-5/timeline.json' +curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID' ``` A working setup returns: @@ -166,28 +155,19 @@ Check that: - the status is `200` and the body is the channel's Break Manifest, with the expected `channelId`; - `x-manifest-signature` is present; -- `cache-control` shows the same `max-age` as a request made directly to the OptiView Ads domain; -- on a second request within that `max-age`, your CDN reports a cache hit, and after it expires the response is refreshed. +- `cache-control` shows the same `max-age` as a request made directly to the OptiView Ads domain. Then play the channel with the new `manifestUrl` and confirm that a break plays. If the SDK rejects the manifest, it reports the `DA-MANIFEST-SIGNATURE-INVALID` diagnostic. -## Ad blockers: what this solves and what it does not - -Serving the Break Manifest from your domain makes it considerably harder to block, but it is not a guarantee: - -- **DNS-level blockers** — such as Pi-hole, NextDNS, or blocking on the router — only see domain names. They cannot block the Break Manifest without blocking your stream. -- **Browser extensions** — such as uBlock Origin — match full URLs. Generic filter lists no longer catch the Break Manifest, but a rule written for your exact URL can still block it. Following the path advice in [step 1](#step-1-choose-the-break-manifest-path) makes such a rule a deliberate, site-specific effort. -- **The ads themselves** — the ad creatives, ad server responses, ad SDKs (such as the Google IMA SDK), and tracking requests — come from the ad server's domains. Blockers can block those regardless of where the Break Manifest is served from. - ## Troubleshooting -| Symptom | Likely cause | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `404 Not found` from your URL | The path rewrite is wrong (check the organization and channel IDs), or the rule does not match the incoming path. | -| Error page from OptiView Ads, or a TLS error | The `Host` header or TLS server name is set to your domain instead of the OptiView Ads domain. | -| Stream plays, no breaks, `DA-MANIFEST-SIGNATURE-INVALID` | The `X-Manifest-Signature` header is stripped, the body is modified, or (on web) the header is not exposed through CORS. | -| Breaks start late or changes arrive late | Your CDN caches the Break Manifest longer than the origin's `Cache-Control` allows. | -| Works in the apps but not in the browser | CORS headers are missing or replaced on the way through your CDN. | +| Symptom | Likely cause | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `404 Not found` from your URL | The rule forwards the wrong path (it must start at `/manifest/v1`), the IDs are wrong, or the CDN forwards to the domain of another region. | +| Error page from OptiView Ads, or a TLS error | The `Host` header or TLS server name is set to your domain instead of the OptiView Ads domain. | +| Stream plays, no breaks, `DA-MANIFEST-SIGNATURE-INVALID` | The `X-Manifest-Signature` header is stripped, the body is modified, or (on web) the header is not exposed through CORS. | +| Breaks start late or changes arrive late | Your CDN caches the Break Manifest longer than the origin's `Cache-Control` allows. | +| Works in the apps but not in the browser | CORS headers are missing or replaced on the way through your CDN. | ## Related resources From bf772726cca1d62d15f39f9f8dd5c06608ef7c3b Mon Sep 17 00:00:00 2001 From: Maarten Rimaux Date: Mon, 12 Oct 2026 14:24:42 +0200 Subject: [PATCH 4/4] ADS-631 Ads: use a /manifest/ path on the customer's domain --- .../break-manifest/served-from-your-cdn.svg | 2 +- .../serve-break-manifest-from-your-cdn.mdx | 44 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/ads/assets/img/break-manifest/served-from-your-cdn.svg b/ads/assets/img/break-manifest/served-from-your-cdn.svg index f952528abf5d..edceab1de2a3 100644 --- a/ads/assets/img/break-manifest/served-from-your-cdn.svg +++ b/ads/assets/img/break-manifest/served-from-your-cdn.svg @@ -22,5 +22,5 @@ break manifest /channel-5/index.m3u8 - /channel-5/timeline/… + /channel-5/manifest/… diff --git a/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx index 1c6b2fa7d21c..e1772a05fe6f 100644 --- a/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx +++ b/ads/how-to-guides/serve-break-manifest-from-your-cdn.mdx @@ -7,10 +7,10 @@ sidebar_label: Serve the Break Manifest from your CDN By default, your player fetches your stream from your CDN, while the OptiView Ads SDK fetches the [Break Manifest](../concepts/break-manifest.mdx) from the OptiView Ads domain of your region. This guide shows how to serve the Break Manifest from **your own domain** instead, by letting your CDN forward a path to OptiView Ads. Your app then fetches everything from one domain you control: -| Resource | Default | Served from your CDN | -| -------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | -| Stream | `https://example.com/channel-5/index.m3u8` | `https://example.com/channel-5/index.m3u8` | -| Break Manifest | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | +| Resource | Default | Served from your CDN | +| -------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | +| Stream | `https://example.com/channel-5/index.m3u8` | `https://example.com/channel-5/index.m3u8` | +| Break Manifest | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID` | No change is needed on the OptiView Ads side: the setup is a routing rule on your CDN and a different `manifestUrl` in your player. @@ -38,31 +38,29 @@ You need: | US | `us.markers.optiview.dolby.com` | | EU | `eu.markers.optiview.dolby.com` | -- **Access to your CDN's routing configuration.** Your CDN must be able to route a path to a different origin and remove a path prefix on the way. Most CDNs support this through path rewrite rules or edge functions. +- **Access to your CDN's routing configuration.** Your CDN must be able to route a path to a different origin. For the recommended approach in step 1, it must also remove a path prefix on the way, which most CDNs support through path rewrite rules or edge functions. ## Step 1: Choose the Break Manifest URL -Pick the URL the SDK will use for the Break Manifest. In both approaches below, the OptiView Ads path (`/manifest/v1/ORG-ID/channels/CHANNEL-ID`) is kept as is at the end of the URL. Your CDN forwards it unchanged, so one rule serves every channel and future versions of the endpoint need no CDN change. +Pick the URL the SDK will use for the Break Manifest. In both approaches below, the URL ends with the OptiView Ads path (`/manifest/v1/ORG-ID/channels/CHANNEL-ID`). Your CDN forwards that path unchanged, so one rule serves every channel and future versions of the endpoint need no CDN change. -| Approach | Example | CDN rule | -| ----------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| **Next to each stream** (recommended) | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Remove everything up to and including `/timeline`, forward the rest. | -| **One domain for all channels** (simpler) | `https://timeline.example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Forward every request on this domain unchanged. | - -`timeline` is only an example: choose any name for the segment or the domain. Keep these points in mind: +| Approach | Example | CDN rule | +| --------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| **Next to each stream** (recommended) | `https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Drop everything before `/manifest/`, forward the rest. | +| **One path for all channels** (simpler) | `https://example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Forward every request under `/manifest/` unchanged, without a rewrite. | - **Next to each stream** places the Break Manifest under the same path as the stream it belongs to, so both share your stream's routing and logging. -- **Choose a neutral name.** Avoid recognizable words such as `ad`, `ads`, or `break`. -- **Keep it stable.** The URL goes into your player configuration, so choose a name you do not need to change. +- **One path for all channels** keeps the CDN configuration to a single rule without any path rewrite. +- In both cases, make sure none of your own content is served under a `/manifest/` path on the same domain, so the rule only matches Break Manifest requests. ## Step 2: Forward the path to OptiView Ads Add a rule on your CDN that sends the Break Manifest requests to OptiView Ads, with everything from `/manifest/v1` onwards forwarded unchanged: -| Approach | Incoming request | Forwarded to | -| ------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| **Next to each stream** | `https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | -| **One domain for all channels** | `https://timeline.example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | +| Approach | Incoming request | Forwarded to | +| ----------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| **Next to each stream** | `https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | +| **One path for all channels** | `https://example.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | `https://us.markers.optiview.dolby.com/manifest/v1/ORG-ID/channels/CHANNEL-ID` | Configure the OptiView Ads origin in your CDN as follows: @@ -94,7 +92,7 @@ import TabItem from '@theme/TabItem'; ```typescript await sdk.startSession({ - manifestUrl: 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID', + manifestUrl: 'https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID', }); ``` @@ -104,7 +102,7 @@ await sdk.startSession({ ```kotlin sdk.startSession( SessionConfig( - manifestUrl = "https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID", + manifestUrl = "https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID", ) ) ``` @@ -115,7 +113,7 @@ sdk.startSession( ```swift try await sdk.startSession( SessionConfig( - manifestUrl: "https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID" + manifestUrl: "https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID" ) ) ``` @@ -125,7 +123,7 @@ try await sdk.startSession( ```typescript await ads.startSession({ - manifestUrl: 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID', + manifestUrl: 'https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID', }); ``` @@ -137,7 +135,7 @@ await ads.startSession({ Request the Break Manifest through your CDN and check the response headers: ```bash -curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' 'https://example.com/channel-5/timeline/manifest/v1/ORG-ID/channels/CHANNEL-ID' +curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' 'https://example.com/channel-5/manifest/v1/ORG-ID/channels/CHANNEL-ID' ``` A working setup returns: