feat: sync group call livestream support

This commit is contained in:
A 2026-07-06 14:31:19 +08:00
parent 56d995474c
commit f1a27996d3
37 changed files with 2219 additions and 83 deletions

View file

@ -45,7 +45,9 @@ type GroupCallStore struct {
inviteByMessage map[inviteMessageKey]domain.GroupCallInvite
chainBlocks map[chainKey][]domain.GroupCallChainBlock
overrides map[overrideKey]domain.GroupCallParticipantOverride
raiseHandSeq map[int64]int64 // callID → 单调举手序号
raiseHandSeq map[int64]int64 // callID → 单调举手序号
rtmpKeys map[int64]string // channelID → RTMP 推流密钥
scheduleSubs map[int64]map[int64]struct{} // callID → 订阅开播提醒的 userID
nextSyntheticID int64
}
@ -62,6 +64,8 @@ func NewGroupCallStore() *GroupCallStore {
chainBlocks: make(map[chainKey][]domain.GroupCallChainBlock),
overrides: make(map[overrideKey]domain.GroupCallParticipantOverride),
raiseHandSeq: make(map[int64]int64),
rtmpKeys: make(map[int64]string),
scheduleSubs: make(map[int64]map[int64]struct{}),
}
}
@ -176,12 +180,13 @@ func (s *GroupCallStore) JoinGroupCall(_ context.Context, req domain.JoinGroupCa
existing, rejoining := rows[req.UserID]
wasActive := rejoining && !existing.Left
p := domain.GroupCallParticipant{
CallID: req.CallID,
UserID: req.UserID,
SSRC: req.SSRC,
JoinDate: req.Now,
ActiveDate: req.Now,
LastCheckDate: req.Now,
CallID: req.CallID,
UserID: req.UserID,
JoinAsChannelID: req.JoinAsChannelID,
SSRC: req.SSRC,
JoinDate: req.Now,
ActiveDate: req.Now,
LastCheckDate: req.Now,
// VideoJSON 整体替换、PresentationJSON 随全新行清空(rejoin 后客户端
// 会重发 joinGroupCallPresentation,旧屏幕登记必须作废)。
VideoJSON: append([]byte(nil), req.VideoJSON...),
@ -803,6 +808,72 @@ func (s *GroupCallStore) ListGroupCallChainBlocks(_ context.Context, callID int6
return page, nil
}
func (s *GroupCallStore) StartScheduledGroupCall(_ context.Context, callID int64) (domain.GroupCall, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
call, ok := s.calls[callID]
if !ok {
return domain.GroupCall{}, false, domain.ErrGroupCallInvalid
}
if !call.Active() {
return domain.GroupCall{}, false, domain.ErrGroupCallDiscarded
}
if call.ScheduleDate == 0 {
return call, false, nil
}
call.ScheduleDate = 0
s.calls[callID] = call
return call, true, nil
}
func (s *GroupCallStore) SetScheduleStartSubscription(_ context.Context, callID, userID int64, subscribed bool) error {
if callID == 0 || userID == 0 {
return domain.ErrGroupCallInvalid
}
s.mu.Lock()
defer s.mu.Unlock()
subs := s.scheduleSubs[callID]
if subscribed {
if subs == nil {
subs = make(map[int64]struct{})
s.scheduleSubs[callID] = subs
}
subs[userID] = struct{}{}
return nil
}
delete(subs, userID)
return nil
}
func (s *GroupCallStore) ListScheduleSubscriberIDs(_ context.Context, callID int64) ([]int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
subs := s.scheduleSubs[callID]
out := make([]int64, 0, len(subs))
for id := range subs {
out = append(out, id)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out, nil
}
func (s *GroupCallStore) GetRtmpStreamKey(_ context.Context, channelID int64) (string, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
key, ok := s.rtmpKeys[channelID]
return key, ok, nil
}
func (s *GroupCallStore) SetRtmpStreamKey(_ context.Context, channelID int64, key string, _ int) error {
if channelID == 0 || key == "" {
return domain.ErrGroupCallInvalid
}
s.mu.Lock()
defer s.mu.Unlock()
s.rtmpKeys[channelID] = key
return nil
}
func max(a, b int) int {
if a > b {
return a