Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danog/MadelineProto/llms.txt

Use this file to discover all available pages before exploring further.

MadelineProto supports making and receiving Telegram voice calls with full audio streaming capabilities.

Basic Voice Call

From bot.php, here’s how to initiate a call:
use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\RemoteUrl;
use danog\MadelineProto\VoIP;

#[FilterCommand('call')]
public function callVoip(Incoming&Message $message): void
{
    $this->requestCall($message->senderId)
         ->play(new RemoteUrl('http://icestreaming.rai.it/1.mp3'));
}

Handling Incoming Calls

#[Handler]
public function handleIncomingCall(VoIP&Incoming $call): void
{
    $call->accept()
         ->play(new RemoteUrl('http://icestreaming.rai.it/1.mp3'));
}
Calls are automatically accepted and start playing audio from the specified source.

Playing Audio Files

Local Files

use danog\MadelineProto\LocalFile;

private $call;

public function onStart(): void
{
    $this->call = $this->requestCall(self::ADMIN)
                       ->play(new LocalFile('/home/daniil/Music/a.ogg'));
}

Remote URLs

use danog\MadelineProto\RemoteUrl;

$call->play(new RemoteUrl('http://icestreaming.rai.it/1.mp3'));

Message Media

Play audio files sent in messages:
#[Handler]
public function playAudio(Incoming&PrivateMessage&HasAudio $message): void
{
    if (!$this->isSelfUser()) {
        return;
    }
    $this->requestCall($message->senderId)
         ->play($message->media->getStream());
}
The HasAudio filter ensures only messages with audio files trigger this handler.

Audio File Conversion

From libtgvoipbot.php - convert audio files to OGG format for voice calls:
<?php declare(strict_types=1);

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\SimpleFilter\HasAudio;
use danog\MadelineProto\EventHandler\SimpleFilter\HasDocument;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\Ogg;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\SimpleEventHandler;

use function Amp\async;

class LibtgvoipEventHandler extends SimpleEventHandler
{
    private const ADMIN = 'danogentili';
    
    public function getReportPeers(): string
    {
        return self::ADMIN;
    }
    
    #[FilterCommand('start')]
    public function startCmd(Incoming&Message $message): void
    {
        $message->reply(
            message: "This bot can be used to convert files to be played by a @MadelineProto Telegram webradio!".
            "\n\nSee https://docs.madelineproto.xyz/docs/CALLS.html for more info, and call @magicalcrazypony to hear some nice tunes!".
            "\n\nSend me an audio file to start.".
            "\n\nPowered by @MadelineProto, [source code](https://github.com/danog/MadelineProto/blob/v8/examples/libtgvoipbot.php).",
            parseMode: ParseMode::MARKDOWN
        );
    }
    
    #[Handler]
    public function convertCmd((Incoming&Message&HasAudio)|(Incoming&Message&HasDocument) $message): void
    {
        $reply = $message->reply("Conversion in progress...");
        async(function () use ($message, $reply): void {
            $pipe = self::getStreamPipe();
            $sink = $pipe->getSink();
            async(
                Ogg::convert(...),
                $message->media->getStream(),
                $sink
            )->finally($sink->close(...));

            $this->sendDocument(
                peer: $message->chatId,
                file: $pipe->getSource(),
                fileName: $message->media->fileName.".ogg",
                replyToMsgId: $message->id
            );
        })->finally($reply->delete(...));
    }
}

if (!getenv('TOKEN')) {
    throw new AssertionError("You must define a TOKEN environment variable with the token of the bot!");
}

LibtgvoipEventHandler::startAndLoopBot($argv[1] ?? 'libtgvoipbot.madeline', getenv('TOKEN'));

Key Features

Asynchronous Conversion

async(function () use ($message, $reply): void {
    $pipe = self::getStreamPipe();
    $sink = $pipe->getSink();
    
    async(
        Ogg::convert(...),
        $message->media->getStream(),
        $sink
    )->finally($sink->close(...));

    $this->sendDocument(
        peer: $message->chatId,
        file: $pipe->getSource(),
        fileName: $message->media->fileName.".ogg",
        replyToMsgId: $message->id
    );
})->finally($reply->delete(...));
The conversion happens asynchronously. The bot sends a “Conversion in progress…” message and deletes it when done.

Stream Pipes

$pipe = self::getStreamPipe();
$sink = $pipe->getSink();
$source = $pipe->getSource();
Stream pipes allow you to:
  • Convert audio on-the-fly
  • Send files while still processing
  • Handle large files efficiently

Union Type Filters

#[Handler]
public function convertCmd(
    (Incoming&Message&HasAudio)|(Incoming&Message&HasDocument) $message
): void
This handler accepts messages with either audio files OR documents, using PHP 8+ union types.

Complete Voice Call Flow

1. User Sends Audio

#[Handler]
public function playAudio(Incoming&PrivateMessage&HasAudio $message): void
{
    if (!$this->isSelfUser()) {
        return;
    }
    $this->requestCall($message->senderId)
         ->play($message->media->getStream());
}

2. Bot Initiates Call

$call = $this->requestCall($userId);

3. Bot Plays Audio

$call->play($audioSource);
Audio sources can be:
  • LocalFile - Local audio files
  • RemoteUrl - HTTP/HTTPS audio streams
  • $media->getStream() - Telegram message media

Audio Formats

MadelineProto supports:
  • OGG Opus (recommended for calls)
  • MP3
  • MP4/M4A
  • WAV
  • FLAC
Use Ogg::convert() to convert any audio format to OGG Opus, which is optimized for Telegram calls.

Web Radio Example

Create a continuous audio stream:
public function startWebRadio(): void
{
    $playlist = [
        new RemoteUrl('http://stream1.example.com/radio.mp3'),
        new RemoteUrl('http://stream2.example.com/music.mp3'),
    ];
    
    $call = $this->requestCall(self::ADMIN);
    
    foreach ($playlist as $track) {
        $call->play($track);
    }
}

Call Control

// Accept incoming call
$call->accept();

// Discard call
$call->discard();

// Check call state
if ($call->getCallState() === \danog\MadelineProto\VoIP::STATE_READY) {
    // Call is ready
}

Error Handling

try {
    $call = $this->requestCall($userId);
    $call->play(new LocalFile('audio.ogg'));
} catch (\Exception $e) {
    $this->logger("Call failed: " . $e->getMessage());
}

Requirements

PHP 8.2.4+ required. Voice calls use advanced PHP features and require a recent PHP version.
libtgvoip extension recommended. For best performance, install the libtgvoip PHP extension. The bot works without it but with reduced quality.

Running the Conversion Bot

TOKEN="your_bot_token" php libtgvoipbot.php
Set the TOKEN environment variable with your bot token from @BotFather.

Advanced: Multiple Filters

#[Handler]
public function handleMediaMessage(
    Incoming & Message & (HasAudio | HasDocument | HasVideo) $message
): void {
    // Handle any media type
}

Next Steps