Documentation
¶
Overview ¶
Package calls implements Telegram 1:1 phone calls (the tgcalls "v2" protocol) on top of the gotd/td MTProto client.
A phone call has two halves:
- Signaling, carried over MTProto: the Diffie-Hellman key exchange (phone.requestCall / acceptCall / confirmCall), reflector/STUN/TURN endpoint discovery and the encrypted control channel (phone.sendSignalingData / updatePhoneCallSignalingData).
- Media, carried over a direct peer-to-peer ICE/DTLS/SRTP connection negotiated through the signaling channel.
This package owns all of the signaling half and drives a github.com/pion/webrtc/v4 transport for the media half. The media engine (audio/video encoding) is intentionally left to the caller: outgoing media is written as RTP to the local tracks exposed by Conn.AudioTrack and Conn.VideoTrack, and incoming media is delivered as github.com/pion/webrtc/v4.TrackRemote values through Conn.OnTrack.
Connectivity ¶
The transport connects over direct ICE candidates (host and STUN/server- reflexive). Telegram also offers relay candidates served by its custom "reflector" protocol (tgcalls ReflectorPort), which is not RFC TURN; those are not implemented here and are skipped. Calls therefore require a usable direct path between the peers — they will not fall back to relay when both sides are behind restrictive NATs.
Signaling protocol ¶
This package speaks the tgcalls "V2" signaling protocol (sequenced, uncompressed). It advertises only the library versions that select V2; it does not implement "V3" (gzip + SCTP-tunneled signaling).
Usage ¶
Create a Client bound to a *tg.Client invoker, register it on the update dispatcher, then place or answer calls:
calls := calls.NewClient(tg.NewClient(invoker), calls.Options{})
calls.Register(dispatcher)
calls.OnIncoming(func(in *calls.IncomingCall) {
conn, err := in.Accept(ctx)
// ... use conn ...
})
conn, err := calls.Request(ctx, inputUser)
The returned Conn reports connectivity through OnConnected/OnDisconnected and exposes the negotiated media tracks.
White-box note: the wire formats (DH key derivation, the signaling EncryptedConnection AES-CTR scheme and the JSON control messages) are reimplemented from the official tgcalls reference and verified against it; no third-party Go implementation is reused.
Index ¶
- type Client
- func (c *Client) Discard(ctx context.Context, reason DiscardReason) error
- func (c *Client) Handle(ctx context.Context, u *tg.UpdatePhoneCall) error
- func (c *Client) HandleSignalingData(ctx context.Context, u *tg.UpdatePhoneCallSignalingData) error
- func (c *Client) OnIncoming(fn func(*IncomingCall))
- func (c *Client) Register(d tg.UpdateDispatcher)
- func (c *Client) Request(ctx context.Context, user tg.InputUserClass) (*Conn, error)
- type Conn
- func (c *Conn) AudioSSRC() uint32
- func (c *Conn) AudioTrack() *webrtc.TrackLocalStaticRTP
- func (c *Conn) Close() error
- func (c *Conn) OnConnected(fn func())
- func (c *Conn) OnDisconnected(fn func())
- func (c *Conn) OnStateChange(fn func(state string))
- func (c *Conn) OnTrack(fn func(*webrtc.TrackRemote, *webrtc.RTPReceiver))
- func (c *Conn) State() string
- func (c *Conn) VideoSSRC() uint32
- func (c *Conn) VideoTrack() *webrtc.TrackLocalStaticRTP
- type DiscardReason
- type GroupCall
- func (g *GroupCall) AudioSSRC() uint32
- func (g *GroupCall) AudioTrack() *webrtc.TrackLocalStaticRTP
- func (g *GroupCall) Join(ctx context.Context, call *tg.InputGroupCall, joinAs tg.InputPeerClass) error
- func (g *GroupCall) Leave(ctx context.Context) error
- func (g *GroupCall) OnConnected(fn func())
- func (g *GroupCall) OnDisconnected(fn func())
- func (g *GroupCall) OnParticipants(fn func([]tg.GroupCallParticipant))
- func (g *GroupCall) OnTrack(fn func(*webrtc.TrackRemote, *webrtc.RTPReceiver))
- func (g *GroupCall) Register(d tg.UpdateDispatcher)
- func (g *GroupCall) WriteAudio(pkt *rtp.Packet) error
- type IncomingCall
- type Options
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client places and answers Telegram 1:1 phone calls. It manages a single active call at a time and routes the relevant updates into it.
Wire it to the update dispatcher with Register (or forward updates manually via Handle and HandleSignalingData).
Example ¶
This example shows how to wire the call client to an update dispatcher and place an outgoing call. Error handling is elided for brevity.
// In a real program the dispatcher is passed to telegram.Options as the
// UpdateHandler, and api wraps the connected client.
dispatcher := tg.NewUpdateDispatcher()
var api *tg.Client // = tg.NewClient(client)
c := calls.NewClient(api, calls.Options{})
c.Register(dispatcher)
// Answer incoming calls.
c.OnIncoming(func(in *calls.IncomingCall) {
conn, err := in.Accept(context.Background())
if err != nil {
return
}
conn.OnTrack(func(remote *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
_ = remote // decode incoming media
})
})
// Place an outgoing call to a resolved input user.
var user tg.InputUserClass
conn, err := c.Request(context.Background(), user)
if err != nil {
return
}
conn.OnConnected(func() {
// Write Opus RTP packets to conn.AudioTrack().
})
func (*Client) Discard ¶
func (c *Client) Discard(ctx context.Context, reason DiscardReason) error
Discard ends the active call with the given reason.
func (*Client) HandleSignalingData ¶
HandleSignalingData decrypts an incoming signaling packet and feeds it to the active call's media connection, then acknowledges it.
func (*Client) OnIncoming ¶
func (c *Client) OnIncoming(fn func(*IncomingCall))
OnIncoming registers a callback invoked when a peer requests a call.
func (*Client) Register ¶
func (c *Client) Register(d tg.UpdateDispatcher)
Register installs the call update handlers on the dispatcher.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is the media transport of an established call. It bridges the Telegram signaling channel to a pion/webrtc ICE/DTLS/SRTP connection.
Outgoing media is written to the tracks returned by AudioTrack/VideoTrack; incoming media is delivered to the OnTrack callback.
func (*Conn) AudioTrack ¶
func (c *Conn) AudioTrack() *webrtc.TrackLocalStaticRTP
AudioTrack returns the local audio track; write Opus RTP packets to it.
func (*Conn) Close ¶
Close tears down the transport.
It uses pion's graceful shutdown variants, which block until the ICE agent's asynchronous state-change notifier goroutines have drained. Otherwise the queued "closed" callback could fire (and log) after the caller — e.g. a test — has already returned.
func (*Conn) OnConnected ¶
func (c *Conn) OnConnected(fn func())
OnConnected registers a callback invoked once the call's media transport is connected. If the connection is already up, fn is called immediately.
func (*Conn) OnDisconnected ¶
func (c *Conn) OnDisconnected(fn func())
OnDisconnected registers a callback invoked when the transport is lost.
func (*Conn) OnStateChange ¶
OnStateChange registers a callback invoked on aggregate state changes.
func (*Conn) OnTrack ¶
func (c *Conn) OnTrack(fn func(*webrtc.TrackRemote, *webrtc.RTPReceiver))
OnTrack registers a callback invoked for each remote media track.
func (*Conn) VideoTrack ¶
func (c *Conn) VideoTrack() *webrtc.TrackLocalStaticRTP
VideoTrack returns the local video track; write VP8 RTP packets to it.
type DiscardReason ¶
type DiscardReason int
DiscardReason describes why a call ended, mapping to the phoneCallDiscardReason* constructors.
const ( // DiscardHangup means the call was ended normally by a participant. DiscardHangup DiscardReason = iota // DiscardBusy means the callee was busy. DiscardBusy // DiscardMissed means the call was not answered. DiscardMissed // DiscardDisconnect means the connection was lost. DiscardDisconnect )
type GroupCall ¶
type GroupCall struct {
// contains filtered or unexported fields
}
GroupCall joins a Telegram group call (voice chat) and streams audio into it.
Unlike a 1:1 Conn, a group call connects to Telegram's SFU rather than to a peer: a single WebRTC PeerConnection carries our outgoing audio and any incoming tracks the server forwards. Outgoing audio is written with GroupCall.WriteAudio (which also feeds RTCP sender reports) or, for raw access, via GroupCall.AudioTrack.
func NewGroupCall ¶
NewGroupCall returns a group call bound to the given invoker.
func (*GroupCall) AudioTrack ¶
func (g *GroupCall) AudioTrack() *webrtc.TrackLocalStaticRTP
AudioTrack returns the raw outgoing audio track for advanced use. Prefer WriteAudio, which also drives RTCP sender reports.
func (*GroupCall) Join ¶
func (g *GroupCall) Join(ctx context.Context, call *tg.InputGroupCall, joinAs tg.InputPeerClass) error
Join joins the group call identified by call, presenting joinAs as the speaking peer (typically the logged-in user). It blocks until the media transport connects or ctx is done.
func (*GroupCall) OnConnected ¶
func (g *GroupCall) OnConnected(fn func())
OnConnected registers a callback fired when the media transport connects.
func (*GroupCall) OnDisconnected ¶
func (g *GroupCall) OnDisconnected(fn func())
OnDisconnected registers a callback fired when the transport is lost.
func (*GroupCall) OnParticipants ¶
func (g *GroupCall) OnParticipants(fn func([]tg.GroupCallParticipant))
OnParticipants registers a callback fired on participant-list updates (only after Register has wired the dispatcher).
func (*GroupCall) OnTrack ¶
func (g *GroupCall) OnTrack(fn func(*webrtc.TrackRemote, *webrtc.RTPReceiver))
OnTrack registers a callback fired for each incoming forwarded track.
func (*GroupCall) Register ¶
func (g *GroupCall) Register(d tg.UpdateDispatcher)
Register installs the group-call participant handler on the dispatcher.
func (*GroupCall) WriteAudio ¶
WriteAudio writes one Opus RTP packet to the call and updates the RTCP sender-report counters. The packet's SSRC and payload type are rewritten to the negotiated values, so only sequence number and timestamp matter.
type IncomingCall ¶
type IncomingCall struct {
// contains filtered or unexported fields
}
IncomingCall is a pending call request from a peer. Answer it with Accept or turn it down with Reject.
func (*IncomingCall) Accept ¶
func (ic *IncomingCall) Accept(ctx context.Context) (*Conn, error)
Accept answers the call, performing the DH exchange and returning the media connection once the signaling handshake completes.
func (*IncomingCall) Reject ¶
func (ic *IncomingCall) Reject(ctx context.Context) error
Reject declines the call as busy.
func (*IncomingCall) UserID ¶
func (ic *IncomingCall) UserID() int64
UserID returns the caller's user ID.
func (*IncomingCall) Video ¶
func (ic *IncomingCall) Video() bool
Video reports whether the caller requested a video call.