Adiciona suporte a encoders de hardware (NVENC, VAAPI, QSV) no ffmpeg
---
- Implementada função buildEncoderFlags() em supervisor.ts e reprovision.mjs que gera o bloco de flags do ffmpeg conforme a env var FFMPEG_HWACCEL (nvenc, vaapi, qsv ou vazio para libx264);
- Template stream.template.conf refatorado para usar {{ENCODER_FLAGS}} no lugar do bloco x264 fixo;
- NVENC configurado com perfil high, mapeamento de presets x264→p1-p7 e tune zerolatency→ll;
- docker-compose.yml atualizado com seções comentadas para gpus, devices, FFMPEG_HWACCEL e instrução de volume WSL2 para libnvidia-encode;
- Dockerfile adiciona mesa-va-drivers e intel-media-va-driver para suporte a VAAPI e remove declaração VOLUME redundante;
- fetchAllStatuses() corrigido: supervisorctl status || true evita exceção com exit code 3 quando há processos parados;
- reprovision.mjs atualizado para incluir AUTO_RELOAD e AUTO_RELOAD_INTERVAL no contexto de renderização do template;
---
This commit is contained in:
+3
-11
@@ -1,13 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- **Chromium auto-reload** — per-stream toggle to reload the browser page on a configurable interval via Chrome DevTools Protocol. Implemented as a dedicated Supervisord process (`autoreload-{id}`) using a raw WebSocket CDP connection (no external dependencies). Responds cleanly to `supervisorctl stop` via SIGTERM trap. Interval is set in minutes from the card's 3-dot menu and persisted server-side. Toggle applies via `POST /api/streams/{id}/autoreload` without restarting the stream.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decap Stream v1.0.0
|
## Decap Stream v1.0.0
|
||||||
|
|
||||||
Turn any web page into an RTMP/HLS stream. Chromium renders the page in a virtual display, ffmpeg captures it, and MediaMTX publishes it, all managed through a web UI.
|
Turn any web page into an RTMP/HLS stream. Chromium renders the page in a virtual display, ffmpeg captures it, and MediaMTX publishes it, all managed through a web UI.
|
||||||
@@ -19,9 +11,9 @@ Turn any web page into an RTMP/HLS stream. Chromium renders the page in a virtua
|
|||||||
- **Scalable card sizes** — mini / sm / md / lg with proportional scaling across all elements
|
- **Scalable card sizes** — mini / sm / md / lg with proportional scaling across all elements
|
||||||
- **Inline VNC** — inspect any stream's virtual display without leaving the UI
|
- **Inline VNC** — inspect any stream's virtual display without leaving the UI
|
||||||
- **Autologin with CDP detection** — skips login if the session is still alive on container restart
|
- **Autologin with CDP detection** — skips login if the session is still alive on container restart
|
||||||
- **Built-in HLS player** — with controls; static standalone page optimized for TV browsers (`/player.html?id=<id>`)
|
- **Built-in HLS player** — with controls; static standalone page optimized for TV browsers (`/player/<id>.html`)
|
||||||
- **Per-card Pure mode** — open streams as a raw `.m3u8` link or a zero-dependency `.html` page, usable in VLC or any HLS-capable player
|
- **Pure mode** — global toggle in Settings to open streams as a raw `.m3u8` link or a zero-dependency `.html` page, usable in VLC or any HLS-capable player
|
||||||
- **Per-card new-tab toggle** — open any action button in a new tab; settings are per-card and saved in the browser
|
- **Open in new tab** — global toggle in Settings to open any action button in a new tab; saved in the browser
|
||||||
- **Optional UI authentication** — set `AUTH_USER` + `AUTH_PASS` to password-protect the entire UI
|
- **Optional UI authentication** — set `AUTH_USER` + `AUTH_PASS` to password-protect the entire UI
|
||||||
- **Persistent desired state** — streams restore automatically on container restart
|
- **Persistent desired state** — streams restore automatically on container restart
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ All processes are managed by Supervisord. The web UI is a Next.js app that contr
|
|||||||
- **Fully configurable encoding** — resolution, scale, FPS, bitrate, preset, tune, GOP, threads, all per stream
|
- **Fully configurable encoding** — resolution, scale, FPS, bitrate, preset, tune, GOP, threads, all per stream
|
||||||
- **GPU acceleration** — optional per-stream Chromium GPU flag (disabled by default for container compatibility)
|
- **GPU acceleration** — optional per-stream Chromium GPU flag (disabled by default for container compatibility)
|
||||||
- **Built-in HLS player** — watch any stream in the browser via a standalone HTML page optimized for TVs (Back + Mute buttons, reconnect on stall, direct MediaMTX connection when available)
|
- **Built-in HLS player** — watch any stream in the browser via a standalone HTML page optimized for TVs (Back + Mute buttons, reconnect on stall, direct MediaMTX connection when available)
|
||||||
- **Per-card Pure mode** — toggle in the card menu to open Play Stream as a raw `.m3u8` link or Run HTML as a minimal `.html` page with no UI; works with native players and TV browsers
|
- **Pure mode** — global toggle in Settings to open Play Stream as a raw `.m3u8` link or Run HTML as a minimal `.html` page with no UI; works with native players and TV browsers
|
||||||
- **Per-card new tab** — toggle to open any button in a new tab instead of navigating in place; both settings are per-card and saved in the browser
|
- **Open in new tab** — global toggle in Settings to open any button in a new tab instead of navigating in place; saved in the browser
|
||||||
- **Chromium auto-reload** — per-card toggle to reload the browser page on a configurable interval; uses Chrome DevTools Protocol (no xdotool focus tricks); configured from the card menu and persisted on the server
|
- **Chromium auto-reload** — per-stream toggle to reload the Chromium page on a configurable interval via Chrome DevTools Protocol; configured from the card menu and persisted on the server
|
||||||
|
- **Player client-side auto-reload** — global toggle in Settings to reload the HLS player itself on a configurable interval (in minutes)
|
||||||
|
|
||||||
## Platform Support
|
## Platform Support
|
||||||
|
|
||||||
@@ -61,20 +62,26 @@ services:
|
|||||||
image: ghcr.io/riguettodev/decap-stream:latest
|
image: ghcr.io/riguettodev/decap-stream:latest
|
||||||
container_name: decap-stream
|
container_name: decap-stream
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
shm_size: "2gb"
|
shm_size: "1gb"
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined # required for Chromium syscalls
|
- seccomp:unconfined
|
||||||
|
# gpus: all # Uncomment for NVIDIA (nvenc) — requires nvidia-container-toolkit on host
|
||||||
|
# devices:
|
||||||
|
# - /dev/dri:/dev/dri # Uncomment for Intel/AMD (vaapi or qsv)
|
||||||
environment:
|
environment:
|
||||||
# TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
# AUTH_USER: admin # optional: enables login if both are set
|
# FFMPEG_HWACCEL: nvenc # GPU encoding: nvenc (NVIDIA), vaapi (Intel/AMD), qsv (Intel QSV) / Requires: nvenc → gpus: all | vaapi/qsv → devices: /dev/dri
|
||||||
|
# LD_LIBRARY_PATH: /usr/lib/wsl/lib # WSL2 + nvenc only: injects NVENC libs not auto-mounted by Docker
|
||||||
|
# AUTH_USER: admin # Se definido (junto com AUTH_PASS), habilita login
|
||||||
# AUTH_PASS: secure_password
|
# AUTH_PASS: secure_password
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000" # Web UI — main entry point
|
- "3000:3000" # Web UI — main entry point
|
||||||
- "127.0.0.1:6080:6080" # VNC — localhost only; remote access via tunnel/VPN
|
- "127.0.0.1:6080:6080" # VNC — localhost only; remote access via tunnel/VPN
|
||||||
# - "1935:1935" # RTMP — expose only for external ingest (e.g. OBS)
|
# - "1935:1935" # RTMP — internal only; expose only for external ingest (e.g. OBS)
|
||||||
# - "8888:8888" # HLS — internal only; proxied through the UI at /api/hls/
|
# - "8888:8888" # HLS — internal only; proxied through Next.js at /api/hls/
|
||||||
volumes:
|
volumes:
|
||||||
- streams:/app/data/streams # Persistent: streams.json, chrome profiles, thumbs
|
- streams:/app/data/streams # Persistent: streams.json, chrome profiles, thumbs
|
||||||
|
# - /usr/lib/wsl/lib:/usr/lib/wsl/lib:ro # WSL2 + nvenc: exposes libnvidia-encode.so.1
|
||||||
# - logs:/app/data/logs # Optional
|
# - logs:/app/data/logs # Optional
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
@@ -109,7 +116,7 @@ Each stream gets a slug ID you define (e.g. `grafana-prod`):
|
|||||||
| RTMP ingest | `rtmp://<host>:1935/live/<id>` |
|
| RTMP ingest | `rtmp://<host>:1935/live/<id>` |
|
||||||
| HLS manifest (proxied) | `http://<host>:3000/api/hls/live/<id>/index.m3u8` |
|
| HLS manifest (proxied) | `http://<host>:3000/api/hls/live/<id>/index.m3u8` |
|
||||||
| HLS manifest (direct) | `http://<host>:8888/live/<id>/index.m3u8` — requires port 8888 exposed |
|
| HLS manifest (direct) | `http://<host>:8888/live/<id>/index.m3u8` — requires port 8888 exposed |
|
||||||
| HTML player | `http://<host>:3000/player.html?id=<id>` — static minimal page, no UI chrome |
|
| HTML player | `http://<host>:3000/player/<id>.html` — static minimal page, no UI chrome |
|
||||||
| VNC (inline) | `http://<host>:3000/vnc/<id>` |
|
| VNC (inline) | `http://<host>:3000/vnc/<id>` |
|
||||||
|
|
||||||
> **Pure mode** (toggle per card): Play Stream opens the proxied HLS `.m3u8` directly; Run HTML opens the `.html` player. Both can be pasted into VLC or any HLS-capable player, or loaded natively on TV browsers that support HLS.
|
> **Pure mode** (toggle per card): Play Stream opens the proxied HLS `.m3u8` directly; Run HTML opens the `.html` player. Both can be pasted into VLC or any HLS-capable player, or loaded natively on TV browsers that support HLS.
|
||||||
|
|||||||
+1
-1
@@ -27,6 +27,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
xvfb x11vnc novnc websockify \
|
xvfb x11vnc novnc websockify \
|
||||||
ffmpeg supervisor xdotool tzdata \
|
ffmpeg supervisor xdotool tzdata \
|
||||||
|
mesa-va-drivers intel-media-va-driver \
|
||||||
chromium \
|
chromium \
|
||||||
curl gnupg \
|
curl gnupg \
|
||||||
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
|
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
|
||||||
@@ -70,7 +71,6 @@ COPY docker/server.mjs /opt/server.mjs
|
|||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /opt/scripts/*.sh /entrypoint.sh
|
RUN chmod +x /opt/scripts/*.sh /entrypoint.sh
|
||||||
|
|
||||||
VOLUME ["/app/data"]
|
|
||||||
EXPOSE 3000 1935 8888 6080
|
EXPOSE 3000 1935 8888 6080
|
||||||
|
|
||||||
CMD ["/entrypoint.sh"]
|
CMD ["/entrypoint.sh"]
|
||||||
|
|||||||
@@ -6,8 +6,13 @@ services:
|
|||||||
shm_size: "1gb"
|
shm_size: "1gb"
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
|
# gpus: all # Uncomment for NVIDIA (nvenc) — requires nvidia-container-toolkit on host
|
||||||
|
# devices:
|
||||||
|
# - /dev/dri:/dev/dri # Uncomment for Intel/AMD (vaapi or qsv)
|
||||||
environment:
|
environment:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
|
# FFMPEG_HWACCEL: nvenc # GPU encoding: nvenc (NVIDIA), vaapi (Intel/AMD), qsv (Intel QSV) / Requires: nvenc → gpus: all | vaapi/qsv → devices: /dev/dri
|
||||||
|
# LD_LIBRARY_PATH: /usr/lib/wsl/lib # WSL2 + nvenc only: injects NVENC libs not auto-mounted by Docker
|
||||||
# AUTH_USER: admin # Se definido (junto com AUTH_PASS), habilita login
|
# AUTH_USER: admin # Se definido (junto com AUTH_PASS), habilita login
|
||||||
# AUTH_PASS: secure_password
|
# AUTH_PASS: secure_password
|
||||||
ports:
|
ports:
|
||||||
@@ -17,6 +22,7 @@ services:
|
|||||||
# - "8888:8888" # HLS — internal only; proxied through Next.js at /api/hls/
|
# - "8888:8888" # HLS — internal only; proxied through Next.js at /api/hls/
|
||||||
volumes:
|
volumes:
|
||||||
- streams:/app/data/streams # Persistent: streams.json, chrome profiles, thumbs
|
- streams:/app/data/streams # Persistent: streams.json, chrome profiles, thumbs
|
||||||
|
# - /usr/lib/wsl/lib:/usr/lib/wsl/lib:ro # WSL2 + nvenc: exposes libnvidia-encode.so.1
|
||||||
# - logs:/app/data/logs # Optional
|
# - logs:/app/data/logs # Optional
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -23,6 +23,70 @@ function render(tpl, vars) {
|
|||||||
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => String(vars[k] ?? ''))
|
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => String(vars[k] ?? ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NVENC_PRESET = {
|
||||||
|
ultrafast: 'p1', superfast: 'p1', veryfast: 'p2',
|
||||||
|
faster: 'p3', fast: 'p3', medium: 'p4',
|
||||||
|
slow: 'p5', slower: 'p6', veryslow: 'p7',
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEncoderFlags(stream) {
|
||||||
|
const { preset, tune, gop, bitrate, bufsize } = stream
|
||||||
|
const hwaccel = (process.env.FFMPEG_HWACCEL ?? '').toLowerCase().trim()
|
||||||
|
const lines = []
|
||||||
|
const ln = (s) => lines.push(` ${s} \\`)
|
||||||
|
|
||||||
|
if (hwaccel === 'nvenc') {
|
||||||
|
ln(`-c:v h264_nvenc`)
|
||||||
|
ln(`-preset ${NVENC_PRESET[preset] ?? 'p4'}`)
|
||||||
|
ln(`-tune ${tune === 'zerolatency' ? 'll' : 'hq'}`)
|
||||||
|
ln(`-profile:v high`)
|
||||||
|
ln(`-pix_fmt yuv420p`)
|
||||||
|
ln(`-rc cbr`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else if (hwaccel === 'vaapi') {
|
||||||
|
ln(`-vaapi_device /dev/dri/renderD128`)
|
||||||
|
ln(`-vf 'format=nv12,hwupload'`)
|
||||||
|
ln(`-c:v h264_vaapi`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else if (hwaccel === 'qsv') {
|
||||||
|
ln(`-c:v h264_qsv`)
|
||||||
|
ln(`-preset veryfast`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-pix_fmt nv12`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else {
|
||||||
|
ln(`-c:v libx264`)
|
||||||
|
ln(`-preset ${preset}`)
|
||||||
|
ln(`-tune ${tune}`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-pix_fmt yuv420p`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-sc_threshold 0`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
for (const stream of streams) {
|
for (const stream of streams) {
|
||||||
const dir = path.join(STREAMS_DIR, stream.id)
|
const dir = path.join(STREAMS_DIR, stream.id)
|
||||||
fs.mkdirSync(path.join(dir, 'chrome-profile'), { recursive: true })
|
fs.mkdirSync(path.join(dir, 'chrome-profile'), { recursive: true })
|
||||||
@@ -49,6 +113,7 @@ for (const stream of streams) {
|
|||||||
USER: stream.user ?? '',
|
USER: stream.user ?? '',
|
||||||
PASS: stream.pass ?? '',
|
PASS: stream.pass ?? '',
|
||||||
GPU_FLAGS: stream.gpu ? '' : ' --disable-gpu \\\n',
|
GPU_FLAGS: stream.gpu ? '' : ' --disable-gpu \\\n',
|
||||||
|
ENCODER_FLAGS: buildEncoderFlags(stream),
|
||||||
AUTO_RELOAD: stream.autoReload ? 'true' : 'false',
|
AUTO_RELOAD: stream.autoReload ? 'true' : 'false',
|
||||||
AUTO_RELOAD_INTERVAL: stream.autoReloadInterval ?? 3600,
|
AUTO_RELOAD_INTERVAL: stream.autoReloadInterval ?? 3600,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,18 +73,7 @@ command=bash -c "sleep {{STREAM_DELAY}} && ffmpeg \
|
|||||||
-i {{DISPLAY}} \
|
-i {{DISPLAY}} \
|
||||||
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
|
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
|
||||||
-shortest \
|
-shortest \
|
||||||
-c:v libx264 \
|
{{ENCODER_FLAGS}}
|
||||||
-preset {{PRESET}} \
|
|
||||||
-tune {{TUNE}} \
|
|
||||||
-profile:v baseline \
|
|
||||||
-level 3.1 \
|
|
||||||
-pix_fmt yuv420p \
|
|
||||||
-g {{GOP}} \
|
|
||||||
-keyint_min {{GOP}} \
|
|
||||||
-sc_threshold 0 \
|
|
||||||
-b:v {{BITRATE}} \
|
|
||||||
-maxrate {{BITRATE}} \
|
|
||||||
-bufsize {{BUFSIZE}} \
|
|
||||||
-c:a aac \
|
-c:a aac \
|
||||||
-b:a 128k \
|
-b:a 128k \
|
||||||
-ar 44100 \
|
-ar 44100 \
|
||||||
|
|||||||
+70
-4
@@ -29,6 +29,70 @@ function supervisorctl(cmd: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NVENC_PRESET: Record<string, string> = {
|
||||||
|
ultrafast: "p1", superfast: "p1", veryfast: "p2",
|
||||||
|
faster: "p3", fast: "p3", medium: "p4",
|
||||||
|
slow: "p5", slower: "p6", veryslow: "p7",
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEncoderFlags(stream: Stream): string {
|
||||||
|
const { preset, tune, gop, bitrate, bufsize } = stream
|
||||||
|
const hwaccel = (process.env.FFMPEG_HWACCEL ?? "").toLowerCase().trim()
|
||||||
|
const lines: string[] = []
|
||||||
|
const ln = (s: string) => lines.push(` ${s} \\`)
|
||||||
|
|
||||||
|
if (hwaccel === "nvenc") {
|
||||||
|
ln(`-c:v h264_nvenc`)
|
||||||
|
ln(`-preset ${NVENC_PRESET[preset] ?? "p4"}`)
|
||||||
|
ln(`-tune ${tune === "zerolatency" ? "ll" : "hq"}`)
|
||||||
|
ln(`-profile:v high`)
|
||||||
|
ln(`-pix_fmt yuv420p`)
|
||||||
|
ln(`-rc cbr`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else if (hwaccel === "vaapi") {
|
||||||
|
ln(`-vaapi_device /dev/dri/renderD128`)
|
||||||
|
ln(`-vf 'format=nv12,hwupload'`)
|
||||||
|
ln(`-c:v h264_vaapi`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else if (hwaccel === "qsv") {
|
||||||
|
ln(`-c:v h264_qsv`)
|
||||||
|
ln(`-preset veryfast`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-pix_fmt nv12`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
} else {
|
||||||
|
ln(`-c:v libx264`)
|
||||||
|
ln(`-preset ${preset}`)
|
||||||
|
ln(`-tune ${tune}`)
|
||||||
|
ln(`-profile:v baseline`)
|
||||||
|
ln(`-level 3.1`)
|
||||||
|
ln(`-pix_fmt yuv420p`)
|
||||||
|
ln(`-g ${gop}`)
|
||||||
|
ln(`-keyint_min ${gop}`)
|
||||||
|
ln(`-sc_threshold 0`)
|
||||||
|
ln(`-b:v ${bitrate}`)
|
||||||
|
ln(`-maxrate ${bitrate}`)
|
||||||
|
ln(`-bufsize ${bufsize}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
// converts "1920x1080" → "1920,1080" for Chrome --window-size flag
|
// converts "1920x1080" → "1920,1080" for Chrome --window-size flag
|
||||||
function resolutionToChrome(res: string): string {
|
function resolutionToChrome(res: string): string {
|
||||||
return res.replace("x", ",")
|
return res.replace("x", ",")
|
||||||
@@ -64,6 +128,7 @@ export function provisionStream(stream: Stream): void {
|
|||||||
USER: stream.user ?? "",
|
USER: stream.user ?? "",
|
||||||
PASS: stream.pass ?? "",
|
PASS: stream.pass ?? "",
|
||||||
GPU_FLAGS: stream.gpu ? "" : " --disable-gpu \\\n",
|
GPU_FLAGS: stream.gpu ? "" : " --disable-gpu \\\n",
|
||||||
|
ENCODER_FLAGS: buildEncoderFlags(stream),
|
||||||
AUTO_RELOAD: stream.autoReload ? "true" : "false",
|
AUTO_RELOAD: stream.autoReload ? "true" : "false",
|
||||||
AUTO_RELOAD_INTERVAL: stream.autoReloadInterval ?? 3600,
|
AUTO_RELOAD_INTERVAL: stream.autoReloadInterval ?? 3600,
|
||||||
}
|
}
|
||||||
@@ -153,20 +218,21 @@ function fetchAllStatuses(): Record<string, Record<string, ProgramStatus>> {
|
|||||||
const result: Record<string, Record<string, ProgramStatus>> = {}
|
const result: Record<string, Record<string, ProgramStatus>> = {}
|
||||||
try {
|
try {
|
||||||
// One call for all programs — avoid N×5 blocking execSync calls per poll cycle
|
// One call for all programs — avoid N×5 blocking execSync calls per poll cycle
|
||||||
|
// supervisorctl exits 3 when any process is EXITED/STOPPED — || true keeps execSync from throwing
|
||||||
const out = execSync(
|
const out = execSync(
|
||||||
`supervisorctl -c /etc/supervisor/supervisord.conf status`,
|
`supervisorctl -c /etc/supervisor/supervisord.conf status || true`,
|
||||||
{ stdio: "pipe" }
|
{ stdio: "pipe", shell: "/bin/sh" }
|
||||||
).toString()
|
).toString()
|
||||||
for (const line of out.split("\n")) {
|
for (const line of out.split("\n")) {
|
||||||
// e.g. "ffmpeg-abc123 RUNNING pid 42, uptime 0:01:00"
|
// e.g. "ffmpeg-abc123 RUNNING pid 42, uptime 0:01:00"
|
||||||
const m = line.match(/^(\S+)-(\S+)\s+(RUNNING|STOPPED|FATAL|STARTING)/)
|
const m = line.match(/^(xvfb|chromium|autologin|autoreload|x11vnc|ffmpeg)-(\S+)\s+(RUNNING|STOPPED|FATAL|STARTING)/)
|
||||||
if (!m) continue
|
if (!m) continue
|
||||||
const [, program, id, status] = m
|
const [, program, id, status] = m
|
||||||
if (!result[id]) result[id] = {}
|
if (!result[id]) result[id] = {}
|
||||||
result[id][program] = status as ProgramStatus
|
result[id][program] = status as ProgramStatus
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// supervisorctl can exit non-zero; return whatever was parsed
|
// fallback: supervisorctl completely unavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
_statusCache = result
|
_statusCache = result
|
||||||
|
|||||||
Reference in New Issue
Block a user