Skip to content

Commit 507e721

Browse files
authored
[sfu] refactor all code and add some doc (#21)
* [sfu] refactor all code * add documentation * added more docs * update README: update some old information and added new sections # Conflicts: # README.md * [sfu] big refactor * [config] add publicIp field to running without turn! * fix build
1 parent b843e70 commit 507e721

File tree

14 files changed

+2509
-1193
lines changed

14 files changed

+2509
-1193
lines changed

README.md

Lines changed: 366 additions & 269 deletions
Large diffs are not rendered by default.

packages/relay/cmd/signalling/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import (
66

77
"github.com/gofiber/fiber/v2"
88
"github.com/gofiber/fiber/v2/middleware/pprof"
9+
"github.com/irdkwmnsb/webrtc-grabber/packages/relay/internal/config"
910
"github.com/irdkwmnsb/webrtc-grabber/packages/relay/internal/signalling"
1011
)
1112

1213
func main() {
13-
config, err := signalling.LoadServerConfig()
14+
config, err := config.LoadServerConfig()
1415
if err != nil {
1516
log.Fatal(err)
1617
}

packages/relay/go.sum

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
2020
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
2121
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
2222
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
23-
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
24-
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
2523
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
2624
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
2725
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
@@ -36,28 +34,18 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
3634
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
3735
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
3836
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
39-
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
40-
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
4137
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
4238
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
4339
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
4440
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
45-
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
46-
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
4741
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
4842
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
49-
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
50-
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
5143
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
5244
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
5345
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
5446
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
5547
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
5648
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
57-
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
58-
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
59-
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
60-
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
6149
github.com/pion/turn/v4 v4.1.0 h1:+J56+aS8Bi6B4zij3ah6VvJpRuy8W8FtExR0OJPiTdM=
6250
github.com/pion/turn/v4 v4.1.0/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
6351
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package config
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"net/netip"
8+
"os"
9+
10+
"github.com/irdkwmnsb/webrtc-grabber/packages/relay/internal/api"
11+
"github.com/pion/webrtc/v4"
12+
)
13+
14+
// ServerConfig holds the complete server configuration loaded from conf/config.json.
15+
// It includes settings for authentication, network access control, WebRTC parameters,
16+
// and codec configuration.
17+
//
18+
// Configuration is loaded once at startup via LoadServerConfig and passed to
19+
// the server and peer manager components.
20+
type ServerConfig struct {
21+
// PlayerCredential is an optional password for admin access to player endpoints.
22+
// If nil, no authentication is required (not recommended for production).
23+
PlayerCredential *string `json:"adminCredential"`
24+
25+
// Participants is a list of expected grabber names for monitoring and status display.
26+
// These names are used in the admin interface to show which grabbers are online/offline.
27+
Participants []string `json:"participants"`
28+
29+
// AdminsRawNetworks defines IP address ranges allowed to access admin endpoints.
30+
// Only clients from these CIDR ranges can connect to /ws/player/admin and /ws/player/play.
31+
// Example: ["192.168.1.0/24", "10.0.0.0/8"]
32+
AdminsRawNetworks []netip.Prefix `json:"adminsNetworks"`
33+
34+
// PeerConnectionConfig contains WebRTC peer connection parameters including
35+
// ICE servers (STUN/TURN) for NAT traversal.
36+
PeerConnectionConfig api.PeerConnectionConfig `json:"peerConnectionConfig"`
37+
38+
PublicIP string `json:"publicIp"`
39+
40+
// GrabberPingInterval specifies how often (in seconds) grabbers should send
41+
// ping messages to indicate they are still alive.
42+
GrabberPingInterval int `json:"grabberPingInterval"`
43+
44+
// ServerPort is the TCP port the WebSocket server listens on.
45+
// Defaults to 8000 if not specified.
46+
ServerPort int `json:"serverPort"`
47+
48+
// ServerTLSCrtFile is the path to the TLS certificate file for HTTPS/WSS.
49+
// If nil, the server runs without TLS (not recommended for production).
50+
ServerTLSCrtFile *string `json:"serverTLSCrtFile"`
51+
52+
// ServerTLSKeyFile is the path to the TLS private key file for HTTPS/WSS.
53+
// Must be provided together with ServerTLSCrtFile.
54+
ServerTLSKeyFile *string `json:"serverTLSKeyFile"`
55+
56+
// Codecs defines the list of audio and video codecs supported by the server.
57+
// Each codec specifies MIME type, clock rate, payload type, and channel count.
58+
Codecs []Codec `json:"codecs"`
59+
60+
// WebcamTrackCount specifies the expected number of tracks for webcam streams.
61+
// Typically 2 (one video, one audio). Defaults to 2 if not specified.
62+
// Screen share streams always expect 1 track (video only).
63+
WebcamTrackCount int `json:"webcamTrackCount"`
64+
RecordTimeout uint `json:"recordTimeout"`
65+
RecordStorageDir string `json:"recordStorageDirectory"`
66+
}
67+
68+
// RawCodec represents the JSON structure for codec configuration before conversion
69+
// to WebRTC types. This intermediate representation allows for custom JSON unmarshaling.
70+
type RawCodec struct {
71+
// Params contains the codec parameters
72+
Params struct {
73+
// MimeType identifies the codec (e.g., "video/VP8", "audio/opus")
74+
MimeType string `json:"mimeType"`
75+
76+
// ClockRate is the codec's clock rate in Hz (e.g., 90000 for video, 48000 for audio)
77+
ClockRate uint32 `json:"clockRate"`
78+
79+
// PayloadType is the RTP payload type identifier (96-127 for dynamic types)
80+
PayloadType uint8 `json:"payloadType"`
81+
82+
// Channels specifies the number of audio channels (e.g., 2 for stereo, 0 for video)
83+
Channels uint16 `json:"channels"`
84+
} `json:"params"`
85+
86+
// Type is the codec type as a string ("video" or "audio")
87+
Type string `json:"type"`
88+
}
89+
90+
// Codec represents a fully parsed codec configuration ready for use with WebRTC API.
91+
type Codec struct {
92+
// Params contains the WebRTC codec parameters
93+
Params webrtc.RTPCodecParameters `json:"params"`
94+
95+
// Type indicates whether this is a video or audio codec
96+
Type webrtc.RTPCodecType `json:"type"`
97+
}
98+
99+
// RawServerConfig is the intermediate structure used when parsing the JSON configuration file.
100+
// It uses string types for fields that need validation or conversion (like IP networks).
101+
type RawServerConfig struct {
102+
PlayerCredential *string `json:"adminCredential"`
103+
Participants []string `json:"participants"`
104+
AdminsRawNetworks []string `json:"adminsNetworks"`
105+
PeerConnectionConfig api.PeerConnectionConfig `json:"peerConnectionConfig"`
106+
PublicIP string `json:"publicIp"`
107+
GrabberPingInterval int `json:"grabberPingInterval"`
108+
ServerPort int `json:"serverPort"`
109+
ServerTLSCrtFile *string `json:"serverTLSCrtFile"`
110+
ServerTLSKeyFile *string `json:"serverTLSKeyFile"`
111+
Codecs []RawCodec `json:"codecs"`
112+
WebcamTrackCount int `json:"webcamTrackCount"`
113+
RecordTimeout uint `json:"recordTimeout"`
114+
RecordStorageDir string `json:"RecordStorageDirectory"`
115+
}
116+
117+
// LoadServerConfig reads and parses the server configuration from conf/config.json.
118+
// It performs validation and applies default values for optional settings.
119+
//
120+
// The function:
121+
// 1. Opens and reads conf/config.json
122+
// 2. Parses JSON into RawServerConfig
123+
// 3. Applies defaults: ServerPort (8000), WebcamTrackCount (2)
124+
// 4. Validates and parses AdminsRawNetworks into CIDR prefixes
125+
// 5. Converts RawCodec structures to WebRTC Codec types
126+
//
127+
// Returns an error if:
128+
// - Configuration file cannot be opened or read
129+
// - JSON is malformed
130+
// - IP network CIDR notation is invalid
131+
//
132+
// Example configuration:
133+
//
134+
// {
135+
// "adminCredential": "secretpassword",
136+
// "participants": ["office-camera", "conference-room"],
137+
// "adminsNetworks": ["192.168.1.0/24", "10.0.0.0/8"],
138+
// "peerConnectionConfig": {
139+
// "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]
140+
// },
141+
// "grabberPingInterval": 5,
142+
// "serverPort": 8443,
143+
// "serverTLSCrtFile": "/etc/ssl/cert.pem",
144+
// "serverTLSKeyFile": "/etc/ssl/key.pem",
145+
// "codecs": [
146+
// {
147+
// "params": {
148+
// "mimeType": "video/VP8",
149+
// "clockRate": 90000,
150+
// "payloadType": 96,
151+
// "channels": 0
152+
// },
153+
// "type": "video"
154+
// }
155+
// ],
156+
// "webcamTrackCount": 2
157+
// }
158+
func LoadServerConfig() (ServerConfig, error) {
159+
var rawConfig RawServerConfig
160+
161+
configFile, err := os.Open("conf/config.json")
162+
163+
if err != nil {
164+
return ServerConfig{}, fmt.Errorf("can not open config file, error - %w", err)
165+
}
166+
167+
defer func() { _ = configFile.Close() }()
168+
169+
err = json.NewDecoder(bufio.NewReader(configFile)).Decode(&rawConfig)
170+
171+
if err != nil {
172+
return ServerConfig{}, fmt.Errorf("can not decode config file to json - %w", err)
173+
}
174+
175+
if rawConfig.ServerPort == 0 {
176+
rawConfig.ServerPort = 8000
177+
}
178+
179+
if rawConfig.WebcamTrackCount == 0 {
180+
rawConfig.WebcamTrackCount = 2
181+
}
182+
183+
adminsNetworks, err := parseAdminsNetworks(rawConfig.AdminsRawNetworks)
184+
185+
if err != nil {
186+
return ServerConfig{}, fmt.Errorf("can not parse admins networks, error - %v", err)
187+
}
188+
189+
if rawConfig.RecordTimeout <= 0 {
190+
rawConfig.RecordTimeout = 180000
191+
}
192+
if rawConfig.RecordStorageDir != "" {
193+
err := os.MkdirAll(rawConfig.RecordStorageDir, os.ModePerm)
194+
if err != nil {
195+
return ServerConfig{}, fmt.Errorf("can create record directory, error - %v", err)
196+
}
197+
}
198+
199+
return ServerConfig{
200+
PlayerCredential: rawConfig.PlayerCredential,
201+
Participants: rawConfig.Participants,
202+
AdminsRawNetworks: adminsNetworks,
203+
PeerConnectionConfig: rawConfig.PeerConnectionConfig,
204+
PublicIP: rawConfig.PublicIP,
205+
GrabberPingInterval: rawConfig.GrabberPingInterval,
206+
ServerPort: rawConfig.ServerPort,
207+
ServerTLSCrtFile: rawConfig.ServerTLSCrtFile,
208+
ServerTLSKeyFile: rawConfig.ServerTLSKeyFile,
209+
Codecs: parseCodecs(rawConfig.Codecs),
210+
WebcamTrackCount: rawConfig.WebcamTrackCount,
211+
RecordTimeout: rawConfig.RecordTimeout,
212+
RecordStorageDir: rawConfig.RecordStorageDir,
213+
}, nil
214+
}
215+
216+
// parseCodecs converts raw codec configurations from JSON into WebRTC codec types.
217+
// It transforms the intermediate RawCodec representation into properly typed
218+
// webrtc.RTPCodecParameters and webrtc.RTPCodecType structures.
219+
//
220+
// The conversion handles:
221+
// - MIME type string to RTPCodecCapability
222+
// - Clock rate (Hz) for timing
223+
// - Channels (for audio codecs)
224+
// - Payload type for RTP packets
225+
// - Codec type string ("video"/"audio") to RTPCodecType enum
226+
func parseCodecs(rawCodecs []RawCodec) []Codec {
227+
result := make([]Codec, 0, len(rawCodecs))
228+
229+
for _, rawCodec := range rawCodecs {
230+
params := webrtc.RTPCodecParameters{
231+
RTPCodecCapability: webrtc.RTPCodecCapability{
232+
MimeType: rawCodec.Params.MimeType,
233+
ClockRate: rawCodec.Params.ClockRate,
234+
Channels: rawCodec.Params.Channels,
235+
},
236+
PayloadType: webrtc.PayloadType(rawCodec.Params.PayloadType),
237+
}
238+
239+
result = append(result, Codec{Params: params, Type: webrtc.NewRTPCodecType(rawCodec.Type)})
240+
}
241+
242+
return result
243+
}
244+
245+
// parseAdminsNetworks converts string CIDR notations into validated netip.Prefix objects.
246+
// This allows efficient IP address range checking for admin access control.
247+
//
248+
// Input strings must be in CIDR notation: "192.168.1.0/24", "10.0.0.0/8", etc.
249+
//
250+
// Returns an error if any network string is not valid CIDR notation.
251+
//
252+
// Example inputs:
253+
// - "192.168.1.0/24" - local network
254+
// - "10.0.0.0/8" - private network
255+
// - "2001:db8::/32" - IPv6 network
256+
func parseAdminsNetworks(rawNetworks []string) ([]netip.Prefix, error) {
257+
result := make([]netip.Prefix, 0, len(rawNetworks))
258+
259+
for _, r := range rawNetworks {
260+
network, err := netip.ParsePrefix(r)
261+
262+
if err != nil {
263+
return nil, err
264+
}
265+
266+
result = append(result, network)
267+
}
268+
269+
return result, nil
270+
}

0 commit comments

Comments
 (0)