The raw HTTP endpoints (/stream/raw, /snapshot/raw, and the
mjpg_streamer-compatible /webcam/?action=stream and ?action=snapshot)
are intentionally not gated by any runtime toggle — OctoPrint's
Classic Webcam plugin uses these for live feeds and timelapse
snapshots and they must remain available regardless of overlay master,
overlay type, or RTSP toggle state.
This was already the behavior; this commit just makes the invariant
explicit in code comments and the README so future changes do not
accidentally regress it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The overlay can now be rendered either way at runtime:
- baked (default): server decodes the JPEG, draws the overlay, re-encodes,
publishes to the broadcaster and ffmpeg. Works with OctoPrint, VLC, and
any RTSP consumer. Same behavior as before.
- html: server skips decode/draw/encode entirely. The browser draws the
stats as positioned HTML over the raw stream. No video-side overlay,
so /stream/overlay and /snapshot/overlay return 503 and the RTSP
overlay process is stopped while in html mode. The user's RTSP overlay
preference is preserved and restored when switching back to baked.
Pipeline:
- overlayBaked atomic.Bool, default true
- SetOverlayBaked(bool) clears the broadcaster and stops the RTSP overlay
process when switching to html; restarts it when switching to baked if
the master toggle and user preference are both on
- runOverlay() skips the decode/draw/encode when not (enabled AND baked)
- pipelineState gains overlay_baked field, default true on load so
existing installs keep their current behavior
Web:
- /api/overlay GET returns {"enabled", "type"}; POST accepts either or
both fields (omitted fields are left unchanged)
- gateOverlay middleware also 503s when the overlay is in html mode
- /overlay full-screen page in html mode serves /stream/raw and renders
the same HTML overlay markup as the index card
UI:
- Second toggle next to "Overlay feed" — Baked vs HTML
- Overlay card has a stream-frame wrapper so the HTML overlay can
absolute-position on top of the image
- New .html-overlay CSS with state pill, filename, hotend/bed temps,
progress bar, elapsed/ETA — matches the baked overlay's information
but rendered crisp in the browser
- HTML overlay updates from the existing /api/stats poll loop
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The streams grid is fixed at two equal columns by default. When the
overlay card is hidden the right column was just empty space, leaving
the raw feed at half width. Add a .solo modifier that collapses the
grid to a single column and toggle it from applyOverlayVisibility().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When OCTOCAM_BASE_PATH is set (e.g. /octocam), all routes are served
under that prefix and the bare prefix without trailing slash gets a
301 redirect to the canonical form with trailing slash.
Implementation:
- Config: OCTOCAM_BASE_PATH env var, normalized to ensure leading
slash and stripped trailing slash
- Server: when base path is set, wrap the existing mux with
http.StripPrefix and add a redirect handler for the bare prefix.
All internal routes stay registered at their root paths so the same
mux works whether the prefix is empty or not.
- Templates: index.html and feed.html receive a BasePath template
variable used to construct all asset, stream, link, and JS fetch
URLs. Standalone /raw and /overlay pages prefix StreamURL and
OtherURL in server.go before rendering.
- Redirect from /overlay (when overlay disabled) now goes to
BasePath + "/" instead of "/"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The overlay feed (rendering, MJPEG broadcaster, RTSP) is now gated by a
master toggle persisted in the state file alongside the RTSP toggles.
A fresh install serves only the raw feed and skips the JPEG decode /
draw / re-encode loop entirely, saving the bulk of the per-frame CPU.
Pipeline changes:
- runOverlay() drops frames when the master is off, before any decode
- New SetOverlayEnabled() clears the overlay broadcaster's cached frame
and stops the ffmpeg overlay process when disabling
- The user's RTSP overlay preference is tracked in overlayRTSPPref so
it survives master toggles. RTSP overlay only runs when both master
overlay and the user preference are on.
- State struct renamed pipelineState; adds overlay_enabled field. Old
state files load cleanly (missing field defaults to false).
Web changes:
- New /api/overlay endpoint (GET/POST) for the master toggle
- /stream/overlay and /snapshot/overlay return 503 when master is off,
so img elements show broken instead of hanging on an empty stream
- /overlay full-screen page redirects to / when master is off
- mjpeg.Broadcaster.Clear() empties the cached last frame so snapshots
do not serve stale data after a feed is disabled
UI:
- Master "Overlay feed" toggle in the stats bar
- Overlay card is hidden when master is off; img src is removed so the
browser does not retry against a 503 endpoint
- RTSP overlay sub-toggle is disabled in the UI when master is off
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HTTP server: add ReadHeaderTimeout (5s) and WriteTimeout (10s); MJPEG
streams clear the deadline via conn.SetDeadline after hijacking so
long-lived streams are unaffected
- OctoPrint client: cap response body at 1 MiB with io.LimitReader to
prevent memory exhaustion from a misbehaving OctoPrint instance
- Feed toggle API: wrap r.Body with http.MaxBytesReader(1024) to prevent
DoS via oversized POST body
- State file: tighten permissions from 0644/0755 to 0640/0750
- Middleware: add X-Content-Type-Options: nosniff to all responses
- Config: reject OCTOPRINT_URL with non-http/https scheme to prevent SSRF
via file://, gopher://, etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OctoPi's default haproxy config proxies /webcam/ to port 8080 using the
traditional mjpg_streamer URL format. Without these routes octocam returns
404, which causes OctoPrint Classic Webcam to show "Webcam stream not
loaded". The handler maps ?action=stream to the raw MJPEG broadcaster
and ?action=snapshot to the raw JPEG snapshot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OctoPrint Classic Webcam plugin sets crossorigin="anonymous" on its
<img> element, which forces a CORS check in the browser. Without
Access-Control-Allow-Origin: * the browser silently blocks the stream
even though the snapshot (fetched server-side by OctoPrint) loads fine.
Applied via middleware so every route gets the header. Also dropped
Connection: close from the MJPEG broadcaster — it is unnecessary for
a streaming response and can confuse some HTTP clients.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/raw and /overlay now serve a no-chrome full-screen image, matching
ustreamer's minimal feed view. /snapshot/raw and /snapshot/overlay
return the latest captured JPEG frame as image/jpeg (503 until the
first frame arrives). Broadcaster stores the last published frame
for snapshot reads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each RTSP feed (raw, overlay) can now be enabled or disabled at runtime
without restarting the service.
API:
GET /api/feeds → {"raw":{enabled,running},"overlay":{...}}
POST /api/feeds/raw ← {"enabled": false}
POST /api/feeds/overlay ← {"enabled": true}
When disabled, the ffmpeg subprocess is stopped and watchExit skips the
auto-restart. Re-enabling starts a fresh subprocess immediately.
The main web page shows two toggle switches in the stats bar (one per
RTSP feed). State is loaded on page open and updated instantly on toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each feed now has its own full-screen page (black background, image
fills the viewport) with a minimal nav bar linking back to the combined
view and to the other feed. The main page gains expand links (⤢) on
each card header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Browser: replace WebRTC/HLS with direct MJPEG HTTP streams
(/stream/raw, /stream/overlay) served by the Go server itself.
<img> tag, no JavaScript, works in every browser, zero extra encoding.
CPU:
- Raw RTSP now uses ffmpeg -c:v copy (MJPEG passthrough), eliminating
one full libx264 encode pass.
- Overlay RTSP throttled to 5fps (OCTOCAM_OVERLAY_RTSP_FPS); browser
still receives the full camera framerate via MJPEG HTTP.
Architecture: new internal/mjpeg.Broadcaster fans JPEG frames out to
any number of HTTP clients with per-client drop-on-full buffering.
RTSP URLs shown in the stats bar for VLC / OctoPrint use.
Fix .gitignore to use /octocam so it doesn't match cmd/octocam dir.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web UI was embedding 127.0.0.1:8889/8888 as the MediaMTX WebRTC
and HLS base URLs. Browsers on other machines tried to connect to their
own localhost, getting nothing. Now handleIndex derives the public host
from the incoming HTTP request's Host header and substitutes it into
the MediaMTX URLs, keeping the port.
Also removed readIPs: [] from mediamtx.yml — an empty list blocks all
readers in MediaMTX, which caused it to drop the ffmpeg publisher after
a timeout (the overlay broken pipe).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Go service that reads a USB webcam via V4L2, overlays OctoPrint stats
(state, filename, temps, progress, ETA) on one stream, and publishes
both raw and overlay feeds to MediaMTX via RTSP for WebRTC/HLS/RTSP
fan-out. Includes web UI, systemd units, and installer script.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>