fix(http/api): carry process WebRTC config through the API DTO
Some checks failed
tests / build (push) Failing after 3s
Some checks failed
tests / build (push) Failing after 3s
ProcessConfig in http/api/process.go shipped without a WebRTC field, so
JSON arriving at POST /api/v3/process was silently stripped of
"webrtc":{"enabled":true}. Marshal() handed restream a zero
ConfigWebRTC, the OnProcessStart hook no-op'd, and every WHEP request
returned 404 — even with a running webrtc-enabled process.
Caught on the M2 TrueNAS deploy at acceptance time: GET /process/{id}/config
came back without the webrtc block, despite the inbound JSON having it.
This is the API-layer twin of the earlier 'fix(config): preserve WebRTC
section in Config.Clone()' — same class of bug (drop-on-copy), different
struct.
- Add ProcessConfigWebRTC mirroring app.ConfigWebRTC.
- Marshal: copy DTO -> app.Config.WebRTC.
- Unmarshal: copy app.Config.WebRTC -> DTO.
- Regression tests cover both the JSON->DTO->Config path and the
default (no webrtc block) case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2d29dc9c4a
commit
f6d36bfa66
2 changed files with 111 additions and 11 deletions
|
|
@ -43,18 +43,26 @@ type ProcessConfigLimits struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessConfig represents the configuration of an ffmpeg process
|
// ProcessConfig represents the configuration of an ffmpeg process
|
||||||
|
type ProcessConfigWebRTC struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
VideoPT uint8 `json:"video_pt,omitempty"`
|
||||||
|
AudioPT uint8 `json:"audio_pt,omitempty"`
|
||||||
|
ForceTranscode bool `json:"force_transcode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ProcessConfig struct {
|
type ProcessConfig struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type" validate:"oneof='ffmpeg' ''" jsonschema:"enum=ffmpeg,enum="`
|
Type string `json:"type" validate:"oneof='ffmpeg' ''" jsonschema:"enum=ffmpeg,enum="`
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
Input []ProcessConfigIO `json:"input" validate:"required"`
|
Input []ProcessConfigIO `json:"input" validate:"required"`
|
||||||
Output []ProcessConfigIO `json:"output" validate:"required"`
|
Output []ProcessConfigIO `json:"output" validate:"required"`
|
||||||
Options []string `json:"options"`
|
Options []string `json:"options"`
|
||||||
Reconnect bool `json:"reconnect"`
|
Reconnect bool `json:"reconnect"`
|
||||||
ReconnectDelay uint64 `json:"reconnect_delay_seconds" format:"uint64"`
|
ReconnectDelay uint64 `json:"reconnect_delay_seconds" format:"uint64"`
|
||||||
Autostart bool `json:"autostart"`
|
Autostart bool `json:"autostart"`
|
||||||
StaleTimeout uint64 `json:"stale_timeout_seconds" format:"uint64"`
|
StaleTimeout uint64 `json:"stale_timeout_seconds" format:"uint64"`
|
||||||
Limits ProcessConfigLimits `json:"limits"`
|
Limits ProcessConfigLimits `json:"limits"`
|
||||||
|
WebRTC ProcessConfigWebRTC `json:"webrtc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal converts a process config in API representation to a restreamer process config
|
// Marshal converts a process config in API representation to a restreamer process config
|
||||||
|
|
@ -70,6 +78,12 @@ func (cfg *ProcessConfig) Marshal() *app.Config {
|
||||||
LimitCPU: cfg.Limits.CPU,
|
LimitCPU: cfg.Limits.CPU,
|
||||||
LimitMemory: cfg.Limits.Memory * 1024 * 1024,
|
LimitMemory: cfg.Limits.Memory * 1024 * 1024,
|
||||||
LimitWaitFor: cfg.Limits.WaitFor,
|
LimitWaitFor: cfg.Limits.WaitFor,
|
||||||
|
WebRTC: app.ConfigWebRTC{
|
||||||
|
Enabled: cfg.WebRTC.Enabled,
|
||||||
|
VideoPT: cfg.WebRTC.VideoPT,
|
||||||
|
AudioPT: cfg.WebRTC.AudioPT,
|
||||||
|
ForceTranscode: cfg.WebRTC.ForceTranscode,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.generateInputOutputIDs(cfg.Input)
|
cfg.generateInputOutputIDs(cfg.Input)
|
||||||
|
|
@ -150,6 +164,11 @@ func (cfg *ProcessConfig) Unmarshal(c *app.Config) {
|
||||||
cfg.Limits.Memory = c.LimitMemory / 1024 / 1024
|
cfg.Limits.Memory = c.LimitMemory / 1024 / 1024
|
||||||
cfg.Limits.WaitFor = c.LimitWaitFor
|
cfg.Limits.WaitFor = c.LimitWaitFor
|
||||||
|
|
||||||
|
cfg.WebRTC.Enabled = c.WebRTC.Enabled
|
||||||
|
cfg.WebRTC.VideoPT = c.WebRTC.VideoPT
|
||||||
|
cfg.WebRTC.AudioPT = c.WebRTC.AudioPT
|
||||||
|
cfg.WebRTC.ForceTranscode = c.WebRTC.ForceTranscode
|
||||||
|
|
||||||
cfg.Options = make([]string, len(c.Options))
|
cfg.Options = make([]string, len(c.Options))
|
||||||
copy(cfg.Options, c.Options)
|
copy(cfg.Options, c.Options)
|
||||||
|
|
||||||
|
|
|
||||||
81
http/api/process_webrtc_test.go
Normal file
81
http/api/process_webrtc_test.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/restream/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestProcessConfigWebRTCRoundtrip locks down the API DTO ↔ restream
|
||||||
|
// app.Config mapping for the per-process WebRTC block.
|
||||||
|
//
|
||||||
|
// Regression: the M2 cut shipped without WebRTC on ProcessConfig, so
|
||||||
|
// JSON arriving at POST /api/v3/process was silently stripped of
|
||||||
|
// `webrtc.enabled`, the restream config never saw it, the start hook
|
||||||
|
// never bound a Source, and WHEP returned 404. This test fails on the
|
||||||
|
// pre-fix code (Marshal would yield `app.ConfigWebRTC{}`) and passes
|
||||||
|
// once the DTO carries the field.
|
||||||
|
func TestProcessConfigWebRTCRoundtrip(t *testing.T) {
|
||||||
|
// 1. JSON in → DTO → app.Config
|
||||||
|
body := []byte(`{
|
||||||
|
"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}],
|
||||||
|
"webrtc":{"enabled":true,"video_pt":102,"audio_pt":111,"force_transcode":true}
|
||||||
|
}`)
|
||||||
|
var dto ProcessConfig
|
||||||
|
if err := json.Unmarshal(body, &dto); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if !dto.WebRTC.Enabled {
|
||||||
|
t.Fatalf("DTO.WebRTC.Enabled lost on JSON decode: %+v", dto.WebRTC)
|
||||||
|
}
|
||||||
|
cfg := dto.Marshal()
|
||||||
|
if !cfg.WebRTC.Enabled || cfg.WebRTC.VideoPT != 102 || cfg.WebRTC.AudioPT != 111 || !cfg.WebRTC.ForceTranscode {
|
||||||
|
t.Fatalf("app.Config.WebRTC mapped wrong: %+v", cfg.WebRTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. app.Config → DTO → JSON out
|
||||||
|
stored := &app.Config{
|
||||||
|
ID: "p",
|
||||||
|
Input: []app.ConfigIO{{ID: "i", Address: "x"}},
|
||||||
|
Output: []app.ConfigIO{{ID: "o", Address: "-"}},
|
||||||
|
WebRTC: app.ConfigWebRTC{
|
||||||
|
Enabled: true,
|
||||||
|
VideoPT: 102,
|
||||||
|
AudioPT: 111,
|
||||||
|
ForceTranscode: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var dto2 ProcessConfig
|
||||||
|
dto2.Unmarshal(stored)
|
||||||
|
if !dto2.WebRTC.Enabled || dto2.WebRTC.VideoPT != 102 {
|
||||||
|
t.Fatalf("Unmarshal lost WebRTC: %+v", dto2.WebRTC)
|
||||||
|
}
|
||||||
|
out, err := json.Marshal(dto2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal: %v", err)
|
||||||
|
}
|
||||||
|
// Decode again and compare.
|
||||||
|
var dto3 ProcessConfig
|
||||||
|
if err := json.Unmarshal(out, &dto3); err != nil {
|
||||||
|
t.Fatalf("re-unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if dto3.WebRTC != dto.WebRTC {
|
||||||
|
t.Fatalf("roundtrip diverged: in=%+v out=%+v", dto.WebRTC, dto3.WebRTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProcessConfigWebRTCDefaults: when "webrtc" is absent in the
|
||||||
|
// inbound JSON, Marshal must still produce a valid app.Config — the
|
||||||
|
// zero ConfigWebRTC means "disabled" and the start hook should no-op.
|
||||||
|
func TestProcessConfigWebRTCDefaults(t *testing.T) {
|
||||||
|
body := []byte(`{"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}]}`)
|
||||||
|
var dto ProcessConfig
|
||||||
|
if err := json.Unmarshal(body, &dto); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
cfg := dto.Marshal()
|
||||||
|
if cfg.WebRTC.Enabled {
|
||||||
|
t.Fatalf("default should be disabled, got %+v", cfg.WebRTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue