539 lines
19 KiB
Go
539 lines
19 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"time"
|
||
|
||
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||
roomv1 "hyapp.local/api/proto/room/v1"
|
||
"hyapp/pkg/tencentim"
|
||
"hyapp/pkg/xerr"
|
||
"hyapp/services/room-service/internal/room/command"
|
||
"hyapp/services/room-service/internal/room/outbox"
|
||
"hyapp/services/room-service/internal/room/rank"
|
||
"hyapp/services/room-service/internal/room/state"
|
||
)
|
||
|
||
// MicUp 把目标用户放到指定麦位。
|
||
func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.MicUpResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
// MicUp 以 actor 作为上麦目标,管理员代操作走 MicDown/ChangeMicSeat 等显式 target 命令。
|
||
cmd := command.MicUp{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
SeatNo: req.GetSeatNo(),
|
||
}
|
||
cmd.MicSessionID = micSessionIDForCommand(cmd.RoomID(), cmd.ID())
|
||
cmd.PublishDeadlineMS = s.clock.Now().Add(s.micPublishTimeout).UnixMilli()
|
||
|
||
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||
if err := requireActiveRoom(current); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if _, exists := current.OnlineUsers[cmd.ActorUserID()]; !exists {
|
||
// 不在 room-service presence 的用户不能上麦。
|
||
return mutationResult{}, nil, xerr.New(xerr.NotFound, "user not in room")
|
||
}
|
||
|
||
if _, exists := current.SeatByUser(cmd.ActorUserID()); exists {
|
||
// 一个用户同一时间只能占一个麦位。
|
||
return mutationResult{}, nil, xerr.New(xerr.Conflict, "user already on seat")
|
||
}
|
||
|
||
index := current.SeatIndex(cmd.SeatNo)
|
||
if index < 0 {
|
||
// 麦位编号必须来自房间初始化的 MicSeats。
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "seat does not exist")
|
||
}
|
||
|
||
if current.MicSeats[index].Locked {
|
||
// 锁定麦位不能被普通上麦命令占用。
|
||
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "seat is locked")
|
||
}
|
||
|
||
if current.MicSeats[index].UserID != 0 {
|
||
// 非空麦位不能被覆盖,避免两个用户同时占同一麦位。
|
||
return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied")
|
||
}
|
||
|
||
// MicUp 只代表业务占麦成功;RTC 发流必须再通过 ConfirmMicPublishing 确认。
|
||
current.Version++
|
||
current.MicSeats[index].UserID = cmd.ActorUserID()
|
||
current.MicSeats[index].PublishState = state.MicPublishPending
|
||
current.MicSeats[index].MicSessionID = cmd.MicSessionID
|
||
current.MicSeats[index].PublishDeadlineMS = cmd.PublishDeadlineMS
|
||
current.MicSeats[index].MicSessionRoomVersion = current.Version
|
||
// 新 session 尚未接受任何 RTC/客户端事件;不能用服务端 MicUp 时间初始化,
|
||
// 否则客户端设备时间略慢时,合法的首次确认会被误判为旧事件。
|
||
current.MicSeats[index].LastPublishEventTimeMS = 0
|
||
|
||
// RoomMicChanged 是房间外消费者和腾讯云 IM 共同使用的麦位事件源。
|
||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.ActorUserID(),
|
||
FromSeat: 0,
|
||
ToSeat: cmd.SeatNo,
|
||
Action: "up",
|
||
MicSessionId: cmd.MicSessionID,
|
||
PublishState: state.MicPublishPending,
|
||
PublishDeadlineMs: cmd.PublishDeadlineMS,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
return mutationResult{
|
||
snapshot: current.ToProto(),
|
||
seatNo: cmd.SeatNo,
|
||
micSessionID: cmd.MicSessionID,
|
||
publishDeadlineMS: cmd.PublishDeadlineMS,
|
||
syncEvent: &tencentim.RoomEvent{
|
||
EventID: micEvent.EventID,
|
||
RoomID: current.RoomID,
|
||
EventType: "room_mic_up",
|
||
ActorUserID: cmd.ActorUserID(),
|
||
TargetUserID: cmd.ActorUserID(),
|
||
SeatNo: cmd.SeatNo,
|
||
RoomVersion: current.Version,
|
||
Attributes: map[string]string{
|
||
"action": "up",
|
||
"publish_state": state.MicPublishPending,
|
||
"mic_session_id": cmd.MicSessionID,
|
||
"publish_deadline_ms": int64String(cmd.PublishDeadlineMS),
|
||
},
|
||
},
|
||
}, []outbox.Record{micEvent}, nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if result.micSessionID == "" {
|
||
// 幂等重试不会重新执行 mutate,需要从当前快照补回首次 MicUp 生成的会话信息。
|
||
if seat := protoSeatByUser(result.snapshot, cmd.ActorUserID()); seat != nil {
|
||
result.seatNo = seat.GetSeatNo()
|
||
result.micSessionID = seat.GetMicSessionId()
|
||
result.publishDeadlineMS = seat.GetPublishDeadlineMs()
|
||
}
|
||
}
|
||
|
||
return &roomv1.MicUpResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
SeatNo: result.seatNo,
|
||
Room: result.snapshot,
|
||
MicSessionId: result.micSessionID,
|
||
PublishDeadlineMs: result.publishDeadlineMS,
|
||
}, nil
|
||
}
|
||
|
||
// MicDown 把目标用户从当前麦位移下。
|
||
func (s *Service) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roomv1.MicDownResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
// MicDown 支持操作者和目标用户不同,权限策略后续可在命令入口扩展。
|
||
cmd := command.MicDown{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
TargetUserID: req.GetTargetUserId(),
|
||
Reason: req.GetReason(),
|
||
}
|
||
if cmd.TargetUserID == 0 {
|
||
// target 缺省时按主动下麦处理;管理别人下麦必须显式传 target_user_id。
|
||
cmd.TargetUserID = cmd.ActorUserID()
|
||
}
|
||
|
||
result, err := s.micDown(ctx, cmd)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &roomv1.MicDownResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
SeatNo: result.seatNo,
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
func (s *Service) micDown(ctx context.Context, cmd command.MicDown) (mutationResult, error) {
|
||
return s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||
if err := requireActiveRoom(current); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if cmd.TargetUserID <= 0 {
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
|
||
}
|
||
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if err := canSelfOrManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
seat, exists := current.SeatByUser(cmd.TargetUserID)
|
||
if !exists {
|
||
// 目标用户不在麦上时不能生成下麦事件。
|
||
return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not on seat")
|
||
}
|
||
|
||
if cmd.MicSessionID != "" && seat.MicSessionID != cmd.MicSessionID {
|
||
// 超时任务携带旧 mic_session_id 时只能成为 no-op,不能清掉用户新的上麦会话。
|
||
return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil
|
||
}
|
||
|
||
// SeatByUser 返回的是值拷贝,真正修改需要再定位切片下标。
|
||
index := current.SeatIndex(seat.SeatNo)
|
||
micSessionID := current.MicSeats[index].MicSessionID
|
||
current.ClearMicSession(index)
|
||
current.Version++
|
||
|
||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
FromSeat: seat.SeatNo,
|
||
ToSeat: 0,
|
||
Action: "down",
|
||
MicSessionId: micSessionID,
|
||
PublishState: state.MicPublishIdle,
|
||
Reason: cmd.Reason,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
return mutationResult{
|
||
snapshot: current.ToProto(),
|
||
seatNo: seat.SeatNo,
|
||
syncEvent: &tencentim.RoomEvent{
|
||
EventID: micEvent.EventID,
|
||
RoomID: current.RoomID,
|
||
EventType: "room_mic_down",
|
||
ActorUserID: cmd.ActorUserID(),
|
||
TargetUserID: cmd.TargetUserID,
|
||
SeatNo: seat.SeatNo,
|
||
RoomVersion: current.Version,
|
||
Attributes: map[string]string{
|
||
"action": "down",
|
||
"publish_state": state.MicPublishIdle,
|
||
"mic_session_id": micSessionID,
|
||
"reason": cmd.Reason,
|
||
},
|
||
},
|
||
}, []outbox.Record{micEvent}, nil
|
||
})
|
||
}
|
||
|
||
// ChangeMicSeat 调整指定用户所在麦位。
|
||
func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRequest) (*roomv1.ChangeMicSeatResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
// ChangeMicSeat 是原子换位:从原麦位清空和写入新麦位必须同版本完成。
|
||
cmd := command.ChangeMicSeat{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
TargetUserID: req.GetTargetUserId(),
|
||
SeatNo: req.GetSeatNo(),
|
||
}
|
||
|
||
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||
if err := requireActiveRoom(current); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if cmd.TargetUserID <= 0 {
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
|
||
}
|
||
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if err := canSelfOrManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
fromSeat, exists := current.SeatByUser(cmd.TargetUserID)
|
||
if !exists {
|
||
// 未上麦用户没有可换出的源麦位。
|
||
return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not on seat")
|
||
}
|
||
|
||
toIndex := current.SeatIndex(cmd.SeatNo)
|
||
if toIndex < 0 {
|
||
// 目标麦位必须存在。
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "seat does not exist")
|
||
}
|
||
|
||
if current.MicSeats[toIndex].Locked {
|
||
// 锁定麦位不能换入。
|
||
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "seat is locked")
|
||
}
|
||
|
||
if current.MicSeats[toIndex].UserID != 0 {
|
||
// 目标麦位被占用时拒绝换位,避免覆盖另一个用户。
|
||
return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied")
|
||
}
|
||
|
||
// 清空源麦位和占用目标麦位在同一 Cell 任务内完成,客户端不会看到中间态。
|
||
fromIndex := current.SeatIndex(fromSeat.SeatNo)
|
||
movedSeat := current.MicSeats[fromIndex]
|
||
current.MicSeats[fromIndex].UserID = 0
|
||
current.MicSeats[fromIndex].PublishState = ""
|
||
current.MicSeats[fromIndex].MicSessionID = ""
|
||
current.MicSeats[fromIndex].PublishDeadlineMS = 0
|
||
current.MicSeats[fromIndex].MicSessionRoomVersion = 0
|
||
current.MicSeats[fromIndex].LastPublishEventTimeMS = 0
|
||
current.MicSeats[toIndex].UserID = cmd.TargetUserID
|
||
current.MicSeats[toIndex].PublishState = movedSeat.PublishState
|
||
current.MicSeats[toIndex].MicSessionID = movedSeat.MicSessionID
|
||
current.MicSeats[toIndex].PublishDeadlineMS = movedSeat.PublishDeadlineMS
|
||
current.MicSeats[toIndex].MicSessionRoomVersion = movedSeat.MicSessionRoomVersion
|
||
current.MicSeats[toIndex].LastPublishEventTimeMS = movedSeat.LastPublishEventTimeMS
|
||
current.MicSeats[toIndex].MicMuted = movedSeat.MicMuted
|
||
current.Version++
|
||
|
||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
FromSeat: fromSeat.SeatNo,
|
||
ToSeat: cmd.SeatNo,
|
||
Action: "change",
|
||
MicSessionId: movedSeat.MicSessionID,
|
||
PublishState: movedSeat.PublishState,
|
||
PublishDeadlineMs: movedSeat.PublishDeadlineMS,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
return mutationResult{
|
||
snapshot: current.ToProto(),
|
||
syncEvent: &tencentim.RoomEvent{
|
||
EventID: micEvent.EventID,
|
||
RoomID: current.RoomID,
|
||
EventType: "room_mic_change",
|
||
ActorUserID: cmd.ActorUserID(),
|
||
TargetUserID: cmd.TargetUserID,
|
||
SeatNo: cmd.SeatNo,
|
||
RoomVersion: current.Version,
|
||
Attributes: map[string]string{
|
||
"action": "change",
|
||
"publish_state": movedSeat.PublishState,
|
||
"mic_session_id": movedSeat.MicSessionID,
|
||
"publish_deadline_ms": int64String(movedSeat.PublishDeadlineMS),
|
||
},
|
||
},
|
||
}, []outbox.Record{micEvent}, nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &roomv1.ChangeMicSeatResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
// ConfirmMicPublishing 把 pending_publish 麦位推进到 publishing。
|
||
func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
cmd := command.ConfirmMicPublishing{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
TargetUserID: req.GetTargetUserId(),
|
||
MicSessionID: req.GetMicSessionId(),
|
||
RoomVersion: req.GetRoomVersion(),
|
||
EventTimeMS: req.GetEventTimeMs(),
|
||
Source: req.GetSource(),
|
||
}
|
||
if cmd.TargetUserID == 0 {
|
||
cmd.TargetUserID = cmd.ActorUserID()
|
||
}
|
||
|
||
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||
if err := requireActiveRoom(current); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if cmd.TargetUserID <= 0 || cmd.MicSessionID == "" || cmd.RoomVersion <= 0 || cmd.EventTimeMS <= 0 {
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "mic publish confirmation is incomplete")
|
||
}
|
||
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if cmd.ActorUserID() != cmd.TargetUserID {
|
||
if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
}
|
||
|
||
seat, exists := current.SeatByUser(cmd.TargetUserID)
|
||
if !exists {
|
||
// RTC webhook 可能晚于用户下麦到达;旧事件只忽略,不返回错误清掉新状态。
|
||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||
}
|
||
if seat.MicSessionID != cmd.MicSessionID ||
|
||
cmd.RoomVersion < seat.MicSessionRoomVersion ||
|
||
cmd.EventTimeMS <= seat.LastPublishEventTimeMS ||
|
||
cmd.EventTimeMS > seat.PublishDeadlineMS {
|
||
return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil
|
||
}
|
||
if seat.PublishState == state.MicPublishPublishing {
|
||
return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil
|
||
}
|
||
if seat.PublishState != state.MicPublishPending {
|
||
return mutationResult{}, nil, xerr.New(xerr.Conflict, "mic is not waiting for publish confirmation")
|
||
}
|
||
|
||
index := current.SeatIndex(seat.SeatNo)
|
||
current.MicSeats[index].PublishState = state.MicPublishPublishing
|
||
current.MicSeats[index].LastPublishEventTimeMS = cmd.EventTimeMS
|
||
current.Version++
|
||
|
||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
FromSeat: seat.SeatNo,
|
||
ToSeat: seat.SeatNo,
|
||
Action: "publish_confirmed",
|
||
MicSessionId: cmd.MicSessionID,
|
||
PublishState: state.MicPublishPublishing,
|
||
PublishDeadlineMs: seat.PublishDeadlineMS,
|
||
PublishEventTimeMs: cmd.EventTimeMS,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
return mutationResult{
|
||
snapshot: current.ToProto(),
|
||
seatNo: seat.SeatNo,
|
||
micSessionID: cmd.MicSessionID,
|
||
publishDeadlineMS: seat.PublishDeadlineMS,
|
||
syncEvent: &tencentim.RoomEvent{
|
||
EventID: micEvent.EventID,
|
||
RoomID: current.RoomID,
|
||
EventType: "room_mic_publish_confirmed",
|
||
ActorUserID: cmd.ActorUserID(),
|
||
TargetUserID: cmd.TargetUserID,
|
||
SeatNo: seat.SeatNo,
|
||
RoomVersion: current.Version,
|
||
Attributes: map[string]string{
|
||
"action": "publish_confirmed",
|
||
"publish_state": state.MicPublishPublishing,
|
||
"mic_session_id": cmd.MicSessionID,
|
||
"publish_event_time_ms": int64String(cmd.EventTimeMS),
|
||
"source": cmd.Source,
|
||
},
|
||
},
|
||
}, []outbox.Record{micEvent}, nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &roomv1.ConfirmMicPublishingResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
SeatNo: result.seatNo,
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
// SetMicMute 修改麦位上的服务端可见静音态。
|
||
func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
cmd := command.SetMicMute{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
TargetUserID: req.GetTargetUserId(),
|
||
Muted: req.GetMuted(),
|
||
}
|
||
if cmd.TargetUserID == 0 {
|
||
cmd.TargetUserID = cmd.ActorUserID()
|
||
}
|
||
|
||
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||
if err := requireActiveRoom(current); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if cmd.TargetUserID <= 0 {
|
||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
|
||
}
|
||
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
if err := canSelfOrManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
seat, exists := current.SeatByUser(cmd.TargetUserID)
|
||
if !exists {
|
||
return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not on seat")
|
||
}
|
||
if seat.MicMuted == cmd.Muted {
|
||
return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil
|
||
}
|
||
|
||
index := current.SeatIndex(seat.SeatNo)
|
||
current.MicSeats[index].MicMuted = cmd.Muted
|
||
current.Version++
|
||
|
||
action := "mic_unmute"
|
||
if cmd.Muted {
|
||
action = "mic_mute"
|
||
}
|
||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
FromSeat: seat.SeatNo,
|
||
ToSeat: seat.SeatNo,
|
||
Action: action,
|
||
MicSessionId: seat.MicSessionID,
|
||
PublishState: seat.PublishState,
|
||
PublishDeadlineMs: seat.PublishDeadlineMS,
|
||
MicMuted: cmd.Muted,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
|
||
return mutationResult{
|
||
snapshot: current.ToProto(),
|
||
seatNo: seat.SeatNo,
|
||
syncEvent: &tencentim.RoomEvent{
|
||
EventID: micEvent.EventID,
|
||
RoomID: current.RoomID,
|
||
EventType: "room_mic_mute_changed",
|
||
ActorUserID: cmd.ActorUserID(),
|
||
TargetUserID: cmd.TargetUserID,
|
||
SeatNo: seat.SeatNo,
|
||
RoomVersion: current.Version,
|
||
Attributes: map[string]string{
|
||
"action": action,
|
||
"mic_session_id": seat.MicSessionID,
|
||
"publish_state": seat.PublishState,
|
||
"mic_muted": boolString(cmd.Muted),
|
||
},
|
||
},
|
||
}, []outbox.Record{micEvent}, nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &roomv1.SetMicMuteResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
SeatNo: result.seatNo,
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
func micSessionIDForCommand(roomID string, commandID string) string {
|
||
sum := sha256.Sum256([]byte(roomID + "\x00" + commandID))
|
||
return "mic_" + hex.EncodeToString(sum[:16])
|
||
}
|
||
|
||
func protoSeatByUser(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatState {
|
||
if snapshot == nil {
|
||
return nil
|
||
}
|
||
|
||
for _, seat := range snapshot.GetMicSeats() {
|
||
if seat.GetUserId() == userID {
|
||
return seat
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|