calls

package
v0.157.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 29 Imported by: 0

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

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 NewClient

func NewClient(api *tg.Client, opts Options) *Client

NewClient returns a Client bound to the given invoker.

func (*Client) Discard

func (c *Client) Discard(ctx context.Context, reason DiscardReason) error

Discard ends the active call with the given reason.

func (*Client) Handle

func (c *Client) Handle(ctx context.Context, u *tg.UpdatePhoneCall) error

Handle routes an updatePhoneCall into the active call.

func (*Client) HandleSignalingData

func (c *Client) HandleSignalingData(ctx context.Context, u *tg.UpdatePhoneCallSignalingData) error

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.

func (*Client) Request

func (c *Client) Request(ctx context.Context, user tg.InputUserClass) (*Conn, error)

Request places an outgoing call to user. It performs the DH exchange and returns the media connection once the signaling handshake completes; the connection becomes usable when its OnConnected callback fires.

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) AudioSSRC

func (c *Conn) AudioSSRC() uint32

AudioSSRC returns the SSRC chosen for the local audio track.

func (*Conn) AudioTrack

func (c *Conn) AudioTrack() *webrtc.TrackLocalStaticRTP

AudioTrack returns the local audio track; write Opus RTP packets to it.

func (*Conn) Close

func (c *Conn) Close() error

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

func (c *Conn) OnStateChange(fn func(state string))

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) State

func (c *Conn) State() string

State returns the current aggregate connection state.

func (*Conn) VideoSSRC

func (c *Conn) VideoSSRC() uint32

VideoSSRC returns the SSRC chosen for the local video 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

func NewGroupCall(api *tg.Client, opts Options) *GroupCall

NewGroupCall returns a group call bound to the given invoker.

func (*GroupCall) AudioSSRC

func (g *GroupCall) AudioSSRC() uint32

AudioSSRC returns the SSRC of the outgoing audio stream.

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) Leave

func (g *GroupCall) Leave(ctx context.Context) error

Leave leaves the call and tears down the transport.

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

func (g *GroupCall) WriteAudio(pkt *rtp.Packet) error

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.

type Options

type Options struct {
	// Logger is used for diagnostics. Defaults to a no-op logger.
	Logger log.Logger
	// Random is the entropy source for DH exponents. Defaults to crypto/rand.
	Random io.Reader
}

Options configures a Client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL