feat(streaming): add local TURN relay support

This commit is contained in:
2026-04-13 13:15:00 +01:00
parent 531fd87197
commit f00c4edc5c
3 changed files with 67 additions and 1 deletions

5
WebApp/.env.example Normal file
View File

@@ -0,0 +1,5 @@
VITE_BACKEND_URL=http://localhost:3000
VITE_STUN_URLS=stun:stun.l.google.com:19302
VITE_TURN_URLS=turn:localhost:3478?transport=udp,turn:localhost:3478?transport=tcp
VITE_TURN_USERNAME=securecam
VITE_TURN_CREDENTIAL=securecamturn

View File

@@ -76,8 +76,36 @@ const SOCKET_HEARTBEAT_INTERVAL_MS = 10_000;
const MAX_STREAM_DIAGNOSTIC_SESSIONS = 12; const MAX_STREAM_DIAGNOSTIC_SESSIONS = 12;
const MAX_STREAM_DIAGNOSTIC_ENTRIES = 24; const MAX_STREAM_DIAGNOSTIC_ENTRIES = 24;
const parseRtcUrls = (value = '') =>
value
.split(',')
.map((item) => item.trim())
.filter(Boolean);
const buildIceServers = () => {
const stunUrls = parseRtcUrls(import.meta.env.VITE_STUN_URLS ?? 'stun:stun.l.google.com:19302');
const turnUrls = parseRtcUrls(import.meta.env.VITE_TURN_URLS ?? '');
const turnUsername = (import.meta.env.VITE_TURN_USERNAME ?? '').trim();
const turnCredential = (import.meta.env.VITE_TURN_CREDENTIAL ?? '').trim();
const iceServers = [];
if (stunUrls.length > 0) {
iceServers.push({ urls: stunUrls });
}
if (turnUrls.length > 0) {
iceServers.push({
urls: turnUrls,
username: turnUsername,
credential: turnCredential
});
}
return iceServers.length > 0 ? iceServers : [{ urls: 'stun:stun.l.google.com:19302' }];
};
const rtcConfig = { const rtcConfig = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] iceServers: buildIceServers()
}; };
let initialized = false; let initialized = false;

View File

@@ -1,4 +1,27 @@
services: services:
coturn:
image: coturn/coturn:4.6.3
# Local development default. If camera/client are on other devices, replace 127.0.0.1
# in TURN_URLS/VITE_TURN_URLS and --external-ip with the host machine's LAN IP.
command:
- -n
- --log-file=stdout
- --realm=securecam.local
- --fingerprint
- --lt-cred-mech
- --user=securecam:securecamturn
- --listening-port=3478
- --listening-ip=0.0.0.0
- --relay-ip=0.0.0.0
- --external-ip=127.0.0.1
- --min-port=49160
- --max-port=49200
ports:
- "3478:3478"
- "3478:3478/udp"
- "49160-49200:49160-49200/udp"
restart: unless-stopped
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
environment: environment:
@@ -54,6 +77,9 @@ services:
MEDIA_MAX_SUBSCRIBERS_PER_ROOM: 12 MEDIA_MAX_SUBSCRIBERS_PER_ROOM: 12
ADMIN_USERNAME: admin ADMIN_USERNAME: admin
ADMIN_PASSWORD: strong-password ADMIN_PASSWORD: strong-password
TURN_URLS: turn:localhost:3478?transport=udp,turn:localhost:3478?transport=tcp
TURN_USERNAME: securecam
TURN_CREDENTIAL: securecamturn
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
@@ -65,6 +91,8 @@ services:
condition: service_healthy condition: service_healthy
minio: minio:
condition: service_started condition: service_started
coturn:
condition: service_started
restart: unless-stopped restart: unless-stopped
webapp: webapp:
@@ -75,6 +103,10 @@ services:
environment: environment:
BACKEND_URL: http://backend:3000 BACKEND_URL: http://backend:3000
VITE_BACKEND_URL: http://localhost:3000 VITE_BACKEND_URL: http://localhost:3000
VITE_STUN_URLS: stun:stun.l.google.com:19302
VITE_TURN_URLS: turn:localhost:3478?transport=udp,turn:localhost:3478?transport=tcp
VITE_TURN_USERNAME: securecam
VITE_TURN_CREDENTIAL: securecamturn
ports: ports:
- "5173:5173" - "5173:5173"
volumes: volumes:
@@ -82,6 +114,7 @@ services:
- webapp_node_modules:/app/node_modules - webapp_node_modules:/app/node_modules
depends_on: depends_on:
- backend - backend
- coturn
restart: unless-stopped restart: unless-stopped
volumes: volumes: