Advanced

Group calls

Group calls and live streams are a parallel signalling surface inside MTProto. gogram exposes the signalling layer — create, join, mute, hand off — but the audio plumbing happens in your own WebRTC stack.

What gogram does

Telegram's group call protocol has two parts: signalling (who is in the call, who is speaking, who has video, what the SDP offers/answers look like) and media transport (Opus audio frames, VP8/VP9/H.264 video). Signalling runs over MTProto; media runs over a separate UDP/SRTP path negotiated through the signalling.

gogram implements the signalling. High-level helpers cover create / discard / invite / mute / raise-hand / edit-title / leave. The actual join call is the raw PhoneJoinGroupCallbecause it takes the SDP payload produced by your WebRTC stack. For media you bring your own stack — pion, libwebrtc bindings, a managed SFU — or use the sister gortc project.

Creating a call

call, err := client.StartGroupCall("@my_group", &telegram.StartGroupCallOptions{
	Title: "Daily standup",
})
if err != nil { log.Fatal(err) }

if c, ok := call.(*telegram.InputGroupCallObj); ok {
	fmt.Println("call id:", c.ID, "access:", c.AccessHash)
}
call, err := client.StartGroupCall("@my_group", &telegram.StartGroupCallOptions{
	Title: "Daily standup",
})
if err != nil { log.Fatal(err) }

if c, ok := call.(*telegram.InputGroupCallObj); ok {
	fmt.Println("call id:", c.ID, "access:", c.AccessHash)
}

StartGroupCall returns an InputGroupCall (interface) you pass to every subsequent method. Set RTMP: true on the options for a live-stream call instead of a two-way voice chat; set ScheduleDate to a future time to create a scheduled call that only opens for join at that moment.

Joining and leaving

// gogram exposes the raw signalling call. The SDP payload comes
// from your WebRTC stack.
_, err := client.PhoneJoinGroupCall(&telegram.PhoneJoinGroupCallParams{
	Call:   call,
	JoinAs: joinAs,               // InputPeer to appear as
	Params: &telegram.DataJson{Data: sdpOfferJSON},
	Muted:  true,
})
// gogram exposes the raw signalling call. The SDP payload comes
// from your WebRTC stack.
_, err := client.PhoneJoinGroupCall(&telegram.PhoneJoinGroupCallParams{
	Call:   call,
	JoinAs: joinAs,               // InputPeer to appear as
	Params: &telegram.DataJson{Data: sdpOfferJSON},
	Muted:  true,
})

The Params.Data is the SDP payload your WebRTC stack produced. The server responds with an SDP answer inside the Updates; hand it back to the WebRTC stack and ICE negotiates the media path. Toggle Muted, VideoStopped, and later runtime state via EditGroupCallParticipant.

// Drop your participant record (source id from the SDP negotiation):
if err := client.LeaveGroupCall(call, mySource); err != nil {
	log.Println(err)
}
// End the call entirely (admins only):
client.DiscardGroupCall(call)
// Drop your participant record (source id from the SDP negotiation):
if err := client.LeaveGroupCall(call, mySource); err != nil {
	log.Println(err)
}
// End the call entirely (admins only):
client.DiscardGroupCall(call)

Muting and moderation

muted := true
client.EditGroupCallParticipant(call, participantPeer, &telegram.GroupCallParticipantPatch{
	Muted: &muted,
})

// convenience wrappers:
client.MuteParticipant(call, participantPeer)
client.UnmuteParticipant(call, participantPeer)

// admin-side controls:
client.EditGroupCallTitle(call, "New title")
client.InviteToGroupCall(call, "@alice", "@bob")
client.ExportGroupCallInvite(call, false)
muted := true
client.EditGroupCallParticipant(call, participantPeer, &telegram.GroupCallParticipantPatch{
	Muted: &muted,
})

// convenience wrappers:
client.MuteParticipant(call, participantPeer)
client.UnmuteParticipant(call, participantPeer)

// admin-side controls:
client.EditGroupCallTitle(call, "New title")
client.InviteToGroupCall(call, "@alice", "@bob")
client.ExportGroupCallInvite(call, false)

Every field on GroupCallParticipantPatchis a pointer — leaving one nilmeans "do not change". To read the roster: GetGroupCallParticipants(call, limit).

Audio plumbing

After joining, gogram surfaces the negotiated SSRC values via the join response. Your WebRTC stack uses those to send Opus packets to the SFU and decode incoming audio. The library does not transcode, mix, or proxy audio — that is firmly outside the MTProto scope.