feat(call): implement group calling and enhance call management
Introduces group calling capabilities, including group invites and participant lifecycle management. Enhances the existing call system with support for media types (voice/video) and improved participant tracking. - Add `GroupInvite` and `ParticipantJoin/Leave` to `CallService`. - Implement WebSocket handlers for `call_group_invite` and `call_participant_join`. - Update `CallSession` model to support group IDs, media types, and tracking who ended the call. - Integrate `GroupService` into `CallService` via dependency injection. - Add error constants for group call limitations and participant capacity. - Update LiveKit webhook handling to process participant leave events. Refactor grade synchronization to remove semester-based filtering in favor of a more flexible user-based approach and improve GPA summary updates.
This commit is contained in:
@@ -377,6 +377,8 @@ func (h *WSHandler) handleMessage(client *ws.Client, msg *ws.Message) {
|
||||
// 通话信令
|
||||
case "call_invite":
|
||||
h.handleCallInvite(ctx, client, msg.Payload)
|
||||
case "call_group_invite":
|
||||
h.handleCallGroupInvite(ctx, client, msg.Payload)
|
||||
case "call_answer":
|
||||
h.handleCallAnswer(ctx, client, msg.Payload)
|
||||
case "call_reject":
|
||||
@@ -389,6 +391,8 @@ func (h *WSHandler) handleMessage(client *ws.Client, msg *ws.Message) {
|
||||
h.handleCallEnd(ctx, client, msg.Payload)
|
||||
case "call_mute":
|
||||
h.handleCallMute(ctx, client, msg.Payload)
|
||||
case "call_participant_join":
|
||||
h.handleCallParticipantJoin(ctx, client, msg.Payload)
|
||||
default:
|
||||
h.publisher.SendError(client, "unknown_type", fmt.Sprintf("unknown message type: %s", msg.Type))
|
||||
}
|
||||
@@ -648,6 +652,61 @@ func (h *WSHandler) handleCallInvite(ctx context.Context, client *ws.Client, pay
|
||||
}
|
||||
}
|
||||
|
||||
// handleCallGroupInvite 处理群组通话邀请
|
||||
func (h *WSHandler) handleCallGroupInvite(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
if !h.isVerified(ctx, client) {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
GroupID string `json:"group_id"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
MediaType string `json:"call_type"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &req); err != nil {
|
||||
h.publisher.SendError(client, "parse_error", "invalid call_group_invite format")
|
||||
return
|
||||
}
|
||||
if req.GroupID == "" || req.ConversationID == "" {
|
||||
h.publisher.SendError(client, "invalid_params", "group_id and conversation_id are required")
|
||||
return
|
||||
}
|
||||
|
||||
mediaType := req.MediaType
|
||||
if mediaType != "video" {
|
||||
mediaType = "voice"
|
||||
}
|
||||
|
||||
call, err := h.callService.GroupInvite(ctx, client.UserID, req.GroupID, req.ConversationID, mediaType)
|
||||
if err != nil {
|
||||
zap.L().Warn("Failed to invite group call",
|
||||
zap.String("caller_id", client.UserID),
|
||||
zap.String("group_id", req.GroupID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.publisher.SendError(client, "call_group_invite_failed", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := ws.ResponseMessage{
|
||||
EventID: h.publisher.NextID(),
|
||||
Type: "call_invited",
|
||||
TS: time.Now().UnixMilli(),
|
||||
Payload: map[string]any{
|
||||
"call_id": call.ID,
|
||||
"conversation_id": req.ConversationID,
|
||||
"group_id": req.GroupID,
|
||||
"call_type": "group",
|
||||
"lifetime": service.CallLifetimeMs,
|
||||
},
|
||||
}
|
||||
data, _ := json.Marshal(resp)
|
||||
select {
|
||||
case <-client.Quit:
|
||||
case client.Send <- data:
|
||||
}
|
||||
}
|
||||
|
||||
// handleCallAnswer 处理接听
|
||||
func (h *WSHandler) handleCallAnswer(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
var req struct {
|
||||
@@ -778,3 +837,18 @@ func (h *WSHandler) handleCallMute(ctx context.Context, client *ws.Client, paylo
|
||||
h.publisher.SendError(client, "call_mute_failed", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// handleCallParticipantJoin 处理群组通话参与者加入
|
||||
func (h *WSHandler) handleCallParticipantJoin(ctx context.Context, client *ws.Client, payload json.RawMessage) {
|
||||
var req struct {
|
||||
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
|
||||
}
|
||||
|
||||
if err := h.callService.ParticipantJoin(ctx, req.CallID, client.UserID); err != nil {
|
||||
h.publisher.SendError(client, "call_participant_join_failed", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user