← Commit history

Initial commit: app/mjpeg-stream-viewer

Colby Knox ·308276a5fc ·26d ago
2 files changed +117
README.mdadded+50
@@ -0,0 +1,50 @@+# 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.++![The viewer fitting a 4K bench cam into a Hydrogen pane](screenshots/viewer-fit.png)++## 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.++![Bare mode: nothing but the picture](screenshots/viewer-bare.png)++![Settings: frame-rate cap, default view, idle shutdown](screenshots/viewer-settings.png)++## 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.
page.jsonadded+67
@@ -0,0 +1,67 @@+{+  "name": "mjpeg-stream-viewer",+  "slug": "mjpeg-stream-viewer",+  "version": "0.1.0",+  "type": "app",+  "title": "MJPEG Stream Viewer",+  "brief": "Give it a camera URL and the raw MJPEG stream turns into a pane that fits: aspect kept, frame rate capped, one-click snapshot.",+  "description": "A raw MJPEG stream (multipart/x-mixed-replace, what bench cams, printer cams and camera boxes emit) opened straight in a Hydrogen webview paints at its native size with scrollbars. This viewer proxies the stream through the container so an http camera on the LAN shows inside https Hydrogen, fits the frame to the pane with the aspect kept, caps the frame rate so a 4K 30 fps source does not flood the browser, shows fps and bandwidth, and saves a snapshot to the project. Ships a dock manifest and takes ?url= so a shared Hydrogen workspace can open a camera in a pane.",+  "tags": [+    "camera",+    "mjpeg",+    "stream",+    "viewer",+    "webview",+    "bench-cam",+    "dock"+  ],+  "discovery_triggers": [+    "mjpeg stream viewer",+    "camera stream does not fit",+    "show my camera in hydrogen",+    "multipart/x-mixed-replace",+    "bench cam webview",+    "view mjpeg url"+  ],+  "discovery_pitch": "Paste a camera stream URL and see it fit in a Hydrogen pane: proxied through the container, aspect kept, frame rate capped, snapshot button.",+  "sample_prompts": [+    {+      "label": "Show a camera",+      "prompt": "Show http://192.168.0.34:3000/stream in a Hydrogen pane with the mjpeg stream viewer"+    },+    {+      "label": "Cap the rate",+      "prompt": "Open my bench cam in the mjpeg viewer at 5 fps and take a snapshot"+    }+  ],+  "dependencies": {},+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "needs_sudo": false,+  "hero": {+    "type": "image",+    "path": "screenshots/hero.png",+    "headline": "MJPEG Stream Viewer",+    "subhead": "Turn raw camera streams into a panel"+  },+  "files": [+    "package.json",+    "README.md",+    "SKILL.md",+    "install.sh",+    "uninstall.sh",+    "server.mjs",+    "bin/mjpeg-stream-viewer",+    "public/**",+    "dockbar.json",+    "screenshots/*.png"+  ],+  "author": {+    "name": "Colby Knox",+    "email": "[email protected]"+  },+  "license": "CC-BY-SA-4.0",+  "org": "adom"+}\ No newline at end of file