The WHEP routes were mounted by http/server.go via the app/webrtc
Handler.Register(), but Subscribe and Unsubscribe carried no swag
annotations. The Swagger UI at /api/swagger/index.html therefore
didn't list /api/v3/whep/* — programmatic API consumers and humans
browsing the docs couldn't discover the endpoints.
Adds the standard upstream-shaped @Summary / @Tags / @ID / @Router
annotations on Subscribe and Unsubscribe (matching the rtmp.go and
srt.go pattern) and regenerates docs/{docs.go,swagger.json,swagger.yaml}
via 'make swagger'. Verified: swagger.json now contains both paths,
swagger UI renders them under the v16.16.0 tag.
Closes#3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End-to-end exercise of the M2 pipeline — subsystem hook, port
allocation, two-track forwarding, WHEP handshake — without
spinning up a full Core HTTP server:
- Fire onProcessStart directly to get the two RTP legs back
- Parse video + audio UDP ports out of the leg addresses,
assert adjacency
- Mount the Handler on an Echo httptest server
- Build a Pion PeerConnection (recvonly video + audio), POST
its offer, feed the answer back in
- Spray synthetic RTP packets at both loopback sockets
- Assert both OnTrack callbacks fire and each delivers at least
one RTP packet within 10s
- DELETE via the returned Location header to confirm teardown
Passes cleanly under -race in ~1s. Catches regressions across
the whole M2 wiring from a single fixture.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Installs the WebRTC egress subsystem at Core boot when
cfg.WebRTC.Enable is true and the subsystem constructs cleanly:
- http.Config gains an optional WebRTC *appwebrtc.Handler field;
server.setRoutesV3 mounts its WHEP routes on the JWT-protected
/api/v3 group.
- api.start() constructs the Subsystem, registers its ProcessHooks
with the restreamer, and builds a Handler. A construction failure
is logged and Core continues without WebRTC — consistent with
disabling the subsystem outright.
- api.stop() closes the Handler (tearing down active peers) before
closing the Subsystem (releasing per-process UDP sockets), mirroring
the RTMP/SRT teardown pattern.
Verified: go build ./... clean; go test ./app/webrtc/...
./core/webrtc/... ./restream/... ./http/... all pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces the HTTP surface the browser (or OBS WebRTC clients)
target when subscribing to a process's egress:
POST /whep/:id -> answer SDP + Location header
DELETE /whep/:id/:resource -> tear down a specific peer
The handler looks up the per-process stream pair via the Subsystem,
validates SDP offer shape, and delegates peer creation to the core
PeerFactory's CreatePeerFromSources (two-source forwarding).
WHEP routes are left unauthenticated in M2 — browsers and OBS don't
carry the Core JWT, and per-process signed-URL tokens are an M3
enhancement. Deployments should place the endpoint behind an
authenticated reverse-proxy for now.
Tests cover:
- 404 for POSTs against unregistered streams
- 400 for empty/invalid SDP offers once a stream is registered
- 404 for DELETE against unknown resource ids
Introduces the subsystem layer that sits alongside api.API and wires
the M1 core/webrtc primitives into the per-process restream lifecycle.
app/webrtc/subsystem.go:
- Subsystem struct holding the global WebRTC config, core PeerFactory,
per-process stream map, and logger
- New(config.DataWebRTC, logger) constructor
- Enabled(), Hooks(), Close(), lookup() methods
app/webrtc/lifecycle.go:
- onProcessStart: allocates an adjacent UDP port pair, binds two
Pion Sources (video on V, audio on V+1), registers them under the
process id, and returns the two RTP output legs to append to the
FFmpeg command.
- onProcessStop: tears down the pair.
- allocAdjacentPair: retries up to 10 times to find a free (V, V+1)
pair since the kernel's ephemeral picker can hand us an odd port.
- splitRTPLegs: converts BuildArgs' flat []string into two ConfigIO
entries by splitting on the second -map token.
core/webrtc/peer.go + forward.go:
- Adds PeerFactory.CreatePeerFromSources for the M2 two-source
forwarding mode (video and audio on separate UDP ports, no
payload-type sniffing). Leaves CreatePeer intact for the M1 PoC.
- Adds forwardRTPSplit companion goroutine.
config/data.go:
- Promote anonymous WebRTC struct to named type DataWebRTC so
app/webrtc can accept it by value.
Adds Alloc(), the ephemeral loopback UDP port grabber the subsystem
uses to pick the RTP port it will hand to FFmpeg and then re-bind with
core/webrtc.NewSourceOn. Covered by a 100x rebind test.
Adds BuildArgs(), which emits the -f rtp output fragments (video on
the passed port, audio on port+1) with copy codecs by default and an
H.264 baseline / libopus re-encode leg when ForceTranscode is set.
Covered by three unit tests.
If purging is enabled, overwriting a file with a file of the same
or smaller size will not result in an error.
It is now possible to change the purging mode on an existing sized
filesystem.
1. Allow variables in placeholders for parameter values, e.g.
{rtmp,name=$processid}. The variable starts with a $ letter.
The recognized variables are provided with the Replace func.
2. The template func recieves the process config and the name of
the section where this placeholder is located, i.e. "global",
"input", or "output".
created_at represents the time when the configuration has been persisted to disk.
loaded_at represents the time when the configuration has actually been used.
If created_at is larger than loaded_at, then the Core needs a reload in order
to apply the latest configuration.
if created_at is lower than laoded_at, then the Core applied the latest
configuration.
The value of updated_at is irrelevant and shouldn't be used.
If no path is given in the environment variable CORE_CONFIGFILE, different
standard locations will be probed:
- os.UserConfigDir() + /datarhei-core/config.js
- os.UserHomeDir() + /.config/datarhei-core/config.js
- ./config/config.js
If the config.js doesn't exist in any of these locations, it will be
assumed at ./config/config.js