diff --git a/.github/workflows/update-theolive-changelog.yml b/.github/workflows/update-theolive-changelog.yml new file mode 100644 index 000000000000..66d3c7ef1fe4 --- /dev/null +++ b/.github/workflows/update-theolive-changelog.yml @@ -0,0 +1,71 @@ +name: Update THEOlive changelog +on: + # Runs every hour + schedule: + - cron: '0 * * * *' + workflow_dispatch: +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + # The branch that will receive the changelog update (will be created if it doesn't exist) + # Will automatically open a new PR (if no PR exists yet) + UPDATE_BRANCH: update-theolive-changelog + steps: + - name: Create app token + uses: actions/create-github-app-token@v3 + id: app-token + with: + app-id: ${{ vars.THEOPLAYER_BOT_APP_ID }} + private-key: ${{ secrets.THEOPLAYER_BOT_PRIVATE_KEY }} + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 1 + - name: Configure Git user + run: | + git config user.name 'theoplayer-bot[bot]' + git config user.email '873105+theoplayer-bot[bot]@users.noreply.github.com' + - name: Fetch THEOlive changelog + run: | + curl -fsSL -o theolive/changelog.md \ + 'https://engine-changelog.s3.eu-west-3.amazonaws.com/CHANGELOG.md' + - name: Check for changes + id: check_changes + run: | + if git diff --quiet theolive/changelog.md; then + echo "No changelog changes detected." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + - name: Commit and push changes + if: ${{ steps.check_changes.outputs.changed }} + run: | + git checkout -b $UPDATE_BRANCH + git add theolive/changelog.md + git commit -m 'Update THEOlive changelog' + git push --force origin HEAD:$UPDATE_BRANCH + - name: Check if pull request already exists + if: ${{ steps.check_changes.outputs.changed }} + id: check_pr_exists + run: | + pr_count=$(gh pr list --base main --head "$UPDATE_BRANCH" --state open --limit 1 --json number --jq length) + if ((pr_count > 0)); then + echo "exists=true" >> "$GITHUB_OUTPUT" + fi + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Create pull request + if: ${{ steps.check_changes.outputs.changed && !steps.check_pr_exists.outputs.exists }} + run: | + gh pr create \ + --base main \ + --head "$UPDATE_BRANCH" \ + --title "Update THEOlive changelog" \ + --body "This PR pulls in the latest THEOlive engine changelog." + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.gitignore b/.gitignore index a90a143b2321..ab474137bea2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ .docusaurus .cache-loader static/theoplayer-license.txt -theolive/changelog.md # Misc .DS_Store diff --git a/package.json b/package.json index a14a2a83fe73..eca1017a6ae0 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,14 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", - "prestart": "npm run gen-api-docs && npm run fetch-theolive-changelog", + "prestart": "npm run gen-api-docs", "build": "docusaurus build", - "prebuild": "npm run gen-api-docs && npm run fetch-theolive-changelog", + "prebuild": "npm run gen-api-docs", "swizzle": "docusaurus swizzle", "clear": "docusaurus clear", "serve": "node serve.js", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", - "fetch-theolive-changelog": "node scripts/fetch-theolive-changelog.mjs", "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", "typecheck": "tsc", diff --git a/scripts/fetch-theolive-changelog.mjs b/scripts/fetch-theolive-changelog.mjs deleted file mode 100644 index db8108deb159..000000000000 --- a/scripts/fetch-theolive-changelog.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const url = 'https://engine-changelog.s3.eu-west-3.amazonaws.com/CHANGELOG.md'; -const outputPath = path.join(__dirname, '..', 'theolive', 'changelog.md'); - -try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch changelog: ${response.status} ${response.statusText}`); - } - const content = await response.text(); - fs.writeFileSync(outputPath, content); - console.log('Fetched THEOlive engine changelog.'); -} catch (error) { - console.warn(`Warning: Could not fetch THEOlive engine changelog: ${error.message}`); - if (!fs.existsSync(outputPath)) { - fs.writeFileSync(outputPath, '# Changelog\n\nChangelog could not be fetched.\n'); - } -} diff --git a/sidebarsTheolive.ts b/sidebarsTheolive.ts index 5026e893467f..e603227fdb5f 100644 --- a/sidebarsTheolive.ts +++ b/sidebarsTheolive.ts @@ -14,7 +14,12 @@ const sidebars: SidebarsConfig = { icon: '📺', }, description: 'Configure channel-level settings and optional features.', - link: { type: 'generated-index', slug: 'channel' }, + link: { + type: 'generated-index', + slug: 'channel', + description: + 'A channel is the core building block of the platform — it represents a complete live streaming pipeline from media ingestion through to viewer delivery. Each channel is composed of ingests, engines, and distributions. This section covers settings configured at the channel level. See the Architecture page for the full breakdown.', + }, items: [{ type: 'autogenerated', dirName: 'channel' }], }, { diff --git a/theolive/assets/img/image-overlays.png b/theolive/assets/img/image-overlays.png new file mode 100644 index 000000000000..2568723d5d52 Binary files /dev/null and b/theolive/assets/img/image-overlays.png differ diff --git a/theolive/changelog.md b/theolive/changelog.md new file mode 100644 index 000000000000..75dbf4e57427 --- /dev/null +++ b/theolive/changelog.md @@ -0,0 +1,25 @@ +# Changelog + +## [10.13.0] - 2026-03-25 + +- Added image overlay support +- Upgraded to GStreamer 1.28.1 +- Upgraded to Rust 1.94 + +## [10.12.0] - 2026-03-06 + +- Optimized encode of HLS when HESP is disabled +- Allow enabling / disabling of individual protocols +- Improved quality of de-interlacing +- Improved quality of video scaler +- Upgraded to GStreamer 1.28 +- Simplified stream config +- VMAF +- eRTMP built-in from GStreamer +- Improved logging +- Disable PlayReady +- Deprecate /streams + +## [10.9.0] - 2026-01-30 + +- Initial changelog diff --git a/theolive/channel/dvr.mdx b/theolive/channel/dvr.mdx index 7d9e2932b6b6..768fe03bc1be 100644 --- a/theolive/channel/dvr.mdx +++ b/theolive/channel/dvr.mdx @@ -24,8 +24,18 @@ To enable DVR, set a **window size** in seconds on your channel. This determines +The DVR window size is configured at the channel level and applies equally to all distributions. You cannot set different window sizes for different distributions. However, you can enable or disable DVR on a per-distribution basis. + +In addition to setting the window size on the channel, DVR must also be **enabled on each distribution** where you want viewers to have time-shifted playback. Distributions without DVR enabled will serve a live-only stream, even if the channel has a DVR window configured. + When DVR is enabled, viewers can scrub back through the live stream within the configured window. Once the window is exceeded, older content is no longer available for playback. +:::caution[Large DVR windows] +Very large DVR windows produce large HLS/DASH playlists, which can result in degraded performance on some players and devices. Chromecasts in particular are known to have issues with long DVR windows. + +If you need a long DVR window, test thoroughly on your target devices before going to production. +::: + ## API example You can also enable DVR via the API by setting `dvr.enabled` to `true` and `dvr.windowInSeconds` to your desired window size (60–86400 seconds) when [creating](../api/create-channel.api.mdx) or [updating](../api/update-channel.api.mdx) your channel. diff --git a/theolive/contribution/ingest-protocols.mdx b/theolive/contribution/ingest-protocols.mdx index cdd107611b39..679b16ac60ee 100644 --- a/theolive/contribution/ingest-protocols.mdx +++ b/theolive/contribution/ingest-protocols.mdx @@ -10,6 +10,8 @@ description: Supported ingest protocols (RTMP and SRT) and their trade-offs. The Dolby OptiView Live platform supports low latency live streaming through two ingest protocols: **RTMP** and **SRT**. +RTMP supports both **pull** and **push** mode out of the box. SRT works out of the box in **pull mode** only, where the platform connects to your encoder to pull the stream. If you need SRT push mode, contact your account representative to have it enabled. + ## RTMP — Real-Time Messaging Protocol RTMP is a widely adopted protocol originally developed by Adobe for transmitting audio, video, and data over the internet. It remains one of the most commonly supported protocols across hardware and software encoders. @@ -34,3 +36,9 @@ SRT is an open-source protocol designed for low latency, secure, and reliable vi - **Cons** - **Less universal encoder support** — While adoption is growing, not all encoders support SRT, especially older hardware models. - **More complex configuration** — SRT may require tuning parameters such as latency, overhead bandwidth, and encryption settings for optimal performance. + +### SRT passphrase + +In **pull mode**, you can secure the connection by including the passphrase directly in the SRT URL that you configure on your channel (e.g. `srt://encoder-host:port?passphrase=my-secret`). + +In **push mode**, the Dolby team configures the passphrase for you and provides an SRT URL with the passphrase included. diff --git a/theolive/getting-started.mdx b/theolive/getting-started.mdx index 607ce489c7d8..da0404acdd80 100644 --- a/theolive/getting-started.mdx +++ b/theolive/getting-started.mdx @@ -6,6 +6,8 @@ sidebar_custom_props: description: Create your first stream, from setup to embedding the player. --- +OptiView Live Streaming is the new name for THEOlive Streaming as part of the OptiView product suite. During the transition, you may still see references to THEOlive. OptiView Live and THEOlive refer to the same product. + # Getting started This guide walks you through creating your first stream, from setting up an account to embedding the player on your page. @@ -78,8 +80,8 @@ The engine is responsible for transcoding and packaging the incoming media. Crea - **Enable DRM** — secure your content with Digital Rights Management. - **Enable HESP** — output content in HESP format for low latency delivery. - **Enable HLS** — output content in HLS format for broad platform compatibility. -- **Enable HLS MPEG-TS** — output content in HLS MPEG-TS format. Only recommended for very specific platforms that don't support HLS with CMAF segments. -- **DAI Asset Key** — asset key for Dynamic Ad Insertion. +- **Enable HLS MPEG-TS** — output content in HLS MPEG-TS format. Only recommended for very specific platforms that don't support HLS with CMAF segments. This feature needs to be explicitly enabled in your account by an admin and isn't compatible with some other features like DRM. +- **DAI Asset Key** — asset key for OptiView ads. To learn more, contact your Dolby team. diff --git a/theolive/media-engine/drm.mdx b/theolive/media-engine/drm.mdx index 5fac47baed56..4e4a9c600068 100644 --- a/theolive/media-engine/drm.mdx +++ b/theolive/media-engine/drm.mdx @@ -10,6 +10,10 @@ description: Protect your live streams with Digital Rights Management. Digital Rights Management (DRM) protects live video content by encrypting the stream so that only authorized viewers can watch it. Without DRM, streams can be intercepted, redistributed, or recorded without permission. +:::info +DRM is a fully managed, premium feature. Contact your account representative to learn more and get started. +::: + DRM is essential for content that requires legal or contractual protection, such as premium sports broadcasts, pay-per-view events, or licensed entertainment. It ensures that content owners retain control over who can access their streams and under what conditions. ## Enabling DRM diff --git a/theolive/media-engine/encoding-quality.mdx b/theolive/media-engine/encoding-quality.mdx index f3671e7bbf19..ad9afa9e8550 100644 --- a/theolive/media-engine/encoding-quality.mdx +++ b/theolive/media-engine/encoding-quality.mdx @@ -22,10 +22,10 @@ If low latency is not a requirement for your use case, configure your engine to ## Choosing the right configuration -| Use case | Recommended outputs | Latency | Image quality | -| ---------------------------------------------- | ------------------- | ----------------- | ------------- | -| Live sports betting, interactive entertainment | HESP + HLS | Ultra-low (1–5s) | Good | -| Linear broadcast, non-interactive live events | HLS only | Standard (10–30s) | Higher | +| Use case | Recommended outputs | Latency | Image quality | +| ---------------------------------------------- | ------------------- | ---------------- | ------------- | +| Live sports betting, interactive entertainment | HESP + HLS | Ultra-low (1–5s) | Good | +| Linear broadcast, non-interactive live events | HLS only | Standard (8s+) | Higher | :::tip You can create multiple engines on the same channel with different output configurations. For example, use one engine with HESP for low-latency viewers and another with HLS only for viewers who prioritize quality over latency. Attach each to a separate distribution to serve both audiences from the same ingest. diff --git a/theolive/media-engine/image-overlays.mdx b/theolive/media-engine/image-overlays.mdx index aa7a25ba5711..6f55a6f898e2 100644 --- a/theolive/media-engine/image-overlays.mdx +++ b/theolive/media-engine/image-overlays.mdx @@ -10,6 +10,14 @@ description: Add static image overlays like logos or watermarks to your live str Image overlays let you burn static images — such as logos or watermarks — directly into your transcoded stream. Overlays are configured per engine and rendered on top of the video at the specified position and opacity. +## Dashboard + +You can configure overlays directly from the dashboard by editing an engine and opening the **Overlays** dialog. For each overlay, provide the image URL, choose a position preset (e.g. Top Left, Bottom Right), and optionally adjust the opacity and dimensions. + +![Edit Overlays dialog](../assets/img/image-overlays.png) + +Use **+ Add Overlay** to add multiple overlays to the same engine. Click **Save** to apply your changes. + ## Configuration Each engine supports one or more overlays. An overlay is defined by: diff --git a/theolive/media-engine/streaming-protocols.mdx b/theolive/media-engine/streaming-protocols.mdx index aa4bce0fde4c..5cd91e52aba6 100644 --- a/theolive/media-engine/streaming-protocols.mdx +++ b/theolive/media-engine/streaming-protocols.mdx @@ -22,7 +22,7 @@ HESP is a next-generation streaming protocol designed for ultra-low latency live HLS is the industry-standard protocol developed by Apple for delivering live and on-demand content over HTTP. The media engine outputs HLS using modern CMAF (fragmented MP4) segments. - **Broad compatibility** — supported by virtually all modern browsers, devices and players. -- **Higher latency** — compared to HESP, HLS typically introduces several seconds of additional latency. +- **Higher latency** — HLS typically delivers latency of 8 seconds or more, compared to 1–5 seconds with HESP. ### HLS (MPEG-TS) diff --git a/theolive/platform/real-time-update-with-webhooks.mdx b/theolive/platform/real-time-update-with-webhooks.mdx index 04ee3d26b311..379004641f92 100644 --- a/theolive/platform/real-time-update-with-webhooks.mdx +++ b/theolive/platform/real-time-update-with-webhooks.mdx @@ -10,6 +10,8 @@ description: Receive real-time notifications when channel events occur. Webhooks let you receive real-time notifications when events happen in your streaming infrastructure. Instead of polling for changes, you register an endpoint URL and the system pushes event data to it automatically. +For the full endpoint specification, see the [Webhooks API reference](../api/create-webhook.api.mdx). + ## What is a Webhook? A webhook is an HTTP callback that sends a `POST` request to your endpoint whenever a subscribed event occurs — for example, when a channel starts, an engine encounters an error, or a distribution is updated. @@ -102,7 +104,7 @@ You can also use the **wildcard** (`*`) to listen to all events at once. After creation, you are redirected to the webhook detail page where you can view the signing secret. -### API example — Create a webhook +### API example — [Create a webhook](../api/create-webhook.api.mdx) **Listen to specific events:** @@ -138,7 +140,7 @@ The **Webhooks** page shows a table of all your webhooks with: - **Events** — Shows "All events" for wildcard webhooks, or the count of subscribed events. - **Actions** — Edit or delete the webhook. -### API example — List all webhooks +### API example — [List all webhooks](../api/get-webhooks.api.mdx) `GET https://api.theo.live/v2/webhooks` @@ -149,7 +151,7 @@ Optional query parameters: | `cursor` | [Pagination](../api/pagination.mdx) cursor for the next page | | `limit` | Number of results per page | -### API example — Get a single webhook +### API example — [Get a single webhook](../api/get-webhook.api.mdx) `GET https://api.theo.live/v2/webhooks/{webhookId}` @@ -186,7 +188,7 @@ You can change: Click **Save** to apply your changes. -### API example — Update a webhook +### API example — [Update a webhook](../api/update-webhook.api.mdx) All fields are optional; only include the fields you want to change. @@ -209,7 +211,7 @@ When editing a webhook, you can toggle the **Active / Inactive** switch. This is useful when you want to temporarily stop receiving events without deleting the webhook. -### API example — Activate or deactivate a webhook +### API example — [Activate or deactivate a webhook](../api/update-webhook.api.mdx) Use the update endpoint with the `active` field: @@ -229,7 +231,7 @@ Click the **trash icon** on the webhooks list, or click **Delete** on the webhoo > **Note:** You must deactivate a webhook before deleting it. -### API example — Delete a webhook +### API example — [Delete a webhook](../api/delete-webhook.api.mdx) `DELETE https://api.theo.live/v2/webhooks/{webhookId}` @@ -240,22 +242,30 @@ When an event fires, Dolby sends a `POST` request to your endpoint with a JSON b ```json { "type": "channel.stopped", - "created": 1711900800, + "createdAt": 1711900800, "object": { "type": "channel", - "id": "ch_abc123" + "id": "ch_abc123", + "name": "name-of-channel", + "organizationId": "organization-id", + // optionally information about the parent object (id, type and name) }, - "data": { ... } + "data": { ... }, } ``` -| Field | Description | -| ------------- | ------------------------------------------------------------------------------ | -| `type` | The event type string (e.g. `channel.stopped`, `engine.error`) | -| `created` | Unix timestamp of when the event occurred | -| `object.type` | The resource type: `channel`, `engine`, `ingest`, `distribution`, or `webhook` | -| `object.id` | The ID of the resource that triggered the event | -| `data` | Event-specific payload with additional details | +| Field | Description | +| ----------------------- | ------------------------------------------------------------------------------ | +| `type` | The event type string (e.g. `channel.stopped`, `engine.error`) | +| `createdAt` | Unix timestamp of when the event occurred | +| `object.type` | The resource type: `channel`, `engine`, `ingest`, `distribution`, or `webhook` | +| `object.id` | The ID of the resource that triggered the event | +| `object.name` | The name of the resource that triggered the event | +| `object.organizationId` | The ID of the organization that owns the resource | +| `object.parent.id` | Optionally: the ID of the parent resource that triggered the event | +| `object.parent.name` | Optionally: the name of the parent resource that triggered the event | +| `object.parent.type` | Optionally: the type of the parent resource that triggered the event | +| `data` | Event-specific payload with additional details | ## Verifying Webhook Signatures @@ -331,7 +341,7 @@ app.listen(port, () => { }); ``` -### API example — Get the webhook secret +### API example — [Get the webhook secret](../api/get-webhook-secret.api.mdx) `GET https://api.theo.live/v2/webhooks/{webhookId}/secret` diff --git a/theolive/platform/viewer-tracking.mdx b/theolive/platform/viewer-tracking.mdx new file mode 100644 index 000000000000..ad58b66f8897 --- /dev/null +++ b/theolive/platform/viewer-tracking.mdx @@ -0,0 +1,59 @@ +--- +sidebar_position: 8 +sidebar_label: Viewer tracking +sidebar_custom_props: + icon: 📍 +description: Track viewers and syndication partners using tracking IDs and custom viewer data. +--- + +# Viewer tracking + +You can syndicate content through multiple distribution partners while utilizing the same channel, or alternatively track different groups through distributions. This can be helpful for aggregating viewing and billing data for a group of viewers. + +Additionally, when using the advanced reporting feature, you might want to assign tracking metadata to individual viewers to post-process later in your data and BI analysis tools. This is all possible with our viewer tracking workflows. + +## Tracking workflow + +The following is an example workflow for setting up stream tracking: + +1. Create a channel. +2. Enable [token security](../distribution/security/token-based-security.mdx) on the channel or alias. +3. When you create your self-signed token, add one or both of the following parameters to the `streaming` object in the payload of your JWT: + - **`tracking.trackingId`** — Groups viewers of the same channel, allows you to get the aggregated bandwidth usage of all viewers. This is useful for billing a single channel or alias if you use multiple distribution partners and want aggregated billing metrics back or viewer reports for groups of viewers. The maximum size is 128 characters. + - **`customViewerData`** — Access the bandwidth consumption of each viewer for analytics purposes and pass through metadata from your CMS for a viewer and be able to retrieve it in a viewer report. This data is not parsed by our system and can be a series of key-value pairs for you to extract later. The maximum size is 1024 characters. +4. Pass the JWT through to the player as described in the [token security](../distribution/security/token-based-security.mdx) section. +5. To get Advanced Reports, including user-reports, please contact your Dolby representative for access. + +:::note +Either or both `trackingId` and/or `customViewerData` may be used depending on what kind of tracking you need to achieve. +::: + +Here is an example `payload` for a JWT that includes sample `trackingId` and `customViewerData`: + +```json +{ + "streaming": { + "tracking": { + "trackingId": "groupAbc" + }, + "customViewerData": "user=user123;appVersion=x.y.x", + "tokenType": "Subscribe" + }, + "iat": 1750813271, + "exp": 1750813871 +} +``` + +Here is the JWT that is generated from this payload. You can paste it into [jwt.io](https://jwt.io/) to inspect it: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdHJlYW1pbmciOnsidHJhY2tpbmciOnsidHJhY2tpbmdJZCI6Imdyb3VwQWJjIn0sImN1c3RvbVZpZXdlckRhdGEiOiJ1c2VyPXVzZXIxMjM7YXBwVmVyc2lvbj14LnkueCIsInRva2VuVHlwZSI6IlN1YnNjcmliZSJ9LCJpYXQiOjE3NTA4MTMyNzEsImV4cCI6MTc1MDgxMzg3MX0.OvIZAvs6XL4KQ8YTtIdjdJNZ5oS6Jkosj3mq274gdrw +``` + +To validate the token, use the following secret: `d2e166fdda89824a6c493d8a2af7a0754199eff9e38c579cba8783767a44039c`. + +### Notes + +- Tracking can be used independently of secure channels. You can pass the same payload parameters in a JWT, however, the token must be "valid" — the required date fields must be valid or the token will be ignored. If a channel does not have token security enabled, be aware that anyone can view the channel with or without a JWT. Without secure channels, the signature is not validated, so there is a risk that a user could change the values. +- If you are combining the OptiView real-time streaming solution and the live streaming solution, you may use the secret from the real-time subscriber token as the secret you provide to OptiView live streaming and use the same JWTs across both streaming products. +- `tokenType` should be set to `Subscribe`.