feat(call): integrate LiveKit for voice and video calling
Replace the manual WebRTC signaling implementation with LiveKit SFU. This includes: - Adding LiveKit service, handler, and configuration. - Updating Docker Compose to include LiveKit server, Redis, and PostgreSQL. - Refactoring `CallService` and `WSHandler` to support LiveKit room readiness instead of raw SDP/ICE relaying. - Adding new API endpoints for LiveKit token generation and webhooks. - Removing deprecated WebRTC configuration and manual signaling DTOs.
This commit is contained in:
@@ -212,7 +212,7 @@ func (h *WSHandler) readPump(conn *websocket.Conn, client *ws.Client) {
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
conn.SetReadLimit(256 * 1024) // 256KB (SDP/ICE candidates can be large)
|
||||
conn.SetReadLimit(256 * 1024) // 256KB
|
||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
conn.SetPongHandler(func(string) error {
|
||||
zap.L().Debug("WebSocket pong received",
|
||||
@@ -383,10 +383,8 @@ func (h *WSHandler) handleMessage(client *ws.Client, msg *ws.Message) {
|
||||
h.handleCallReject(ctx, client, msg.Payload)
|
||||
case "call_busy":
|
||||
h.handleCallBusy(ctx, client, msg.Payload)
|
||||
case "call_sdp":
|
||||
h.handleCallSDP(ctx, client, msg.Payload)
|
||||
case "call_ice":
|
||||
h.handleCallICE(ctx, client, msg.Payload)
|
||||
case "call_ready":
|
||||
h.handleCallReady(ctx, client, msg.Payload)
|
||||
case "call_end":
|
||||
h.handleCallEnd(ctx, client, msg.Payload)
|
||||
case "call_mute":
|
||||
@@ -717,43 +715,18 @@ func (h *WSHandler) handleCallBusy(ctx context.Context, client *ws.Client, paylo
|
||||
}
|
||||
}
|
||||
|
||||
// handleCallSDP 转发 SDP
|
||||
func (h *WSHandler) handleCallSDP(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
// handleCallReady 处理通话就绪(LiveKit 房间连接成功)
|
||||
func (h *WSHandler) handleCallReady(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
var req struct {
|
||||
CallID string `json:"call_id"`
|
||||
SDPType string `json:"sdp_type"`
|
||||
SDP json.RawMessage `json:"sdp"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &req); err != nil || req.CallID == "" || req.SDPType == "" {
|
||||
h.publisher.SendError(client, "invalid_params", "call_id and sdp_type are required")
|
||||
return
|
||||
}
|
||||
|
||||
signalPayload, _ := json.Marshal(map[string]any{
|
||||
"sdp_type": req.SDPType,
|
||||
"sdp": json.RawMessage(req.SDP),
|
||||
})
|
||||
if err := h.callService.RelaySignal(ctx, req.CallID, client.UserID, "call_sdp", signalPayload); err != nil {
|
||||
h.publisher.SendError(client, "call_sdp_failed", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// handleCallICE 转发 ICE candidate
|
||||
func (h *WSHandler) handleCallICE(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
var req struct {
|
||||
CallID string `json:"call_id"`
|
||||
Candidate json.RawMessage `json:"candidate"`
|
||||
CallID string `json:"call_id"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &req); err != nil || req.CallID == "" {
|
||||
h.publisher.SendError(client, "invalid_params", "call_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
signalPayload, _ := json.Marshal(map[string]any{
|
||||
"candidate": json.RawMessage(req.Candidate),
|
||||
})
|
||||
if err := h.callService.RelaySignal(ctx, req.CallID, client.UserID, "call_ice", signalPayload); err != nil {
|
||||
h.publisher.SendError(client, "call_ice_failed", err.Error())
|
||||
if err := h.callService.Ready(ctx, req.CallID, client.UserID); err != nil {
|
||||
h.publisher.SendError(client, "call_ready_failed", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user