Recipe: stream an art exhibition with multiple cameras
Put every gallery room online at once: one ingress per camera, public play pages and embeds per room, optional recording to your own S3, and date/time plus watermark overlays. A hands-on multi-camera build.
Why a gallery streams
A vernissage happens once, in one city, for the people who can be in the room. Streaming it turns a single evening into a global opening and a permanent archive — collectors abroad watch live, the press embeds a feed, and the recording becomes part of the show's catalogue. For a museum or gallery, that is reach and provenance for the cost of a few IP cameras.
This recipe wires one live channel per room, all under a single app, with public players you can drop into any page.
Architecture in words
Each camera becomes one ingress and therefore one room. An IP camera is pulled over RTSP; a phone or an OBS machine pushes over RTMP. Every room gets a public /play page, an iframe /embed, and an HLS playlist for scale. Overlays (a corner watermark and a CCTV-style timestamp) render on the player, and recording — when you want it — writes each room to the app's own S3 bucket as a VOD.
1. Create the app
export BASE="https://your-server.example.com/api/v1"
export TOKEN="sk_...." # the seeded global token
curl -s -X POST $BASE/apps -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"gallery","displayName":"Gallery Live"}'
2. One ingress per room
Pull an RTSP camera (transcoding stays on by default, which is what HLS needs):
curl -s -X POST $BASE/apps/gallery/ingress -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"inputType":"url","room":"hall-a","url":"rtsp://camera.local/stream"}'
For a room where a curator streams from OBS or a phone, create an RTMP ingress instead:
curl -s -X POST $BASE/apps/gallery/ingress -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"inputType":"rtmp","room":"hall-b"}'
The RTMP response returns everything the encoder needs — rtmp_url (rtmp://your-server.example.com:1935/live/<key>), stream_key, plus a public player_url and embed_iframe. Repeat once per camera: hall-a, hall-b, sculpture-garden, and so on. Each ingress counts against the app's concurrent-stream quota.
3. Add the overlays
Install and configure the two player-overlay plugins. Every field has a default, so you only send what you want to change:
curl -X POST $BASE/apps/gallery/plugins/timestamp/install -H "Authorization: Bearer $TOKEN"
curl -X PATCH $BASE/apps/gallery/plugins/timestamp -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "config": {"format": "datetime-24h", "position": "bottom-left", "color": "#ffffff", "showName": true}}'
curl -X POST $BASE/apps/gallery/plugins/watermark/install -H "Authorization: Bearer $TOKEN"
curl -X PATCH $BASE/apps/gallery/plugins/watermark -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "config": {"text": "Museo Example", "position": "bottom-right", "opacity": 0.6}}'
Watermark defaults are text: "StreamHub", position: "bottom-right", opacity: 0.6; timestamp defaults are format: "datetime-24h", position: "bottom-right", color: "#00e5ff", showName: true.
4. Publish the public gallery
Every room is now watchable with no token:
https://your-server.example.com/play/gallery/hall-a
https://your-server.example.com/embed/gallery/hall-a
https://your-server.example.com/hls/gallery/hall-a/index.m3u8
Build a simple landing page with one iframe per room and you have a live multi-camera gallery:
<iframe src="https://your-server.example.com/embed/gallery/hall-a"
width="640" height="360" frameborder="0" allowfullscreen></iframe>
5. Optional — record each room to your S3
Point the app at your bucket, then start a recording per room:
curl -X PUT $BASE/apps/gallery/s3 -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"provider":"aws","bucket":"gallery-archive","region":"us-east-1","key":"AKIA...","secret":"..."}'
curl -s -X POST $BASE/apps/gallery/recording/start -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"roomName":"gallery-hall-a"}'
For a long opening, set recording.split_minutes (0, 15, 30, 60, 90 or 120) so each part becomes its own VOD, and recording.snapshot_seconds for periodic thumbnails.
What you should see
Each camera appears in the app's live tab within a moment. The /play pages show the feed with the timestamp and watermark drawn on top, and once egress finishes a recording, the VOD turns ready with a playable URL in your bucket.
Troubleshooting
- RTSP room never goes live — confirm the server can reach the camera URL (a private
camera.localmust resolve from the server, not just your laptop), and that the camera speaks H.264. - Overlays missing from the recording — that is expected. Watermark and timestamp are drawn client-side on the player only; they are not burned into the recorded MP4 or any outbound RTMP. Treat them as a presentation layer.
Closing
StreamHub is open source, so a gallery can run this on a single VM it controls and keep every recording in its own archive. If you would rather hand off the camera provisioning, the public embed page and the archival workflow, that is exactly the kind of turnkey install our team does as a consulting engagement.