. */ namespace danog\MadelineProto; use Amp\Delayed; use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\Stream\Common\FileBufferedStream; use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\Ogg\Ogg; use danog\MadelineProto\VoIP\Endpoint; use function Amp\delay; use function Amp\File\open; if (\extension_loaded('php-libtgvoip')) { return; } class VoIP { use \danog\MadelineProto\VoIP\MessageHandler; use \danog\MadelineProto\VoIP\AckHandler; const PHP_LIBTGVOIP_VERSION = '1.5.0'; const STATE_CREATED = 0; const STATE_WAIT_INIT = 1; const STATE_WAIT_INIT_ACK = 2; const STATE_ESTABLISHED = 3; const STATE_FAILED = 4; const STATE_RECONNECTING = 5; const TGVOIP_ERROR_UNKNOWN = 0; const TGVOIP_ERROR_INCOMPATIBLE = 1; const TGVOIP_ERROR_TIMEOUT = 2; const TGVOIP_ERROR_AUDIO_IO = 3; const NET_TYPE_UNKNOWN = 0; const NET_TYPE_GPRS = 1; const NET_TYPE_EDGE = 2; const NET_TYPE_3G = 3; const NET_TYPE_HSPA = 4; const NET_TYPE_LTE = 5; const NET_TYPE_WIFI = 6; const NET_TYPE_ETHERNET = 7; const NET_TYPE_OTHER_HIGH_SPEED = 8; const NET_TYPE_OTHER_LOW_SPEED = 9; const NET_TYPE_DIALUP = 10; const NET_TYPE_OTHER_MOBILE = 11; const DATA_SAVING_NEVER = 0; const DATA_SAVING_MOBILE = 1; const DATA_SAVING_ALWAYS = 2; const PROXY_NONE = 0; const PROXY_SOCKS5 = 1; const AUDIO_STATE_NONE = -1; const AUDIO_STATE_CREATED = 0; const AUDIO_STATE_CONFIGURED = 1; const AUDIO_STATE_RUNNING = 2; const CALL_STATE_NONE = -1; const CALL_STATE_REQUESTED = 0; const CALL_STATE_INCOMING = 1; const CALL_STATE_ACCEPTED = 2; const CALL_STATE_CONFIRMED = 3; const CALL_STATE_READY = 4; const CALL_STATE_ENDED = 5; const PKT_INIT = 1; const PKT_INIT_ACK = 2; const PKT_STREAM_STATE = 3; const PKT_STREAM_DATA = 4; const PKT_UPDATE_STREAMS = 5; const PKT_PING = 6; const PKT_PONG = 7; const PKT_STREAM_DATA_X2 = 8; const PKT_STREAM_DATA_X3 = 9; const PKT_LAN_ENDPOINT = 10; const PKT_NETWORK_CHANGED = 11; const PKT_SWITCH_PREF_RELAY = 12; const PKT_SWITCH_TO_P2P = 13; const PKT_NOP = 14; const TLID_DECRYPTED_AUDIO_BLOCK_HEX = 'dbf948c1'; const TLID_SIMPLE_AUDIO_BLOCK_HEX = 'cc0d0e76'; const TLID_REFLECTOR_SELF_INFO_HEX = 'c01572c7'; const TLID_REFLECTOR_PEER_INFO_HEX = '27D9371C'; const PROTO_ID = 'GrVP'; const PROTOCOL_VERSION = 9; const MIN_PROTOCOL_VERSION = 9; const STREAM_TYPE_AUDIO = 1; const STREAM_TYPE_VIDEO = 2; const CODEC_OPUS = 'SUPO'; private $TLID_DECRYPTED_AUDIO_BLOCK; private $TLID_SIMPLE_AUDIO_BLOCK; private $TLID_REFLECTOR_SELF_INFO; private $TLID_REFLECTOR_PEER_INFO; private $MadelineProto; public $received_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; public $remote_ack_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; public $session_out_seq_no = 0; public $session_in_seq_no = 0; public $voip_state = 0; public $configuration = ['endpoints' => [], 'shared_config' => []]; public $storage = []; public $internalStorage = []; private $signal = 0; private $callState; private $callID; private $creatorID; private $otherID; private $protocol; private $visualization; private $holdFiles = []; private $inputFiles; private $outputFile; private $isPlaying = false; private bool $creator; private PermAuthKey $authKey; private int $peerVersion = 0; /** * @var Endpoint[] */ private array $sockets = []; private $connection_settings = []; private $dclist = []; private $datacenter; public function __construct(bool $creator, int $otherID, MTProto $MadelineProto, $callState) { $this->creator = $creator; $this->otherID = $otherID; //$this->callID = $callID; $this->madeline = $this->MadelineProto = $MadelineProto; $this->callState = $callState; //$this->protocol = $protocol; $this->TLID_REFLECTOR_SELF_INFO = \strrev(\hex2bin(self::TLID_REFLECTOR_SELF_INFO_HEX)); $this->TLID_REFLECTOR_PEER_INFO = \strrev(\hex2bin(self::TLID_REFLECTOR_PEER_INFO_HEX)); $this->TLID_DECRYPTED_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_DECRYPTED_AUDIO_BLOCK_HEX)); $this->TLID_SIMPLE_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_SIMPLE_AUDIO_BLOCK_HEX)); } public static function getConnectionMaxLayer(): int { return 92; } public function deInitVoIPController() { } public function getDebugString(): string { return ''; } public function setCall($callID) { $this->callID = $callID; } public function setVisualization($visualization) { $this->visualization = $visualization; } public function getVisualization() { return $this->visualization; } public function discard($reason = ['_' => 'phoneCallDiscardReasonDisconnect'], $rating = [], $debug = false) { if ($this->callState === self::CALL_STATE_ENDED || empty($this->configuration)) { return false; } $this->deinitVoIPController(); return Tools::callFork($this->MadelineProto->discardCall($this->callID, $reason, $rating, $debug)); } public function accept() { if ($this->callState !== self::CALL_STATE_INCOMING) { return false; } $this->callState = self::CALL_STATE_ACCEPTED; Tools::call($this->MadelineProto->acceptCall($this->callID))->onResolve(function ($e, $res) { if ($e || !$res) { $this->discard(['_' => 'phoneCallDiscardReasonDisconnect']); } }); return $this; } public function close() { $this->deinitVoIPController(); } public function startTheMagic() { Tools::callFork((function () { $this->authKey = new PermAuthKey(); $this->authKey->setAuthKey($this->configuration['auth_key']); foreach ($this->configuration['endpoints'] as $endpoint) { $this->sockets['v6 '.$endpoint['id']] = new Endpoint('['.$endpoint['ipv6'].']', $endpoint['port'], $endpoint['peer_tag'], true, $this); $this->sockets['v4 '.$endpoint['id']] = new Endpoint($endpoint['ip'], $endpoint['port'], $endpoint['peer_tag'], true, $this); } foreach ($this->sockets as $socket) { yield from $socket->connect(); } $this->init_all(); Tools::callFork((function () use ($socket) { while ($payload = yield from $this->recv_message($socket)) { Tools::callFork($this->handlePacket($socket, $payload)); } })()); })()); return $this; } public function handlePacket($datacenter, $packet) { //\var_dump($packet); switch ($packet['_']) { case self::PKT_INIT: //$this->voip_state = self::STATE_WAIT_INIT_ACK; $this->send_message(['_' => self::PKT_INIT_ACK, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'all_streams' => [['id' => 0, 'type' => self::STREAM_TYPE_AUDIO, 'codec' => self::CODEC_OPUS, 'frame_duration' => 60, 'enabled' => 1]]], $datacenter); if ($this->voip_state !== self::STATE_ESTABLISHED) { $this->voip_state = self::STATE_ESTABLISHED; $ctx = new ConnectionContext; $ctx->addStream(FileBufferedStream::class, yield open('kda.opus', 'r')); $stream = yield from $ctx->getStream(); $ogg = yield from Ogg::init($stream, 60000); $it = $ogg->getEmitter()->iterate(); Tools::callFork($ogg->read()); Tools::callFork((function () use ($it, $datacenter) { $timestamp = 0; $frames = []; while (yield $it->advance()) { $frames []= $it->getCurrent(); } foreach ($frames as $frame) { $t = (microtime(true) / 1000) + 60; yield $this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $frame, 'timestamp' => $timestamp], $datacenter); yield new Delayed((int) ($t - (microtime(true) / 1000))); $timestamp += 60; } })()); } break; case self::PKT_INIT_ACK: break; } } public $timestamp = 0; public function oggCallback($data) { \var_dump(\strlen($data)); $this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $data, 'timestamp' => $this->timestamp]); $this->timestamp += 60; } public function play($file) { $this->inputFiles[] = $file; return $this; } public function then($file) { $this->inputFiles[] = $file; return $this; } public function playOnHold($files) { $this->holdFiles = $files; return $this; } public function setOutputFile($file) { $this->outputFile = $file; return $this; } public function unsetOutputFile() { $this->outputFile = null; } public function setMadeline($MadelineProto) { $this->MadelineProto = $MadelineProto; } public function getProtocol() { return $this->protocol; } public function getOtherID() { return $this->otherID; } public function getCallID() { return $this->callID; } public function whenCreated() { return isset($this->internalStorage['created']) ? $this->internalStorage['created'] : false; } public function parseConfig() { } private function init_all() { foreach ($this->sockets as $socket) { $this->send_message(['_' => self::PKT_INIT, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'audio_streams' => [self::CODEC_OPUS], 'video_streams' => []], $socket); $this->voip_state = self::STATE_WAIT_INIT; } } public function getCallState() { return $this->callState; } public function getVersion() { return 'libponyvoip-1.0'; } public function getPreferredRelayID() { return 0; } public function getLastError() { return ''; } public function getDebugLog() { return ''; } public function getSignalBarsCount() { return $this->signal; } /** * Get the value of creator. * * @return bool */ public function isCreator(): bool { return $this->creator; } /** * Get the value of authKey. * * @return PermAuthKey */ public function getAuthKey(): PermAuthKey { return $this->authKey; } /** * Get the value of peerVersion. * * @return int */ public function getPeerVersion(): int { return $this->peerVersion; } }