feat(media): deploy Element Call fork + optimize LiveKit quality

- Deploy self-hosted Element Call at call.sunbeam.pt with SSO login
- LiveKit: VP9 > AV1 > H.264 codec preferences, Opus stereo
- LiveKit: congestion_control.allow_pause=false, larger NACK buffers
- LiveKit: resources bumped to 2Gi/4CPU for VP9 SVC
- Proxy: add call.* route, TLS cert SAN for call.sunbeam.pt
This commit is contained in:
2026-03-26 09:38:53 +00:00
parent a912331f97
commit 632099893a
5 changed files with 131 additions and 10 deletions

View File

@@ -348,6 +348,10 @@ data:
backend = "http://openbao.data.svc.cluster.local:8200"
auth_request = "http://hydra-public.ory.svc.cluster.local:4444/userinfo"
[[routes]]
host_prefix = "call"
backend = "http://element-call.media.svc.cluster.local:80"
# Bare domain (sunbeam.pt) — serves .well-known/matrix delegation only.
# The proxy splits on '.', so sunbeam.pt yields prefix "sunbeam".
[[routes]]

View File

@@ -0,0 +1,79 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: element-call-config
namespace: media
data:
config.json: |
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://messages.DOMAIN_SUFFIX",
"server_name": "DOMAIN_SUFFIX"
}
},
"livekit": {
"livekit_service_url": "https://livekit.DOMAIN_SUFFIX"
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: element-call
namespace: media
spec:
replicas: 1
selector:
matchLabels:
app: element-call
template:
metadata:
labels:
app: element-call
spec:
containers:
- name: element-call
image: src.sunbeam.pt/studio/element-call:latest
ports:
- containerPort: 8080
volumeMounts:
- name: config
mountPath: /app/config.json
subPath: config.json
readOnly: true
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
resources:
limits:
memory: 64Mi
cpu: 100m
requests:
memory: 32Mi
cpu: 25m
volumes:
- name: config
configMap:
name: element-call-config
---
apiVersion: v1
kind: Service
metadata:
name: element-call
namespace: media
spec:
selector:
app: element-call
ports:
- port: 80
targetPort: 8080

View File

@@ -8,6 +8,7 @@ resources:
- vault-secrets.yaml
- livekit-alertrules.yaml
- lk-jwt-service.yaml
- element-call.yaml
# livekit-servicemonitor.yaml disabled — LiveKit runs on hostNetwork and port 6789
# is not reachable from Prometheus due to host firewall. Open port 6789 on the host
# or add an iptables rule, then re-enable.

View File

@@ -4,16 +4,57 @@
# Reference: https://github.com/livekit/livekit-helm/blob/master/server-sample.yaml
livekit:
# LiveKit server config injected as config.yaml
port: 7880
log_level: info
prometheus_port: 6789
# ── Codec preferences (order matters — first match wins) ────────────
room:
enabled_codecs:
- mime: audio/opus
- mime: audio/red
# VP9: ~2x quality-per-bit over H.264, built-in SVC.
- mime: video/vp9
# AV1: best compression, limited HW decode support still.
- mime: video/av1
# H.264: broadest HW accel, fallback for constrained clients.
- mime: video/h264
# VP8: widest compatibility baseline (Element Call default).
- mime: video/vp8
# Receiver-side jitter buffer. Tight values for a small team.
playout_delay:
enabled: true
min: 100
max: 500
sync_streams: true
empty_timeout: 300
departure_timeout: 20
# ── RTC / transport ─────────────────────────────────────────────────
rtc:
port_range_start: 49152
port_range_end: 49252
tcp_port: 7881
use_external_ip: true
allow_tcp_fallback: true
# Request keyframes faster after quality drops.
pli_throttle:
low_quality: 300ms
mid_quality: 500ms
high_quality: 500ms
# Larger NACK retransmission buffers for loss recovery.
packet_buffer_size_video: 1000
packet_buffer_size_audio: 500
# Never pause video tracks due to congestion — we have the bandwidth.
congestion_control:
enabled: true
allow_pause: false
# ── TURN ────────────────────────────────────────────────────────────
turn:
enabled: true
domain: meet.DOMAIN_SUFFIX
@@ -24,11 +65,8 @@ livekit:
relay_range_end: 23333
redis:
# Valkey is protocol-compatible with Redis; LiveKit sees this as a Redis endpoint
address: valkey.data.svc.cluster.local:6379
# API keys — loaded from K8s Secret managed by VSO (secret/livekit in OpenBao).
# The keys.yaml field contains "devkey: <api-secret>" in YAML format.
key_file: keys.yaml
storeKeysInSecret:
@@ -39,16 +77,14 @@ storeKeysInSecret:
# chart template requires livekit.prometheus_port which conflicts with hostNetwork.
deployment:
# hostNetwork gives LiveKit direct access to the host network namespace,
# which is the only practical way to expose the 10k-port TURN relay range
# (13333-23333) without listing individual hostPorts in the pod spec.
hostNetwork: true
resources:
limits:
memory: 128Mi
memory: 2048Mi
cpu: 4000m
requests:
memory: 64Mi
cpu: 100m
memory: 512Mi
cpu: 500m
# Recreate strategy: hostPorts (TURN UDP relay range) block RollingUpdate —
# the new pod cannot schedule while the old one still holds the host ports.

View File

@@ -78,3 +78,4 @@ spec:
- search.DOMAIN_SUFFIX
- vault.DOMAIN_SUFFIX
- find.DOMAIN_SUFFIX
- call.DOMAIN_SUFFIX