Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/PLUGIN_AUTHOR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,8 @@ Each method requires the matching permission in your manifest:
| `owncast.server.emotes()`, custom chat emotes `[{name, url}]` | `server.read` |
| `owncast.server.federation()`, `{enabled, username, isPrivate}` | `server.read` |
| `owncast.server.tags()`, `[string]` | `server.read` |
| `owncast.videoConfig.read()`, `{latencyLevel, codec, variants}` | `videoconfig.read` |
| `owncast.videoConfig.write({latencyLevel?, codec?, variants?})`, partial update, throws on failure | `videoconfig.write` |
| `owncast.videoConfig.read()`, `{latencyLevel, codec, autoplay, variants}` | `videoconfig.read` |
| `owncast.videoConfig.write({latencyLevel?, codec?, autoplay?, variants?})`, partial update, throws on failure | `videoconfig.write` |
| `owncast.notifications.discord(text)` | `notifications.send` |
| `owncast.notifications.browserPush({title, body, url?})` | `notifications.send` |
| `owncast.notifications.fediverse({type, body, image?, link?})` | `notifications.send` |
Expand Down
26 changes: 22 additions & 4 deletions docs/WIRE_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ plugin should store values above `Number.MAX_SAFE_INTEGER` (2^53 - 1) as TEXT.
### `videoconfig.read`

- `owncast_video_config_read(): PTR`. Input: none. Output: JSON `VideoConfig`
with `latencyLevel`, `codec`, and `variants`.
with `latencyLevel`, `codec`, `autoplay`, and `variants`.

### `videoconfig.write`

Expand Down Expand Up @@ -869,28 +869,46 @@ type FederationInfo = {
isPrivate?: boolean;
};

type AutoplayMode = "off" | "always" | "sound-only";

type VideoCodec =
| "libx264"
| "h264_omx"
| "h264_vaapi"
| "h264_qsv"
| "h264_nvenc"
| "h264_v4l2m2m"
| "h264_videotoolbox";
Comment thread
gabek marked this conversation as resolved.

type StreamVariant = {
width: number;
height: number;
framerate: number;
videoBitrate: number;
audioBitrate: number;
cpuUsageLevel: number;
isPassthrough: boolean;
};

type VideoConfig = {
latencyLevel: number;
codec: string;
codec: VideoCodec;
autoplay: AutoplayMode;
variants: StreamVariant[];
};

type VideoConfigUpdate = {
latencyLevel?: number;
codec?: string;
codec?: VideoCodec;
autoplay?: AutoplayMode;
variants?: StreamVariant[];
};
```

`cpuUsageLevel` accepts `0` through `4`, from lowest to highest CPU usage.
Audio settings are not exposed. A variant update preserves the host's existing
audio configuration for that output.
Hardware codecs require the matching encoder in the host's ffmpeg build.

### Fediverse and notifications

```ts
Expand Down
2 changes: 1 addition & 1 deletion examples/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ One self-contained npm project per directory. Each has its own `README.md` with
| [overlay](./overlay/) | `http.serve`, static files from `public/` + dynamic JSON endpoint. |
| [stream-tracker](./stream-tracker/) | Every typed lifecycle / chat-user handler + read APIs. |
| [stream-ops](./stream-ops/) | Broadcast telemetry (`server.read`) + video config read/write (`videoconfig.read`/`videoconfig.write`). |
| [manual-video-settings](./manual-video-settings/) | Admin form for the video config: latency, codec, and per-variant resolution / framerate / bitrate. |
| [manual-video-settings](./manual-video-settings/) | Admin form for video latency, codec, autoplay, and output variants. |
| [engagement-bot](./engagement-bot/) | Discord + browser-push + fediverse notifier on stream / fediverse events, with a small inline spam filter. |
| [admin-demo](./admin-demo/) | `manifest.admin.pages`, host-gated admin routes. |
| [file-manager](./file-manager/) | `storage.fs`, admin page to browse/upload/download/delete files in the plugin's private sandbox. |
Expand Down
7 changes: 4 additions & 3 deletions examples/js/manual-video-settings/INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ A hand-edit form for the Owncast transcoding pipeline. Use it when you want to t
Opening **Manual Video Settings** under the admin sidebar shows a single form with:

- **Latency level**: `0` (lowest) through `4` (highest). Lower means viewers see the stream sooner but rebuffer more on flaky networks.
- **Codec**: the FFmpeg encoder Owncast invokes (`libx264` software, plus the usual hardware variants: `h264_vaapi`, `h264_nvenc`, `h264_qsv`, `h264_omx`, `h264_v4l2m2m`, `h264_videotoolbox`). If your live config uses something not in the dropdown, this plugin shows it as a `(current)` option so saving doesn't silently overwrite it.
- **Output variants**: one row per HLS rendition, with editable **width**, **height**, **FPS**, **video kbps**, and a **passthrough** checkbox. Add or remove rows from the form. Each variant's audio bitrate is preserved across saves (it's part of the host's data model, just not surfaced here).
- **Codec**: the FFmpeg encoder Owncast invokes (`libx264` software, plus `h264_vaapi`, `h264_nvenc`, `h264_qsv`, `h264_omx`, `h264_v4l2m2m`, and `h264_videotoolbox`). Hardware encoders must be available in the host's ffmpeg build.
- **Autoplay**: `off`, `always`, or `sound-only`.
- **Output variants**: one row per HLS rendition, with editable **width**, **height**, **FPS**, **video kbps**, **CPU usage**, and a **passthrough** checkbox. Add or remove rows from the form.

## How to use it

Expand All @@ -17,7 +18,7 @@ Opening **Manual Video Settings** under the admin sidebar shows a single form wi
3. Edit fields, then click **Save**. The status line confirms `Saved.` or shows the host's error message if a setting was rejected.
4. **Reload** discards in-form edits and re-fetches the live config.

Changes apply on the next stream segment Owncast encodes. An active broadcast does not need to be restarted, but viewers may see a brief quality switch.
Changes apply when the next stream starts. The host does not restart an active broadcast.

## Safety notes

Expand Down
2 changes: 1 addition & 1 deletion examples/js/manual-video-settings/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# manual-video-settings

Admin-only form at `/plugins/manual-video-settings/admin/` for hand-editing the host's transcoding config: HLS latency level, output codec, and each output variant's resolution / framerate / video bitrate (plus the passthrough flag). The form reads the current state via `owncast.videoConfig.read()` and POSTs partial updates through `owncast.videoConfig.write()`.
Admin-only form at `/plugins/manual-video-settings/admin/` for hand-editing the host's transcoding config: HLS latency level, output codec, autoplay, and each output variant's resolution, framerate, video bitrate, CPU usage level, and passthrough flag. The form reads the current state via `owncast.videoConfig.read()` and POSTs partial updates through `owncast.videoConfig.write()`.

**Demonstrates:** combining `manifest.admin.pages` (host-gated routes, no auth code in the plugin) with the `videoconfig.read` / `videoconfig.write` permission split. The HTTP handler accepts a partial `VideoConfigUpdate` so fields the form didn't touch are left untouched by the host.

Expand Down
14 changes: 8 additions & 6 deletions examples/js/manual-video-settings/__tests__/admin.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
"videoConfig": {
"latencyLevel": 2,
"codec": "libx264",
"autoplay": "sound-only",
"variants": [
{ "width": 1920, "height": 1080, "framerate": 30, "videoBitrate": 6000, "audioBitrate": 160, "isPassthrough": false },
{ "width": 1280, "height": 720, "framerate": 30, "videoBitrate": 3000, "audioBitrate": 128, "isPassthrough": false }
{ "width": 1920, "height": 1080, "framerate": 30, "videoBitrate": 6000, "cpuUsageLevel": 3, "isPassthrough": false },
{ "width": 1280, "height": 720, "framerate": 30, "videoBitrate": 3000, "cpuUsageLevel": 2, "isPassthrough": false }
]
}
},
Expand All @@ -45,7 +46,7 @@
"expect": {
"status": 200,
"headers": { "content-type": "application/json" },
"body": "{\"latencyLevel\":2,\"codec\":\"libx264\",\"variants\":[{\"width\":1920,\"height\":1080,\"framerate\":30,\"videoBitrate\":6000,\"audioBitrate\":160,\"isPassthrough\":false},{\"width\":1280,\"height\":720,\"framerate\":30,\"videoBitrate\":3000,\"audioBitrate\":128,\"isPassthrough\":false}]}"
"body": "{\"latencyLevel\":2,\"codec\":\"libx264\",\"autoplay\":\"sound-only\",\"variants\":[{\"width\":1920,\"height\":1080,\"framerate\":30,\"videoBitrate\":6000,\"cpuUsageLevel\":3,\"isPassthrough\":false},{\"width\":1280,\"height\":720,\"framerate\":30,\"videoBitrate\":3000,\"cpuUsageLevel\":2,\"isPassthrough\":false}]}"
}
}
}
Expand Down Expand Up @@ -90,7 +91,7 @@
"method": "POST",
"path": "/admin/api/config",
"headers": { "content-type": "application/json" },
"body": "{\"latencyLevel\":2,\"codec\":\"libx264\",\"variants\":[{\"width\":1920,\"height\":1080,\"framerate\":30,\"videoBitrate\":6000,\"audioBitrate\":160,\"isPassthrough\":false},{\"width\":1280,\"height\":720,\"framerate\":30,\"videoBitrate\":3000,\"audioBitrate\":128,\"isPassthrough\":false}]}",
"body": "{\"latencyLevel\":2,\"codec\":\"libx264\",\"autoplay\":\"sound-only\",\"variants\":[{\"width\":1920,\"height\":1080,\"framerate\":30,\"videoBitrate\":6000,\"cpuUsageLevel\":3,\"isPassthrough\":false},{\"width\":1280,\"height\":720,\"framerate\":30,\"videoBitrate\":3000,\"cpuUsageLevel\":2,\"isPassthrough\":false}]}",
"authenticated": true,
"expect": { "status": 204 }
}
Expand All @@ -101,9 +102,10 @@
{
"latencyLevel": 2,
"codec": "libx264",
"autoplay": "sound-only",
"variants": [
{ "width": 1920, "height": 1080, "framerate": 30, "videoBitrate": 6000, "audioBitrate": 160, "isPassthrough": false },
{ "width": 1280, "height": 720, "framerate": 30, "videoBitrate": 3000, "audioBitrate": 128, "isPassthrough": false }
{ "width": 1920, "height": 1080, "framerate": 30, "videoBitrate": 6000, "cpuUsageLevel": 3, "isPassthrough": false },
{ "width": 1280, "height": 720, "framerate": 30, "videoBitrate": 3000, "cpuUsageLevel": 2, "isPassthrough": false }
]
}
]
Expand Down
22 changes: 15 additions & 7 deletions examples/js/manual-video-settings/public/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
<h1>Manual Video Settings</h1>
<p class="lead">
Edit the host's live transcoding config: HLS latency level, output codec,
and each output variant's resolution / framerate / video bitrate. Changes
are applied via <code>owncast.videoConfig.write</code>.
autoplay, and each output variant's resolution / framerate / video bitrate
/ CPU usage. Changes are applied via <code>owncast.videoConfig.write</code>.
</p>

<form id="form">
Expand Down Expand Up @@ -89,6 +89,14 @@ <h1>Manual Video Settings</h1>
<option value="h264_videotoolbox">h264_videotoolbox (macOS)</option>
</select>
</label>
<label>
<span>Autoplay</span>
<select name="autoplay">
<option value="off">Never</option>
<option value="always">Always</option>
<option value="sound-only">Only if sound is available</option>
</select>
</label>
</fieldset>

<fieldset>
Expand All @@ -100,6 +108,7 @@ <h1>Manual Video Settings</h1>
<th>Height</th>
<th>FPS</th>
<th>Video kbps</th>
<th>CPU usage</th>
<th>Passthrough</th>
<th class="row-actions"></th>
</tr>
Expand All @@ -125,15 +134,12 @@ <h1>Manual Video Settings</h1>

function variantRow(v) {
const tr = document.createElement("tr");
// Audio bitrate is part of the host's variant shape but isn't
// exposed in this form. Stash it on the row so save() echoes it
// back instead of clobbering it with 0.
tr.dataset.audioBitrate = String(v.audioBitrate ?? 0);
tr.innerHTML = `
<td><input type="number" name="width" min="0" value="${v.width ?? 0}" /></td>
<td><input type="number" name="height" min="0" value="${v.height ?? 0}" /></td>
<td><input type="number" name="framerate" min="0" value="${v.framerate ?? 0}" /></td>
<td><input type="number" name="videoBitrate" min="0" value="${v.videoBitrate ?? 0}" /></td>
<td><input type="number" name="cpuUsageLevel" min="0" max="4" value="${v.cpuUsageLevel ?? 0}" /></td>
<td><input type="checkbox" name="isPassthrough" ${v.isPassthrough ? "checked" : ""} /></td>
<td class="row-actions">
<button type="button" class="danger" data-action="remove">Remove</button>
Expand All @@ -149,12 +155,13 @@ <h1>Manual Video Settings</h1>
height: Number(row.querySelector("[name=height]").value) || 0,
framerate: Number(row.querySelector("[name=framerate]").value) || 0,
videoBitrate: Number(row.querySelector("[name=videoBitrate]").value) || 0,
audioBitrate: Number(row.dataset.audioBitrate) || 0,
cpuUsageLevel: Number(row.querySelector("[name=cpuUsageLevel]").value) || 0,
isPassthrough: row.querySelector("[name=isPassthrough]").checked,
}));
return {
latencyLevel: Number(data.get("latencyLevel")),
codec: data.get("codec"),
autoplay: data.get("autoplay"),
variants,
};
}
Expand All @@ -164,6 +171,7 @@ <h1>Manual Video Settings</h1>
if ([...latency.options].some((o) => o.value === String(config.latencyLevel))) {
latency.value = String(config.latencyLevel);
}
form.querySelector("[name=autoplay]").value = config.autoplay || "off";
const codec = form.querySelector("[name=codec]");
if (![...codec.options].some((o) => o.value === config.codec) && config.codec) {
// Show a custom codec if the host reports something the dropdown
Expand Down
5 changes: 4 additions & 1 deletion examples/js/manual-video-settings/src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function parseVariant(v) {
height: Number(v.height) || 0,
framerate: Number(v.framerate) || 0,
videoBitrate: Number(v.videoBitrate) || 0,
audioBitrate: Number(v.audioBitrate) || 0,
cpuUsageLevel: Number(v.cpuUsageLevel) || 0,
isPassthrough: Boolean(v.isPassthrough),
};
}
Expand Down Expand Up @@ -46,6 +46,9 @@ module.exports = definePlugin({
if (typeof parsed.codec === "string" && parsed.codec.length > 0) {
update.codec = parsed.codec;
}
if (typeof parsed.autoplay === "string" && parsed.autoplay.length > 0) {
update.autoplay = parsed.autoplay;
}
if (Array.isArray(parsed.variants)) {
update.variants = parsed.variants.map(parseVariant);
}
Expand Down
9 changes: 5 additions & 4 deletions examples/js/stream-ops/__tests__/reads.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,23 @@
"given": {
"videoConfig": {
"latencyLevel": 2,
"codec": "h264",
"codec": "libx264",
"autoplay": "off",
"variants": [
{
"width": 1920,
"height": 1080,
"framerate": 30,
"videoBitrate": 6000,
"audioBitrate": 160,
"cpuUsageLevel": 3,
"isPassthrough": false
},
{
"width": 1280,
"height": 720,
"framerate": 30,
"videoBitrate": 3000,
"audioBitrate": 128,
"cpuUsageLevel": 2,
"isPassthrough": false
}
]
Expand All @@ -77,7 +78,7 @@
],
"expect": {
"chatSends": [
"latency 2, codec h264, 2 variant(s)"
"latency 2, codec libx264, 2 variant(s)"
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ One self-contained plugin per directory, authored in Python and compiled to wasm
| [overlay](./overlay/) | `http.serve`, static files from `public/` + dynamic JSON endpoint. |
| [stream-tracker](./stream-tracker/) | Every typed lifecycle / chat-user handler + read APIs. |
| [stream-ops](./stream-ops/) | Broadcast telemetry (`server.read`) + video config read/write (`videoconfig.read`/`videoconfig.write`). |
| [manual-video-settings](./manual-video-settings/) | Admin form for the video config: latency, codec, and per-variant resolution / framerate / bitrate. |
| [manual-video-settings](./manual-video-settings/) | Admin form for video latency, codec, autoplay, and output variants. |
| [engagement-bot](./engagement-bot/) | Discord + browser-push + fediverse notifier on stream / fediverse events, with a small inline spam filter. |
| [admin-demo](./admin-demo/) | `manifest.admin.pages`, host-gated admin routes. |
| [file-manager](./file-manager/) | `storage.fs`, admin page to browse/upload/download/delete files in the plugin's private sandbox. |
Expand Down
7 changes: 4 additions & 3 deletions examples/python/manual-video-settings/INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ A hand-edit form for the Owncast transcoding pipeline. Use it when you want to t
Opening **Manual Video Settings** under the admin sidebar shows a single form with:

- **Latency level**: `0` (lowest) through `4` (highest). Lower means viewers see the stream sooner but rebuffer more on flaky networks.
- **Codec**: the FFmpeg encoder Owncast invokes (`libx264` software, plus the usual hardware variants: `h264_vaapi`, `h264_nvenc`, `h264_qsv`, `h264_omx`, `h264_v4l2m2m`, `h264_videotoolbox`). If your live config uses something not in the dropdown, this plugin shows it as a `(current)` option so saving doesn't silently overwrite it.
- **Output variants**: one row per HLS rendition, with editable **width**, **height**, **FPS**, **video kbps**, and a **passthrough** checkbox. Add or remove rows from the form. Each variant's audio bitrate is preserved across saves (it's part of the host's data model, just not surfaced here).
- **Codec**: the FFmpeg encoder Owncast invokes (`libx264` software, plus `h264_vaapi`, `h264_nvenc`, `h264_qsv`, `h264_omx`, `h264_v4l2m2m`, and `h264_videotoolbox`). Hardware encoders must be available in the host's ffmpeg build.
- **Autoplay**: `off`, `always`, or `sound-only`.
- **Output variants**: one row per HLS rendition, with editable **width**, **height**, **FPS**, **video kbps**, **CPU usage**, and a **passthrough** checkbox. Add or remove rows from the form.

## How to use it

Expand All @@ -17,7 +18,7 @@ Opening **Manual Video Settings** under the admin sidebar shows a single form wi
3. Edit fields, then click **Save**. The status line confirms `Saved.` or shows the host's error message if a setting was rejected.
4. **Reload** discards in-form edits and re-fetches the live config.

Changes apply on the next stream segment Owncast encodes. An active broadcast does not need to be restarted, but viewers may see a brief quality switch.
Changes apply when the next stream starts. The host does not restart an active broadcast.

## Safety notes

Expand Down
2 changes: 1 addition & 1 deletion examples/python/manual-video-settings/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# manual-video-settings

Admin-only form at `/plugins/manual-video-settings/admin/` for hand-editing the host's transcoding config: HLS latency level, output codec, and each output variant's resolution / framerate / video bitrate (plus the passthrough flag). The form reads the current state via `owncast.video_config.read()` and POSTs partial updates through `owncast.video_config.write()`.
Admin-only form at `/plugins/manual-video-settings/admin/` for hand-editing the host's transcoding config: HLS latency level, output codec, autoplay, and each output variant's resolution, framerate, video bitrate, CPU usage level, and passthrough flag. The form reads the current state via `owncast.video_config.read()` and POSTs partial updates through `owncast.video_config.write()`.

**Demonstrates:** combining `manifest.admin.pages` (host-gated routes, no auth code in the plugin) with the `videoconfig.read` / `videoconfig.write` permission split. The handler accepts a partial `VideoConfigUpdate` so fields the form didn't touch are left untouched by the host.

Expand Down
Loading
Loading