internal/domain/groupcall.go
Rama main · réplica de lectura del repositorio original.
package domain
import "errors"
// Group call (supergroup voice chat) domain model. The signaling truth lives in GroupCallStore (memory/postgres
// dual implementation), and the version protocol requires version to be persisted: the client ignores an
// updateGroupCallParticipants with a version lower than its local cache, so a rollback on restart would silently disconnect the whole room.
// GroupCallState is the group call state.
type GroupCallState string
const (
GroupCallStateActive GroupCallState = "active"
GroupCallStateDiscarded GroupCallState = "discarded"
)
// GroupCallKind distinguishes regular channel group calls from ad-hoc
// conference calls. Conference calls have no channel membership scope; access is
// granted by creator/participant/invite/slug.
type GroupCallKind string
const (
GroupCallKindChannel GroupCallKind = "channel"
GroupCallKindConference GroupCallKind = "conference"
)
// GroupCallInviteStatus is the durable state of a private conference invite.
type GroupCallInviteStatus string
const (
GroupCallInvitePending GroupCallInviteStatus = "pending"
GroupCallInviteAccepted GroupCallInviteStatus = "accepted"
GroupCallInviteDeclined GroupCallInviteStatus = "declined"
GroupCallInviteMissed GroupCallInviteStatus = "missed"
GroupCallInviteRevoked GroupCallInviteStatus = "revoked"
)
// Group call business errors; the rpc layer maps them to GROUPCALL_* RPC_ERROR.
var (
ErrGroupCallInvalid = errors.New("group call invalid")
ErrGroupCallDiscarded = errors.New("group call already discarded")
ErrGroupCallAlreadyStarted = errors.New("group call already started")
ErrGroupCallSSRCDuplicate = errors.New("group call ssrc duplicate")
ErrGroupCallNotJoined = errors.New("group call participant missing")
ErrConferenceChainInvalid = errors.New("conference call chain invalid")
)
// GroupCall is the authoritative state of one group call.
type GroupCall struct {
ID int64
AccessHash int64
ChannelID int64
CreatorUserID int64
Kind GroupCallKind
State GroupCallState
Title string
JoinMuted bool
// RtmpStream=true means this is an RTMP live room (ingest goes through external RTMP, viewers pull
// via broadcast); join creates no SFU connection and connection params return stream JSON.
RtmpStream bool
// ScheduleDate>0 means the scheduled call has not started (the client shows a countdown panel and does not join);
// after startScheduledGroupCall clears it, the client automatically initialJoins. Persisted column.
ScheduleDate int
// ScheduleStartSubscribed is a per-viewer projection field (not a persisted column): whether the current viewer
// subscribed to the start reminder. Filled per viewer before RPC/push egress (same kind of semantics as the Creator flag).
ScheduleStartSubscribed bool
// Version is the participant protocol version: +1 within the transaction on every participant change, monotonic and persisted.
Version int
ParticipantsCount int
CreatedAt int
DiscardedAt int
Duration int
// StartedMsgID is the channel message id of messageActionGroupCall(started) (on discard the
// client uses it to locate the starting service message; currently only recorded).
StartedMsgID int
// InviteSlug/InviteLink are used only by conference. The slug must be stably parseable in the migrate reason and
// InputGroupCallSlug; the link is the full URL used by the client UI for copy/share.
InviteSlug string
InviteLink string
// RandomID is the idempotency key of a conference create (creator_user_id + random_id).
RandomID int64
// MigratedFromPhoneCallID records which P2P call it was upgraded from; 0 for a link-only create.
MigratedFromPhoneCallID int64
}
// Active reports whether the call is still ongoing.
func (c GroupCall) Active() bool {
return c.State == GroupCallStateActive
}
func (c GroupCall) Conference() bool {
return c.Kind == GroupCallKindConference
}
// GroupCallParticipant is one participant in the room.
type GroupCallParticipant struct {
CallID int64
UserID int64
// A non-zero JoinAsChannelID means joining as that channel/group identity (anonymous-admin semantics), with TL output
// participant.peer=PeerChannel; 0 = join as the user's own identity. The identity's unique key is still UserID,
// so rejoining with a different identity is a replacement.
JoinAsChannelID int64
// SSRC is the audio ssrc the client self-reports in the join JSON (uint32 range, stored as int64 to avoid sign pitfalls).
SSRC int64
JoinDate int
ActiveDate int
Muted bool
// When MutedByAdmin=true, can_self_unmute=false (admin mute/default-mute policy).
MutedByAdmin bool
// VolumeByAdmin is the global volume set by an admin (1..20000); 0=unset.
VolumeByAdmin int
// A non-zero RaiseHandRating means the hand is raised; the value increases monotonically for ordering.
RaiseHandRating int64
// VideoJSON / PresentationJSON are the raw JSON snapshots of tg.GroupCallParticipantVideo
// (enabled in M3/M4; in M0/M1 they only transparently store self-edits and are not forwarded).
VideoJSON []byte
PresentationJSON []byte
// PublicKey/JoinBlock are used only by conference. The server does not parse E2E content; it only persists
// opaque bytes for chain block backfill/forwarding.
PublicKey []byte
JoinBlock []byte
Left bool
// LastCheckDate is the checkGroupCall keepalive watermark. Note: the client sends checkGroupCall
// only in the Connecting state (the heartbeat stops once media is connected), so a disconnect decision must
// combine this with SFU media-plane liveness for a double expiry (see sweeper); never rely on this field alone.
LastCheckDate int
}
// CreateGroupCallRequest creates a group call.
type CreateGroupCallRequest struct {
ChannelID int64
CreatorUserID int64
RandomID int64
Title string
Now int
}
// JoinGroupCallRequest joins/rejoins a group call (rejoin keeps the same key but takes a new ssrc).
type JoinGroupCallRequest struct {
CallID int64
UserID int64
// Non-zero JoinAsChannelID = join as a channel identity (the caller has already checked permissions).
JoinAsChannelID int64
SSRC int64
Muted bool
IsAdmin bool
PublicKey []byte
JoinBlock []byte
// VideoJSON is the video internal state minted by this join (endpoint+source group+active); a rejoin
// replaces it wholesale and **clears the old PresentationJSON** (after the client's main connection rejoins it re-sends
// joinGroupCallPresentation, so the old screen registration must be invalidated).
VideoJSON []byte
Now int
}
// GroupCallMutation is the result of one participant-dimension change: the updated call row (with the new version)
// and the affected participant row (for pushing updateGroupCallParticipants).
type GroupCallMutation struct {
Call GroupCall
Participant GroupCallParticipant
}
// RemoveConferenceCallParticipantsRequest describes a conference participant
// removal from both the media participant set and the E2E member chain.
type RemoveConferenceCallParticipantsRequest struct {
CallID int64
AuthorUserID int64
TargetUserIDs []int64
OnlyLeft bool
Kick bool
Block []byte
Now int
}
// RemoveConferenceCallParticipantsResult is the transactional result of
// accepting a conference E2E removal block and/or kicking active participants.
type RemoveConferenceCallParticipantsResult struct {
Call GroupCall
ParticipantsChanged []GroupCallParticipant
ChainBlock GroupCallChainBlock
ChainBlockAppended bool
}
// GroupCallParticipantUpdate is a field-level update of editGroupCallParticipant (nil=unchanged).
type GroupCallParticipantUpdate struct {
Muted *bool
MutedByAdmin *bool
VolumeByAdmin *int
RaiseHandRating *int64
VideoJSON *[]byte
PresentationJSON *[]byte
Now int
}
// GroupCallParticipantOverride is a per-viewer view override (setter→target):
// local mute/local volume, visible only to the setter, not entering the room-wide version.
type GroupCallParticipantOverride struct {
MutedByYou bool
Volume int // 0=unset
}
// GroupCallParticipantPage is the paginated result of getGroupParticipants.
type GroupCallParticipantPage struct {
Count int
Participants []GroupCallParticipant
NextOffset string
Version int
}
// GroupCallInvite is the index of an invite service message a conference call sent in a private chat.
type GroupCallInvite struct {
CallID int64
InviterUserID int64
InviteeUserID int64
MessageID int
Status GroupCallInviteStatus
Video bool
CreatedAt int
UpdatedAt int
}
// GroupCallChainBlockLatestOffset is the sentinel value clients use to request the current sub-chain's latest block.
const GroupCallChainBlockLatestOffset = -1
// GroupCallChainBlock is an opaque block of the conference E2E chain.
type GroupCallChainBlock struct {
CallID int64
SubChainID int
Offset int
AuthorUserID int64
Block []byte
CreatedAt int
}
type GroupCallChainBlockPage struct {
Blocks []GroupCallChainBlock
NextOffset int
}