gotgcall

package module
v0.5.9 Latest Latest
Warning

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

Go to latest
Published: May 31, 2026 License: GPL-3.0 Imports: 11 Imported by: 0

README

gotgcall

Pure-Go library for streaming audio and video into Telegram group calls. A drop-in alternative to ntgcalls — no libwebrtc, no cgo, no native build chain. Just go build.

WebRTC runs on pion v4. ffmpeg is invoked as a runtime binary for transcoding; nothing is linked in.

Status

Work in progress. Built for my own bots; the API is intentionally close to ntgcalls so existing code translates with minimal change.

Install

go get gotgcall

ffmpeg must be on PATH at runtime (or set gotgcall.WithFFmpegPath("/path/to/ffmpeg")).

Sources

Four constructors, all targeting Opus-in-OGG (audio) or VP8-in-IVF (video):

gotgcall.FromFile("song.mp3", gotgcall.EncodeOptions{})                  // local file
gotgcall.FromURL("https://stream.example.com/...", gotgcall.EncodeOptions{}) // HTTP / HLS / RTMP
gotgcall.FromShell("ffmpeg -i thing.mp3", gotgcall.TrackAudio)           // one custom command, one track
gotgcall.FromShells("ffmpeg -i x.mp4 ...", "ffmpeg -i x.mp4 ...")        // two custom commands, both tracks

Anything ffmpeg can decode is fair game — mp3, m4a, flac, ogg, opus, wav, webm, mp4, mkv, mov, etc. Defaults to audio only, regardless of what the container actually holds. Opt in to video extraction with EncodeOptions{Tracks: TrackAudio | TrackVideo}:

// audio + video from a single source (uses two ffmpeg subprocesses):
client.SetStreamSources(chatID, gotgcall.FromFile("movie.mp4", gotgcall.EncodeOptions{
    Tracks: gotgcall.TrackAudio | gotgcall.TrackVideo,
}))

If the input has no video stream, the video ffmpeg exits with Output file does not contain any stream and the call fails — don't request video tracks unless you know the source has them.

FromShell ffmpeg recipes

FromShell accepts a partial command — any missing essentials (-analyzeduration 0, -probesize 64k before -i; -c:a libopus, -application audio, -frame_duration 20, -page_duration 20000, -mapping_family 0, -ar 48000, -ac 2, -f ogg, pipe:1 after — or -c:v libvpx -deadline realtime -f ivf pipe:1 for video) are filled in. Raw-PCM output codecs are rejected; the frame readers can't parse them.

// audio: minimum command — everything else is auto-filled
gotgcall.FromShell(`ffmpeg -i "song.mp3"`, gotgcall.TrackAudio)

// audio: the full hand-written form (equivalent after auto-fill)
gotgcall.FromShell(`ffmpeg -analyzeduration 0 -probesize 64k -i "song.mp3" `+
    `-vn -c:a libopus -b:a 64k -application audio `+
    `-frame_duration 20 -page_duration 20000 -mapping_family 0 `+
    `-ar 48000 -ac 2 -f ogg pipe:1`, gotgcall.TrackAudio)

// video-only ffmpeg leg (the audio leg is a separate Source / FromShell call)
gotgcall.FromShell(`ffmpeg -i "movie.mp4" -an -c:v libvpx -deadline realtime `+
    `-b:v 800k -vf scale=1280:720 -r 30 -f ivf pipe:1`, gotgcall.TrackVideo)

A single FromShell call produces a single output (audio OR video). For both tracks with custom ffmpeg for each, use FromShells(audioCmd, videoCmd) — equivalent to ntgcalls' MediaDescription{microphone, camera}. Either string may be empty to skip that track. For the convenience path use FromFile/FromURL with Tracks: TrackVideo and let the library construct both ffmpeg commands for you.

Quick start

client, _ := gotgcall.New()
defer client.Close()

client.OnStreamEnd(func(chat int64, t gotgcall.StreamType, d gotgcall.Device, err error) {
    log.Printf("stream end: %v", err)
})

// 1. Local-side JSON.
localParams, _ := client.CreateCall(chatID)

// 2. Drive Telegram via your MTProto layer (gogram, etc.).
//    Pass localParams to phone.JoinGroupCall; read the response.
remoteParams := joinViaYourMTProto(localParams)

// 3. Finish the WebRTC handshake.
client.Connect(chatID, remoteParams)

// 4. Stream.
client.SetStreamSources(chatID, gotgcall.FromFile("song.mp3"))

// 5. Pause / resume / mute / change source any time.
client.Pause(chatID)
client.Resume(chatID)
client.SetStreamSources(chatID, gotgcall.FromURL("https://stream.example.com/radio.m3u8"))

// 6. Stop tears down the call.
client.Stop(chatID)

The library is blob-only — it never imports gogram or any MTProto stack. You drive phone.JoinGroupCall / phone.LeaveGroupCall yourself; the library just produces and consumes JSON. See examples/bot/ for the full wiring against gogram.

RTMP mode

For "go live" (host) broadcasts, swap WebRTC for RTMP push. Obtain the URL via phone.GetGroupCallStreamRtmpUrl, then:

client.StartRTMP(chatID, rtmpURL)
client.SetStreamSources(chatID, gotgcall.FromFile("movie.mp4"))
// Pause/Resume/Stop work identically.

Pause is kill-and-restart-with--ss (Telegram's RTMP ingest times out silent streams; SIGSTOP can't be used).

Concurrency

One *Client per process multiplexes any number of group calls. Methods are safe for concurrent use; per-chat operations are serialised internally. Two concurrent CreateCalls for the same chat won't allocate twice — the per-chat creation mutex gates them.

Options

gotgcall.New(
    gotgcall.WithFFmpegPath("/opt/ffmpeg/bin/ffmpeg"),
    gotgcall.WithLogger(slog.Default()),
    gotgcall.WithSharedUDPMux(),    // single UDP socket for all calls (high-concurrency)
    gotgcall.WithDTLSCertPool(16),  // pre-generate N certs to absorb burst joins
    gotgcall.WithDispatchBuffer(512),
)

Callbacks

client.OnStreamEnd(func(chat int64, t StreamType, d Device, err error) { ... })
client.OnConnectionChange(func(chat int64, info NetworkInfo) { ... })

All callbacks fire from a single dispatcher goroutine so they can safely re-enter the API (e.g. call client.Stop from inside OnStreamEnd).

Server-side media-state changes (admin mute, video disabled) come in through your own gogram UpdateGroupCallParticipants handler — react there by calling client.Pause / client.Resume directly. The library deliberately stays out of MTProto.

Why pure Go

ntgcalls works fine but pulls in libwebrtc + glibc + a C++ build chain. Cross-compiling music bots becomes a maintenance burden. gotgcall builds with CGO_ENABLED=0 to a single static binary on every supported platform. The trade-off is ffmpeg as a runtime dependency, which most bot deployments already have.

License

MIT — see LICENSE.

Documentation

Overview

Package gotgcall is a pure-Go library for streaming audio and video into Telegram group calls. The public API mirrors ntgcalls method names so bot code translates one-to-one.

The library is blob-only: signaling JSON is exchanged through your own MTProto client (typically gogram). Two calls are required:

params, _ := client.CreateCall(chatID)
resp, _   := tg.PhoneJoinGroupCall(... Params: &DataJson{Data: params})
client.Connect(chatID, resp.Updates[...].Call.Params.Data)
client.SetStreamSources(chatID, gotgcall.FromFile("song.mp3", gotgcall.EncodeOptions{}))

See README.md for the full pattern.

Index

Constants

View Source
const (
	TrackAudio = media.TrackAudio
	TrackVideo = media.TrackVideo

	Audio      = models.Audio
	Video      = models.Video
	Microphone = models.Microphone
	Camera     = models.Camera

	Connecting = models.Connecting
	Connected  = models.Connected
	Failed     = models.Failed
	Closed     = models.Closed
	Timeout    = models.Timeout
)

Variables

View Source
var (
	FromFile   = media.FromFile
	FromURL    = media.FromURL
	FromShell  = media.FromShell
	FromShells = media.FromShells
)
View Source
var (
	ErrConnectionExists   = models.ErrConnectionExists
	ErrConnectionNotFound = models.ErrConnectionNotFound
	ErrConnectionTimeout  = models.ErrConnectionTimeout
	ErrConnectionFailed   = models.ErrConnectionFailed
	ErrInvalidParams      = models.ErrInvalidParams
	ErrFFmpegSpawn        = models.ErrFFmpegSpawn
	ErrFFmpegCrashed      = models.ErrFFmpegCrashed
	ErrFile               = models.ErrFile
	ErrClosed             = models.ErrClosed
	ErrInternal           = models.ErrInternal
	ErrNotConnected       = models.ErrNotConnected
	ErrWrongMode          = models.ErrWrongMode
)

Functions

This section is empty.

Types

type CallInfo

type CallInfo = models.CallInfo

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client multiplexes many concurrent group calls behind a single process-wide handle. Safe for concurrent use.

func New

func New(opts ...Option) (*Client, error)

New constructs a Client with the given options. Fails fast if the ffmpeg binary isn't on PATH (or wherever WithFFmpegPath points) so callers see the error at startup rather than on first stream.

func (*Client) AudioSSRC

func (c *Client) AudioSSRC(chatID int64) (uint32, error)

AudioSSRC returns the audio SSRC of a WebRTC call. Pass as Source to phone.LeaveGroupCall. Returns ErrWrongMode for RTMP calls.

func (*Client) Calls

func (c *Client) Calls() map[int64]CallInfo

Calls returns a snapshot of all active calls.

func (*Client) Close

func (c *Client) Close() error

Close stops every call and releases resources. Idempotent.

func (*Client) Connect

func (c *Client) Connect(chatID int64, telegramParams string) error

Connect finishes the WebRTC handshake using Telegram's response JSON.

func (*Client) CreateCall

func (c *Client) CreateCall(chatID int64) (string, error)

CreateCall starts a new WebRTC group-call instance for chatID and returns the JSON params the caller must pass to phone.JoinGroupCall.

Concurrent CreateCall / StartRTMP calls for the same chat are serialized; the first one wins, others get ErrConnectionExists without allocating a pion PeerConnection.

func (*Client) GetState

func (c *Client) GetState(chatID int64) (MediaState, error)

GetState returns the current media-state (mute/pause flags).

func (*Client) Mute

func (c *Client) Mute(chatID int64) (bool, error)

func (*Client) OnConnectionChange

func (c *Client) OnConnectionChange(fn func(chatID int64, info NetworkInfo))

OnConnectionChange registers a callback for ICE/DTLS state transitions.

func (*Client) OnStreamEnd

func (c *Client) OnStreamEnd(fn func(chatID int64, t StreamType, d Device, err error))

OnStreamEnd registers a callback fired when a track ends (EOF, crash, stop). Called on the dispatcher goroutine so it is safe to re-enter the Client API from within.

func (*Client) Pause

func (c *Client) Pause(chatID int64) (bool, error)

func (*Client) Resume

func (c *Client) Resume(chatID int64) (bool, error)

func (*Client) SetStreamSources

func (c *Client) SetStreamSources(chatID int64, src Source) error

SetStreamSources installs or replaces the streaming source for chatID. Encode options (FPS, tracks, bitrates) ride along with the Source — set them on the constructor (FromFile/FromURL).

func (*Client) StartRTMP

func (c *Client) StartRTMP(chatID int64, rtmpURL string) error

StartRTMP creates an RTMP-push call for chatID. The caller obtains rtmpURL via phone.GetGroupCallStreamRtmpUrl gogram-side. Serialised with CreateCall via the same per-chat creation mutex.

func (*Client) Stop

func (c *Client) Stop(chatID int64) error

Stop tears down the call and clears every per-chat scrap of state the library kept (call instance, create-mutex). After Stop the chatID can be re-used cleanly.

func (*Client) Time

func (c *Client) Time(chatID int64) (uint64, error)

Time returns elapsed ms of media pushed.

func (*Client) Unmute

func (c *Client) Unmute(chatID int64) (bool, error)

type ConnState

type ConnState = models.ConnState

type Device

type Device = models.Device

type EncodeOptions

type EncodeOptions = media.EncodeOptions

type MediaState

type MediaState = models.MediaState

type NetworkInfo

type NetworkInfo = models.NetworkInfo

type Option

type Option func(*config)

func WithDTLSCertPool

func WithDTLSCertPool(n int) Option

WithDTLSCertPool sets the size of the pre-generated DTLS certificate pool. Larger pools absorb bigger call-creation bursts without keygen latency. 0 disables pre-generation.

func WithDispatchBuffer

func WithDispatchBuffer(n int) Option

WithDispatchBuffer sizes the event dispatcher's channel. Default 256.

func WithFFmpegPath

func WithFFmpegPath(p string) Option

WithFFmpegPath overrides the ffmpeg binary path (default "ffmpeg").

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets a structured logger for internal events.

func WithSharedUDPMux

func WithSharedUDPMux() Option

WithSharedUDPMux makes all calls share one UDP socket for ICE traffic. Useful for high-concurrency setups (100+ simultaneous calls).

type SeekableSource

type SeekableSource = media.SeekableSource

type Source

type Source = media.Source

type StreamType

type StreamType = models.StreamType

type Track

type Track = media.Track

Directories

Path Synopsis
Package instances holds the per-chat call state.
Package instances holds the per-chat call state.
jsonparams
Package jsonparams encodes and decodes the SDP-like JSON envelope that Telegram's group-call signaling uses in place of standard SDP O/A.
Package jsonparams encodes and decodes the SDP-like JSON envelope that Telegram's group-call signaling uses in place of standard SDP O/A.

Jump to

Keyboard shortcuts

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