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'));
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
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