MJPEG Stream Viewer
Public Made by Adomby adom
Give it a camera URL and the raw MJPEG stream turns into a pane that fits: aspect kept, frame rate capped, one-click snapshot.
MJPEG Stream Viewer
Give it a camera URL and the raw stream turns into a pane that fits.
A bench cam, a printer cam or a camera box usually serves a raw MJPEG stream (multipart/x-mixed-replace). Opened straight in a Hydrogen webview that stream paints at its native size, so a 4K camera shows one corner of the frame behind two scrollbars, and an http camera on the LAN cannot show inside https Hydrogen at all. This viewer fixes both: it proxies the stream through the container (same origin, so the browser is happy), fits the picture to the pane with the aspect kept, caps the frame rate so a 30 fps 4K source does not flood the browser, shows fps, bandwidth and frame size, and saves a snapshot to the project with one click.

Use it
adom-wiki pkg install adom/mjpeg-stream-viewer
mjpeg-stream-viewer --ai-thread "<your thread>" show --url http://192.168.0.34:3000/stream
show starts the server if needed and opens the viewer in a Hydrogen webview tab in a work pane (never the VS Code pane). Or start it yourself and open the URL it prints:
mjpeg-stream-viewer --ai-thread "<your thread>" serve --print-url --port auto --url http://<camera>/stream
The page takes ?url=<stream>, ?fps=<n> and ?bare=1. Paste a URL in the bar and press Show; Save source keeps it in the dropdown for next time; Fit keeps the whole frame, Fill crops to the pane; Snapshot writes ~/project/screenshots/mjpeg-<timestamp>.jpg. Bare mode (--bare on show, ?bare=1, the corner button on hover, or the b key) hides the header and the bar so the pane is nothing but the picture; it is remembered per browser.


What it does to the stream
- Proxies it:
GET /stream?url=<camera>&fps=<n>fetches the camera from the container and re-serves it same-origin. Only http and https sources; the viewer refuses to proxy itself. - Caps the rate: frames above the cap are dropped in the proxy, so the browser sees at most
fpsframes per second. Default 10; a 4K source at 30 fps is about 11 MB/s raw, and 5 fps of it is about 2 MB/s. - Reads the frame size from the JPEG itself and shows it in the status pills, with a hint when the source is larger than the pane can use.
GET /snapshot.jpg?url=<camera>returns one frame;POST /api/snapshot { aiThread, url }saves one.- A camera that serves a still image instead of a stream works too: the image is fetched once per show.
Status, settings, lifecycle
GET /api/status is the dock's LED source: source, frames, size, each with a state and a hint. Settings persist on the server (GET/POST /settings: maxFps, fit, ttlHours, sources). State-changing calls need the ai-thread name (--ai-thread on the CLI, aiThread in a raw body); anonymous ones are refused with caller_identity_required. The server stops itself after 24 hours idle (--ttl <hours>, --keep-alive), registers itself under ~/.adom/instances/mjpeg-stream-viewer/, and ls lists live instances; stop shuts one down politely.
In a shared workspace
The page ships dockbar.json, so it launches from the dock and a shared Hydrogen workspace can open a camera in a pane:
{ "panel": "app", "app": "adom/mjpeg-stream-viewer", "displayName": "Bench cam", "args": { "url": "http://192.168.0.34:3000/stream", "fps": "5" } }
Limits
A viewer, not a recorder. One source per pane. MJPEG and stills only (no HLS, no WebRTC). The camera must be reachable from the container: true on the desktop and in the office, not true for a cloud container on another network, and the status pill says so.
# MJPEG Stream Viewer
Give it a camera URL and the raw stream turns into a pane that fits.
A bench cam, a printer cam or a camera box usually serves a raw MJPEG stream (`multipart/x-mixed-replace`). Opened straight in a Hydrogen webview that stream paints at its native size, so a 4K camera shows one corner of the frame behind two scrollbars, and an http camera on the LAN cannot show inside https Hydrogen at all. This viewer fixes both: it proxies the stream through the container (same origin, so the browser is happy), fits the picture to the pane with the aspect kept, caps the frame rate so a 30 fps 4K source does not flood the browser, shows fps, bandwidth and frame size, and saves a snapshot to the project with one click.

## Use it
```bash
adom-wiki pkg install adom/mjpeg-stream-viewer
mjpeg-stream-viewer --ai-thread "<your thread>" show --url http://192.168.0.34:3000/stream
```
`show` starts the server if needed and opens the viewer in a Hydrogen webview tab in a work pane (never the VS Code pane). Or start it yourself and open the URL it prints:
```bash
mjpeg-stream-viewer --ai-thread "<your thread>" serve --print-url --port auto --url http://<camera>/stream
```
The page takes `?url=<stream>`, `?fps=<n>` and `?bare=1`. Paste a URL in the bar and press Show; Save source keeps it in the dropdown for next time; Fit keeps the whole frame, Fill crops to the pane; Snapshot writes `~/project/screenshots/mjpeg-<timestamp>.jpg`. Bare mode (`--bare` on `show`, `?bare=1`, the corner button on hover, or the `b` key) hides the header and the bar so the pane is nothing but the picture; it is remembered per browser.


## What it does to the stream
- Proxies it: `GET /stream?url=<camera>&fps=<n>` fetches the camera from the container and re-serves it same-origin. Only http and https sources; the viewer refuses to proxy itself.
- Caps the rate: frames above the cap are dropped in the proxy, so the browser sees at most `fps` frames per second. Default 10; a 4K source at 30 fps is about 11 MB/s raw, and 5 fps of it is about 2 MB/s.
- Reads the frame size from the JPEG itself and shows it in the status pills, with a hint when the source is larger than the pane can use.
- `GET /snapshot.jpg?url=<camera>` returns one frame; `POST /api/snapshot { aiThread, url }` saves one.
- A camera that serves a still image instead of a stream works too: the image is fetched once per show.
## Status, settings, lifecycle
`GET /api/status` is the dock's LED source: `source`, `frames`, `size`, each with a state and a hint. Settings persist on the server (`GET/POST /settings`: `maxFps`, `fit`, `ttlHours`, `sources`). State-changing calls need the ai-thread name (`--ai-thread` on the CLI, `aiThread` in a raw body); anonymous ones are refused with `caller_identity_required`. The server stops itself after 24 hours idle (`--ttl <hours>`, `--keep-alive`), registers itself under `~/.adom/instances/mjpeg-stream-viewer/`, and `ls` lists live instances; `stop` shuts one down politely.
## In a shared workspace
The page ships `dockbar.json`, so it launches from the dock and a shared Hydrogen workspace can open a camera in a pane:
```json
{ "panel": "app", "app": "adom/mjpeg-stream-viewer", "displayName": "Bench cam", "args": { "url": "http://192.168.0.34:3000/stream", "fps": "5" } }
```
## Limits
A viewer, not a recorder. One source per pane. MJPEG and stills only (no HLS, no WebRTC). The camera must be reachable from the container: true on the desktop and in the office, not true for a cloud container on another network, and the status pill says so.