mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-30 07:58:58 +01:00
Implement hold logic
This commit is contained in:
parent
2c95ca4415
commit
8678bba3bb
12
README.md
12
README.md
@ -257,7 +257,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* [Create a secret chat bot](https://docs.madelineproto.xyz/docs/SECRET_CHATS.html)
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.declinePasswordReset.html" name="account.declinePasswordReset">Abort a pending 2FA password reset, see here for more info »: account.declinePasswordReset</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth.acceptLoginToken.html" name="auth.acceptLoginToken">Accept QR code login token, logging in the app that generated it: auth.acceptLoginToken</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#acceptcall-array-call-bool" name="acceptCall">Accept call: acceptCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#acceptcall-int-id-void" name="acceptCall">Accept call: acceptCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#acceptsecretchat-array-params-void" name="acceptSecretChat">Accept secret chat: acceptSecretChat</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help.acceptTermsOfService.html" name="help.acceptTermsOfService">Accept the new terms of service: help.acceptTermsOfService</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/bots.toggleUsername.html" name="bots.toggleUsername">Activate or deactivate a purchased fragment.com username associated to a bot we own: bots.toggleUsername</a>
|
||||
@ -326,11 +326,9 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#closeconnection-string-message-void" name="closeConnection">Close connection with client, connected via web: closeConnection</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#completephonelogin-string-code-mixed" name="completePhoneLogin">Complet user login using login code: completePhoneLogin</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#complete2falogin-string-password-array" name="complete2faLogin">Complete 2FA login: complete2faLogin</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#completecall-array-params-mixed" name="completeCall">Complete call handshake: completeCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#completesignup-string-first_name-string-last_name-array" name="completeSignup">Complete signup to Telegram: completeSignup</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.startHistoryImport.html" name="messages.startHistoryImport">Complete the history import process, importing all messages into the chat. : messages.startHistoryImport</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.confirmPhone.html" name="account.confirmPhone">Confirm a phone number to cancel account deletion, for more info click here »: account.confirmPhone</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#confirmcall-array-params-mixed" name="confirmCall">Confirm call: confirmCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.receivedMessages.html" name="messages.receivedMessages">Confirms receipt of messages by a client, cancels PUSH-notification sending: messages.receivedMessages</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#tosupergroup-int-id-int" name="toSupergroup">Convert MTProto channel ID to bot API channel ID: toSupergroup</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#mtprototobotapi-array-data-array" name="MTProtoToBotAPI">Convert MTProto parameters to bot API parameters: MTProtoToBotAPI</a>
|
||||
@ -383,7 +381,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts.deleteContacts.html" name="contacts.deleteContacts">Deletes several contacts from the list: contacts.deleteContacts</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts.unblock.html" name="contacts.unblock">Deletes the user from the blacklist: contacts.unblock</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.deactivateAllUsernames.html" name="channels.deactivateAllUsernames">Disable all purchased usernames of a supergroup or channel: channels.deactivateAllUsernames</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#discardcall-array-call-array-reason-array-rating-bool-need_debug-true-danog-madelineproto-voip" name="discardCall">Discard call: discardCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#discardcall-int-id-danog-madelineproto-voip-discardreason-reason-danog-madelineproto-voip-discardreason-hangup-int-1-5-rating-null-string-comment-null-void" name="discardCall">Discard call: discardCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#discardsecretchat-int-chat-void" name="discardSecretChat">Discard secret chat: discardSecretChat</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help.dismissSuggestion.html" name="help.dismissSuggestion">Dismiss a suggestion, see here for more info »: help.dismissSuggestion</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/chatlists.hideChatlistUpdates.html" name="chatlists.hideChatlistUpdates">Dismiss new pending peers recently added to a chat folder deep link »: chatlists.hideChatlistUpdates</a>
|
||||
@ -471,8 +469,8 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.getAutoSaveSettings.html" name="account.getAutoSaveSettings">Get autosave settings: account.getAutoSaveSettings</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getconfig-array-config-array" name="getConfig">Get cached (or eventually re-fetch) server-side config: getConfig</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getcachedconfig-array" name="getCachedConfig">Get cached server-side config: getCachedConfig</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getcall-int-call-array" name="getCall">Get call info: getCall</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#callstatus-int-id-int" name="callStatus">Get call status: callStatus</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getcallstate-int-id-danog-madelineproto-voip-callstate" name="getCallState">Get call state: getCallState</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#callstatus-int-id-danog-madelineproto-voip-callstate" name="callStatus">Get call status: callStatus</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getEmojiKeywordsDifference.html" name="messages.getEmojiKeywordsDifference">Get changed emoji keywords »: messages.getEmojiKeywordsDifference</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help.getAppChangelog.html" name="help.getAppChangelog">Get changelog of current app. : help.getAppChangelog</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/stats.getBroadcastStats.html" name="stats.getBroadcastStats">Get channel statistics: stats.getBroadcastStats</a>
|
||||
@ -699,6 +697,8 @@ Want to add your own open-source project to this list? [Click here!](https://doc
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.updatePinnedMessage.html" name="messages.updatePinnedMessage">Pin a message: messages.updatePinnedMessage</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.updatePinnedForumTopic.html" name="channels.updatePinnedForumTopic">Pin or unpin forum topics: channels.updatePinnedForumTopic</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.toggleDialogPin.html" name="messages.toggleDialogPin">Pin/unpin a dialog: messages.toggleDialogPin</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#callplay-int-id-string-file-void" name="callPlay">Play file in call: callPlay</a>
|
||||
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#callplayonhold-int-id-string-files-void" name="callPlayOnHold">Play files on hold in call: callPlayOnHold</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels.getMessages.html" name="channels.getMessages">Please use the event handler: channels.getMessages</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getHistory.html" name="messages.getHistory">Please use the event handler: messages.getHistory</a>
|
||||
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getMessages.html" name="messages.getMessages">Please use the event handler: messages.getMessages</a>
|
||||
|
@ -747,9 +747,7 @@
|
||||
<code>MTProtoToTdcli</code>
|
||||
<code>all</code>
|
||||
<code>any</code>
|
||||
<code>completeCall</code>
|
||||
<code>completePhoneLogin</code>
|
||||
<code>confirmCall</code>
|
||||
<code>downloadToCallable</code>
|
||||
<code>downloadToDir</code>
|
||||
<code>downloadToStream</code>
|
||||
@ -1276,7 +1274,6 @@
|
||||
<PossiblyNullReference>
|
||||
<code>logger</code>
|
||||
<code>start</code>
|
||||
<code>start</code>
|
||||
</PossiblyNullReference>
|
||||
<PropertyNotSetInConstructor>
|
||||
<code>$channelParticipants</code>
|
||||
@ -1300,6 +1297,7 @@
|
||||
<RedundantCondition>
|
||||
<code><![CDATA[$this->minDatabase?->sync()]]></code>
|
||||
<code><![CDATA[isset($this->wrapper) && $this->isInited()]]></code>
|
||||
<code><![CDATA[isset($this->wrapper) && isset(self::$references[$this->getSessionName()])]]></code>
|
||||
</RedundantCondition>
|
||||
<RedundantConditionGivenDocblockType>
|
||||
<code><![CDATA[$this->authorized === API::LOGGED_IN && \is_int($dc_id)]]></code>
|
||||
@ -2009,39 +2007,6 @@
|
||||
<code>$stream</code>
|
||||
</MissingPropertyType>
|
||||
</file>
|
||||
<file src="src/Stream/Ogg/Ogg.php">
|
||||
<MismatchingDocblockReturnType>
|
||||
<code><![CDATA[iterable<string>]]></code>
|
||||
</MismatchingDocblockReturnType>
|
||||
<PossiblyInvalidArgument>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$size</code>
|
||||
</PossiblyInvalidArgument>
|
||||
<PossiblyInvalidPropertyAssignmentValue>
|
||||
<code><![CDATA[$this->currentDuration += $frameDuration]]></code>
|
||||
<code><![CDATA[$this->currentDuration += $totalDuration]]></code>
|
||||
</PossiblyInvalidPropertyAssignmentValue>
|
||||
<PropertyNotSetInConstructor>
|
||||
<code>$emitter</code>
|
||||
<code>$packFormat</code>
|
||||
<code>$stream</code>
|
||||
<code>$streamCount</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
<PropertyTypeCoercion>
|
||||
<code><![CDATA[$stream->getReadBuffer($l)]]></code>
|
||||
</PropertyTypeCoercion>
|
||||
<ReferenceConstraintViolation>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
<code>$offset</code>
|
||||
</ReferenceConstraintViolation>
|
||||
</file>
|
||||
<file src="src/Stream/Proxy/HttpProxy.php">
|
||||
<MissingConstructor>
|
||||
<code>$stream</code>
|
||||
@ -2341,98 +2306,61 @@
|
||||
</UnsupportedReferenceUsage>
|
||||
</file>
|
||||
<file src="src/VoIP.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code>$stream</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<MissingPropertyType>
|
||||
<code>$TLID_DECRYPTED_AUDIO_BLOCK</code>
|
||||
<code>$TLID_REFLECTOR_PEER_INFO</code>
|
||||
<code>$TLID_REFLECTOR_SELF_INFO</code>
|
||||
<code>$TLID_SIMPLE_AUDIO_BLOCK</code>
|
||||
<code>$callID</code>
|
||||
<code>$configuration</code>
|
||||
<code>$creatorID</code>
|
||||
<code>$holdFiles</code>
|
||||
<code>$inputFiles</code>
|
||||
<code>$internalStorage</code>
|
||||
<code>$isPlaying</code>
|
||||
<code>$otherID</code>
|
||||
<code>$outputFile</code>
|
||||
<code>$protocol</code>
|
||||
<code>$received_timestamp_map</code>
|
||||
<code>$remote_ack_timestamp_map</code>
|
||||
<code>$session_in_seq_no</code>
|
||||
<code>$session_out_seq_no</code>
|
||||
<code>$signal</code>
|
||||
<code>$storage</code>
|
||||
<code>$visualization</code>
|
||||
<code>$voip_state</code>
|
||||
</MissingPropertyType>
|
||||
<MissingReturnType>
|
||||
<code>openFile</code>
|
||||
</MissingReturnType>
|
||||
<PropertyNotSetInConstructor>
|
||||
<code>$authKey</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/VoIP/AckHandler.php">
|
||||
<MissingParamType>
|
||||
<code>$ack_mask</code>
|
||||
<code>$last_ack_id</code>
|
||||
<code>$packet_seq_no</code>
|
||||
<code>$s1</code>
|
||||
<code>$s2</code>
|
||||
</MissingParamType>
|
||||
<MissingReturnType>
|
||||
<code>received_packet</code>
|
||||
<code>seqgt</code>
|
||||
</MissingReturnType>
|
||||
<PossiblyInvalidArrayAccess>
|
||||
<code><![CDATA[$API->getSelf()['id']]]></code>
|
||||
</PossiblyInvalidArrayAccess>
|
||||
</file>
|
||||
<file src="src/VoIP/AuthKeyHandler.php">
|
||||
<MissingClosureParamType>
|
||||
<code>$controller</code>
|
||||
<code>$id</code>
|
||||
</MissingClosureParamType>
|
||||
<MissingReturnType>
|
||||
<code>completeCall</code>
|
||||
<code>confirmCall</code>
|
||||
<code>requestCall</code>
|
||||
</MissingReturnType>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code><![CDATA[$call['id']]]></code>
|
||||
<code><![CDATA[$call['id']]]></code>
|
||||
<code><![CDATA[$call['id']]]></code>
|
||||
<code><![CDATA[$call['id']]]></code>
|
||||
<code><![CDATA[$user['user_id']]]></code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
</file>
|
||||
<file src="src/VoIP/Endpoint.php">
|
||||
<MissingReturnType>
|
||||
<code>read</code>
|
||||
</MissingReturnType>
|
||||
<MissingParamType>
|
||||
<code>$stream</code>
|
||||
</MissingParamType>
|
||||
<PossiblyInvalidArrayOffset>
|
||||
<code><![CDATA[$result[$x]['data']]]></code>
|
||||
<code><![CDATA[$result[$x]['data']]]></code>
|
||||
<code><![CDATA[$result[$x]['has_more_flags']]]></code>
|
||||
<code><![CDATA[$result[$x]['has_more_flags']]]></code>
|
||||
<code><![CDATA[$result[$x]['stream_id']]]></code>
|
||||
<code><![CDATA[$result[$x]['stream_id']]]></code>
|
||||
<code><![CDATA[$result[$x]['timestamp']]]></code>
|
||||
<code><![CDATA[$result[$x]['timestamp']]]></code>
|
||||
</PossiblyInvalidArrayOffset>
|
||||
<PossiblyNullReference>
|
||||
<code>read</code>
|
||||
</PossiblyNullReference>
|
||||
</file>
|
||||
<file src="src/VoIP/MessageHandler.php">
|
||||
<MissingParamType>
|
||||
<code>$args</code>
|
||||
<code>$datacenter</code>
|
||||
<code>$object</code>
|
||||
<code>$stream</code>
|
||||
</MissingParamType>
|
||||
<MissingReturnType>
|
||||
<code>pack_string</code>
|
||||
<code>recv_message</code>
|
||||
<code>send_message</code>
|
||||
<code>unpack_string</code>
|
||||
</MissingReturnType>
|
||||
<PossiblyUndefinedVariable>
|
||||
<code>$ack_mask</code>
|
||||
<code>$in_seq_no</code>
|
||||
<code>$out_seq_no</code>
|
||||
</PossiblyUndefinedVariable>
|
||||
</file>
|
||||
<file src="src/VoIPController.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code>$stream</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<InaccessibleProperty>
|
||||
<code><![CDATA[$this->authKey]]></code>
|
||||
<code><![CDATA[$this->authKey]]></code>
|
||||
</InaccessibleProperty>
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$this->call]]></code>
|
||||
</InvalidArgument>
|
||||
<InvalidPropertyAssignmentValue>
|
||||
<code>$visualization</code>
|
||||
<code>$visualization</code>
|
||||
</InvalidPropertyAssignmentValue>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code><![CDATA[$this->call['g_a']]]></code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
<PropertyNotSetInConstructor>
|
||||
<code>$authKey</code>
|
||||
<code>$bestEndpoint</code>
|
||||
<code>$messageHandler</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Wrappers/DialogHandler.php">
|
||||
<InvalidReturnType>
|
||||
<code><![CDATA[array<int, array>]]></code>
|
||||
|
@ -335,8 +335,7 @@ abstract class InternalDoc
|
||||
}
|
||||
/**
|
||||
* Play files on hold in call.
|
||||
*
|
||||
* @param list<string> $files
|
||||
* @param array<string> $files
|
||||
*/
|
||||
public function callPlayOnHold(int $id, array $files): void
|
||||
{
|
||||
@ -399,13 +398,13 @@ abstract class InternalDoc
|
||||
}
|
||||
/**
|
||||
* Discard call.
|
||||
*
|
||||
* @param int<1, 5> $rating Call rating in stars
|
||||
* @param string $comment Additional comment on call quality.
|
||||
*/
|
||||
public function discardCall(int $id, array $reason = [
|
||||
'_' => 'phoneCallDiscardReasonDisconnect',
|
||||
], array $rating = [
|
||||
]): void
|
||||
public function discardCall(int $id, \danog\MadelineProto\VoIP\DiscardReason $reason = \danog\MadelineProto\VoIP\DiscardReason::HANGUP, ?int $rating = null, ?string $comment = null): void
|
||||
{
|
||||
$this->wrapper->getAPI()->discardCall($id, $reason, $rating);
|
||||
$this->wrapper->getAPI()->discardCall($id, $reason, $rating, $comment);
|
||||
}
|
||||
/**
|
||||
* Discard secret chat.
|
||||
@ -695,6 +694,13 @@ abstract class InternalDoc
|
||||
{
|
||||
return $this->wrapper->getAPI()->getCachedConfig();
|
||||
}
|
||||
/**
|
||||
* Get the call with the specified user ID.
|
||||
*/
|
||||
public function getCallByPeer(int $userId): ?\danog\MadelineProto\VoIP
|
||||
{
|
||||
return $this->wrapper->getAPI()->getCallByPeer($userId);
|
||||
}
|
||||
/**
|
||||
* Get call state.
|
||||
*/
|
||||
|
@ -670,6 +670,7 @@ final class MTProto implements TLCallback, LoggerGetter
|
||||
'reportDest',
|
||||
|
||||
'calls',
|
||||
'callsByPeer',
|
||||
'snitch',
|
||||
];
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ use danog\MadelineProto\TL\TL;
|
||||
use danog\MadelineProto\TL\Types\Button;
|
||||
use danog\MadelineProto\Tools;
|
||||
use danog\MadelineProto\UpdateHandlerType;
|
||||
use danog\MadelineProto\VoIP;
|
||||
use danog\MadelineProto\VoIPController;
|
||||
use Revolt\EventLoop;
|
||||
use Throwable;
|
||||
@ -753,14 +752,16 @@ trait UpdateHandler
|
||||
if ($update['_'] === 'updatePhoneCall') {
|
||||
switch ($update['phone_call']['_']) {
|
||||
case 'phoneCallRequested':
|
||||
return;
|
||||
/*if (!isset($this->calls[$update['phone_call']['id']])) {
|
||||
$update['phone_call'] = $this->calls[$update['phone_call']['id']] = new VoIP(
|
||||
$this,
|
||||
$update['phone_call'],
|
||||
);
|
||||
if (isset($this->calls[$update['phone_call']['id']])) {
|
||||
return;
|
||||
}
|
||||
break;*/
|
||||
$this->calls[$update['phone_call']['id']] = $controller = new VoIPController(
|
||||
$this,
|
||||
$update['phone_call'],
|
||||
);
|
||||
$this->callsByPeer[$controller->public->otherID] = $controller;
|
||||
$update['phone_call'] = $controller->public;
|
||||
break;
|
||||
case 'phoneCallWaiting':
|
||||
if (isset($this->calls[$update['phone_call']['id']])) {
|
||||
return;
|
||||
@ -769,6 +770,7 @@ trait UpdateHandler
|
||||
$this,
|
||||
$update['phone_call']
|
||||
);
|
||||
$this->callsByPeer[$controller->public->otherID] = $controller;
|
||||
$update['phone_call'] = $controller->public;
|
||||
break;
|
||||
case 'phoneCallAccepted':
|
||||
|
24
src/VoIP.php
24
src/VoIP.php
@ -15,10 +15,12 @@ If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\EventHandler\SimpleFilters;
|
||||
use danog\MadelineProto\EventHandler\Update;
|
||||
use danog\MadelineProto\VoIP\CallState;
|
||||
use danog\MadelineProto\VoIP\DiscardReason;
|
||||
|
||||
final class VoIP extends Update
|
||||
final class VoIP extends Update implements SimpleFilters
|
||||
{
|
||||
/** Phone call ID */
|
||||
public readonly int $callID;
|
||||
@ -26,8 +28,6 @@ final class VoIP extends Update
|
||||
public readonly bool $outgoing;
|
||||
/** ID of the other user in the call */
|
||||
public readonly int $otherID;
|
||||
/** ID of the creator of the call */
|
||||
public readonly int $creatorID;
|
||||
/** When was the call created */
|
||||
public readonly int $date;
|
||||
|
||||
@ -44,14 +44,12 @@ final class VoIP extends Update
|
||||
$call['_'] = 'inputPhoneCall';
|
||||
$this->date = $call['date'];
|
||||
$this->callID = $call['id'];
|
||||
if ($call['_'] === 'phoneCallWaiting') {
|
||||
$this->outgoing = false;
|
||||
$this->otherID = $call['participant_id'];
|
||||
$this->creatorID = $call['admin_id'];
|
||||
} else {
|
||||
if ($call['admin_id'] === $API->getSelf()['id']) {
|
||||
$this->outgoing = true;
|
||||
$this->otherID = $call['participant_id'];
|
||||
} else {
|
||||
$this->outgoing = false;
|
||||
$this->otherID = $call['admin_id'];
|
||||
$this->creatorID = $call['participant_id'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,10 +63,13 @@ final class VoIP extends Update
|
||||
}
|
||||
/**
|
||||
* Discard call.
|
||||
*
|
||||
* @param int<1, 5> $rating Call rating in stars
|
||||
* @param string $comment Additional comment on call quality.
|
||||
*/
|
||||
public function discard(array $reason = ['_' => 'phoneCallDiscardReasonDisconnect'], array $rating = []): self
|
||||
public function discard(DiscardReason $reason = DiscardReason::HANGUP, ?int $rating = null, ?string $comment = null): self
|
||||
{
|
||||
$this->getClient()->discardCall($this->callID, $reason, $rating);
|
||||
$this->getClient()->discardCall($this->callID, $reason, $rating, $comment);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -104,6 +105,7 @@ final class VoIP extends Update
|
||||
|
||||
/**
|
||||
* Files to play on hold.
|
||||
* @param array<string> $files
|
||||
*/
|
||||
public function playOnHold(array $files): self
|
||||
{
|
||||
|
@ -44,6 +44,8 @@ trait AuthKeyHandler
|
||||
{
|
||||
/** @var array<int, VoIPController> */
|
||||
private array $calls = [];
|
||||
/** @var array<int, VoIPController> */
|
||||
private array $callsByPeer = [];
|
||||
private array $pendingCalls = [];
|
||||
/**
|
||||
* Request VoIP call.
|
||||
@ -75,6 +77,7 @@ trait AuthKeyHandler
|
||||
$res['a'] = $a;
|
||||
$res['g_a'] = \str_pad($g_a->toBytes(), 256, \chr(0), STR_PAD_LEFT);
|
||||
$this->calls[$res['id']] = $controller = new VoIPController($this, $res);
|
||||
$this->callsByPeer[$controller->public->otherID] = $controller;
|
||||
unset($this->pendingCalls[$user]);
|
||||
$deferred->complete($controller->public);
|
||||
} catch (Throwable $e) {
|
||||
@ -124,10 +127,21 @@ trait AuthKeyHandler
|
||||
|
||||
/**
|
||||
* Discard call.
|
||||
*
|
||||
* @param int<1, 5> $rating Call rating in stars
|
||||
* @param string $comment Additional comment on call quality.
|
||||
*/
|
||||
public function discardCall(int $id, array $reason = ['_' => 'phoneCallDiscardReasonDisconnect'], array $rating = []): void
|
||||
public function discardCall(int $id, DiscardReason $reason = DiscardReason::HANGUP, ?int $rating = null, ?string $comment = null): void
|
||||
{
|
||||
($this->calls[$id] ?? null)?->discard($reason, $rating);
|
||||
($this->calls[$id] ?? null)?->discard($reason, $rating, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the call with the specified user ID.
|
||||
*/
|
||||
public function getCallByPeer(int $userId): ?VoIP
|
||||
{
|
||||
return $this->callsByPeer[$userId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,8 +154,7 @@ trait AuthKeyHandler
|
||||
|
||||
/**
|
||||
* Play files on hold in call.
|
||||
*
|
||||
* @param list<string> $files
|
||||
* @param array<string> $files
|
||||
*/
|
||||
public function callPlayOnHold(int $id, array $files): void
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ use danog\MadelineProto\Tools;
|
||||
use danog\MadelineProto\VoIPController;
|
||||
use Exception;
|
||||
|
||||
use function Amp\delay;
|
||||
use function Amp\Socket\connect;
|
||||
|
||||
final class Endpoint
|
||||
@ -42,7 +43,7 @@ final class Endpoint
|
||||
{
|
||||
$vars = \get_object_vars($this);
|
||||
unset($vars['socket']);
|
||||
return array_keys($vars);
|
||||
return \array_keys($vars);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
@ -104,6 +105,7 @@ final class Endpoint
|
||||
}
|
||||
$pos = 16;
|
||||
}
|
||||
$result = [];
|
||||
if (\fread($payload, 12) === "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
|
||||
switch ($crc = \fread($payload, 4)) {
|
||||
case VoIPController::TLID_REFLECTOR_SELF_INFO:
|
||||
@ -222,7 +224,7 @@ final class Endpoint
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if (!$this->handler->shouldSkip($in_seq_no, $out_seq_no, $ack_mask)) {
|
||||
if (isset($in_seq_no) && isset($out_seq_no) && !$this->handler->shouldSkip($in_seq_no, $out_seq_no, $ack_mask)) {
|
||||
continue;
|
||||
}
|
||||
switch ($result['_']) {
|
||||
@ -331,6 +333,20 @@ final class Endpoint
|
||||
return $result;
|
||||
} while (true);
|
||||
}
|
||||
public function writeReliably(array $data): bool
|
||||
{
|
||||
do {
|
||||
$payload = $this->handler->encryptPacket($data);
|
||||
$seqno = $this->handler->getLastSentSeq();
|
||||
if (!$this->write($payload)) {
|
||||
return false;
|
||||
}
|
||||
delay(0.2);
|
||||
if ($this->handler->acked($seqno)) {
|
||||
return true;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
/**
|
||||
* Write data.
|
||||
*/
|
||||
|
@ -15,9 +15,9 @@ If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace danog\MadelineProto\VoIP;
|
||||
|
||||
use AssertionError;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Tools;
|
||||
use danog\MadelineProto\VoIP;
|
||||
use danog\MadelineProto\VoIPController;
|
||||
|
||||
/**
|
||||
@ -27,11 +27,11 @@ use danog\MadelineProto\VoIPController;
|
||||
*/
|
||||
final class MessageHandler
|
||||
{
|
||||
private array $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];
|
||||
private array $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];
|
||||
private int $outSeqNo = 0;
|
||||
private int $inSeqNo = 0;
|
||||
|
||||
private int $session_out_seq_no = 0;
|
||||
private int $session_in_seq_no = 0;
|
||||
private int $acksForSent = 0;
|
||||
private int $acksForReceived = 0;
|
||||
|
||||
public int $peerVersion = 0;
|
||||
|
||||
@ -40,6 +40,10 @@ final class MessageHandler
|
||||
public readonly string $callID
|
||||
) {
|
||||
}
|
||||
public function getLastSentSeq(): int
|
||||
{
|
||||
return $this->outSeqNo-1;
|
||||
}
|
||||
private static function pack_string(string $object): string
|
||||
{
|
||||
$l = \strlen($object);
|
||||
@ -99,7 +103,7 @@ final class MessageHandler
|
||||
// packetStreamState#3 state:streamTypeState = Packet;
|
||||
case VoIPController::PKT_STREAM_STATE:
|
||||
$message .= \chr($args['id']);
|
||||
$message .= \chr($args['enabled']);
|
||||
$message .= \chr($args['enabled'] ? 1 : 0);
|
||||
break;
|
||||
// streamData flags:int2 stream_id:int6 has_more_flags:flags.1?true length:(flags.0?int16:int8) timestamp:int data:byteArray = StreamData;
|
||||
// packetStreamData#4 stream_data:streamData = Packet;
|
||||
@ -169,21 +173,11 @@ final class MessageHandler
|
||||
break;*/
|
||||
}
|
||||
|
||||
$ack_mask = 0;
|
||||
for ($x=0; $x<32; $x++) {
|
||||
if ($this->received_timestamp_map[$x]>0) {
|
||||
$ack_mask|=1;
|
||||
}
|
||||
if ($x<31) {
|
||||
$ack_mask<<=1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->peerVersion >= 8 || (!$this->peerVersion)) {
|
||||
$payload = \chr($args['_']);
|
||||
$payload .= Tools::packUnsignedInt($this->session_in_seq_no);
|
||||
$payload .= Tools::packUnsignedInt($init ? 0 : $this->session_out_seq_no);
|
||||
$payload .= Tools::packUnsignedInt($ack_mask);
|
||||
$payload .= Tools::packUnsignedInt($this->inSeqNo);
|
||||
$payload .= Tools::packUnsignedInt($init ? 0 : $this->outSeqNo);
|
||||
$payload .= Tools::packUnsignedInt($this->acksForReceived);
|
||||
$payload .= \chr(0);
|
||||
$payload .= $message;
|
||||
} elseif (\in_array($this->instance->getVoIPState(), [VoIPState::WAIT_INIT, VoIPState::WAIT_INIT_ACK], true)) {
|
||||
@ -201,9 +195,9 @@ final class MessageHandler
|
||||
$flags = $flags | ($args['_'] << 24);
|
||||
$payload .= Tools::packUnsignedInt($flags);
|
||||
$payload .= $this->callID;
|
||||
$payload .= Tools::packUnsignedInt($this->session_in_seq_no);
|
||||
$payload .= Tools::packUnsignedInt($init ? 0 : $this->session_out_seq_no);
|
||||
$payload .= Tools::packUnsignedInt($ack_mask);
|
||||
$payload .= Tools::packUnsignedInt($this->inSeqNo);
|
||||
$payload .= Tools::packUnsignedInt($init ? 0 : $this->outSeqNo);
|
||||
$payload .= Tools::packUnsignedInt($this->acksForReceived);
|
||||
$payload .= VoIPController::PROTO_ID;
|
||||
if ($flags & 2) {
|
||||
$payload .= $this->pack_string($args['extra']);
|
||||
@ -216,12 +210,12 @@ final class MessageHandler
|
||||
$payload .= Tools::random(8);
|
||||
$payload .= \chr(7);
|
||||
$payload .= Tools::random(7);
|
||||
$message = \chr($args['_']).Tools::packUnsignedInt($this->session_in_seq_no).Tools::packUnsignedInt($init ? 0 : $this->session_out_seq_no).Tools::packUnsignedInt($ack_mask).$message;
|
||||
$message = \chr($args['_']).Tools::packUnsignedInt($this->inSeqNo).Tools::packUnsignedInt($init ? 0 : $this->outSeqNo).Tools::packUnsignedInt($this->acksForReceived).$message;
|
||||
|
||||
$payload .= $this->pack_string($message);
|
||||
}
|
||||
if (!$init) {
|
||||
$this->session_out_seq_no++;
|
||||
$this->outSeqNo++;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
@ -229,53 +223,35 @@ final class MessageHandler
|
||||
|
||||
public function shouldSkip(int $last_ack_id, int $packet_seq_no, int $ack_mask): bool
|
||||
{
|
||||
if ($packet_seq_no > $this->session_in_seq_no) {
|
||||
$diff = $packet_seq_no - $this->session_in_seq_no;
|
||||
if ($packet_seq_no > $this->inSeqNo) {
|
||||
$diff = $packet_seq_no - $this->inSeqNo;
|
||||
if ($diff > 31) {
|
||||
$this->received_timestamp_map = \array_fill(0, 32, 0);
|
||||
$this->acksForReceived = 0;
|
||||
} else {
|
||||
$remaining = 32-$diff;
|
||||
for ($x = 0; $x < $remaining; $x++) {
|
||||
$this->received_timestamp_map[$diff+$x] = $this->received_timestamp_map[$x];
|
||||
}
|
||||
for ($x = 1; $x < $diff; $x++) {
|
||||
$this->received_timestamp_map[$x] = 0;
|
||||
}
|
||||
$this->received_timestamp_map[0] = \microtime(true);
|
||||
$this->acksForReceived = (($this->acksForReceived << ($diff+1)) & 0xFFFF_FFFF) | 1;
|
||||
}
|
||||
$this->session_in_seq_no = $packet_seq_no;
|
||||
} elseif (($diff = $this->session_in_seq_no - $packet_seq_no) < 32) {
|
||||
if (!$this->received_timestamp_map[$diff]) {
|
||||
$this->inSeqNo = $packet_seq_no;
|
||||
} elseif (($diff = $this->inSeqNo - $packet_seq_no) < 32) {
|
||||
if ($this->acksForReceived & (1 << ($diff+1))) {
|
||||
Logger::log("Got duplicate $packet_seq_no");
|
||||
return false;
|
||||
}
|
||||
$this->received_timestamp_map[$diff] = \microtime(true);
|
||||
$this->acksForReceived |= 1 << ($diff+1);
|
||||
} else {
|
||||
Logger::log("Packet $packet_seq_no is out of order and too late");
|
||||
return false;
|
||||
}
|
||||
if ($last_ack_id > $this->session_out_seq_no) {
|
||||
$diff = $last_ack_id - $this->session_out_seq_no;
|
||||
if ($diff > 31) {
|
||||
$this->remote_ack_timestamp_map = \array_fill(0, 32, 0);
|
||||
} else {
|
||||
$remaining = 32-$diff;
|
||||
for ($x = 0; $x < $remaining; $x++) {
|
||||
$this->remote_ack_timestamp_map[$diff+$x] = $this->remote_ack_timestamp_map[$x];
|
||||
}
|
||||
for ($x = 1; $x < $diff; $x++) {
|
||||
$this->remote_ack_timestamp_map[$x] = 0;
|
||||
}
|
||||
$this->remote_ack_timestamp_map[0] = \microtime(true);
|
||||
}
|
||||
$this->session_out_seq_no = $last_ack_id;
|
||||
|
||||
for ($x = 1; $x < 32; $x++) {
|
||||
if (!$this->remote_ack_timestamp_map[$x] && ($ack_mask >> 32-$x) & 1) {
|
||||
$this->remote_ack_timestamp_map[$x] = \microtime(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->inSeqNo = $packet_seq_no;
|
||||
$this->acksForSent = $ack_mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function acked(int $seq): bool
|
||||
{
|
||||
$diff = $this->outSeqNo - $seq;
|
||||
if ($diff > 31) {
|
||||
throw new AssertionError("Already forgot about packet!");
|
||||
}
|
||||
return (bool) ($this->acksForReceived & (1 << ($diff+1)));
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use danog\MadelineProto\MTProtoTools\Crypt;
|
||||
use danog\MadelineProto\Stream\Common\FileBufferedStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\VoIP\CallState;
|
||||
use danog\MadelineProto\VoIP\DiscardReason;
|
||||
use danog\MadelineProto\VoIP\Endpoint;
|
||||
use danog\MadelineProto\VoIP\MessageHandler;
|
||||
use danog\MadelineProto\VoIP\VoIPState;
|
||||
@ -96,23 +97,23 @@ final class VoIPController
|
||||
|
||||
private array $call;
|
||||
|
||||
/** @var list<string> */
|
||||
/** @var array<string> */
|
||||
private array $holdFiles = [];
|
||||
/** @var list<string> */
|
||||
private array $inputFiles = [];
|
||||
private int $holdIndex = 0;
|
||||
|
||||
/**
|
||||
* @var array<Endpoint>
|
||||
*/
|
||||
private array $sockets = [];
|
||||
private ?Endpoint $bestEndpoint = null;
|
||||
private Endpoint $bestEndpoint;
|
||||
private bool $pendingPing = true;
|
||||
private ?string $timeoutWatcher = null;
|
||||
private float $lastIncomingTimestamp = 0.0;
|
||||
private float $lastOutgoingTimestamp = 0.0;
|
||||
private int $opusTimestamp = 0;
|
||||
private SplQueue $packetQueue;
|
||||
private array $tempHoldFiles = [];
|
||||
|
||||
/** Auth key */
|
||||
private readonly string $authKey;
|
||||
@ -134,10 +135,10 @@ final class VoIPController
|
||||
$call['_'] = 'inputPhoneCall';
|
||||
$this->packetQueue = new SplQueue;
|
||||
$this->call = $call;
|
||||
if ($call['_'] === 'phoneCallWaiting') {
|
||||
$this->callState = CallState::INCOMING;
|
||||
} else {
|
||||
if ($this->public->outgoing) {
|
||||
$this->callState = CallState::REQUESTED;
|
||||
} else {
|
||||
$this->callState = CallState::INCOMING;
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +166,7 @@ final class VoIPController
|
||||
}
|
||||
if ($e->rpc === 'CALL_ALREADY_DECLINED') {
|
||||
$this->API->logger->logger(Lang::$current_lang['call_already_declined']);
|
||||
$this->discard(['_' => 'phoneCallDiscardReasonHangup']);
|
||||
$this->discard(DiscardReason::HANGUP);
|
||||
return false;
|
||||
}
|
||||
throw $e;
|
||||
@ -202,6 +203,8 @@ final class VoIPController
|
||||
$b = BigInteger::randomRange(Magic::$two, $dh_config['p']->subtract(Magic::$two));
|
||||
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
||||
Crypt::checkG($g_b, $dh_config['p']);
|
||||
|
||||
$this->callState = CallState::ACCEPTED;
|
||||
try {
|
||||
$this->API->methodCallAsyncRead('phone.acceptCall', ['peer' => ['id' => $this->call['id'], 'access_hash' => $this->call['access_hash'], '_' => 'inputPhoneCall'], 'g_b' => $g_b->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_reflector' => true, 'udp_p2p' => true, 'min_layer' => 65, 'max_layer' => 92]]);
|
||||
} catch (RPCErrorException $e) {
|
||||
@ -211,15 +214,13 @@ final class VoIPController
|
||||
}
|
||||
if ($e->rpc === 'CALL_ALREADY_DECLINED') {
|
||||
$this->API->logger->logger(Lang::$current_lang['call_already_declined']);
|
||||
$this->discard(['_' => 'phoneCallDiscardReasonHangup']);
|
||||
$this->discard(DiscardReason::HANGUP);
|
||||
return $this;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
$this->call['b'] = $b;
|
||||
|
||||
$this->callState = CallState::ACCEPTED;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -236,7 +237,7 @@ final class VoIPController
|
||||
}
|
||||
$this->API->logger->logger(\sprintf(Lang::$current_lang['call_completing'], $this->public->otherID), Logger::VERBOSE);
|
||||
$dh_config = $this->API->getDhConfig();
|
||||
if (\hash('sha256', $params['g_a_or_b'], true) != $this->call['g_a_hash']) {
|
||||
if (\hash('sha256', (string) $params['g_a_or_b'], true) !== (string) $this->call['g_a_hash']) {
|
||||
throw new SecurityException('Invalid g_a!');
|
||||
}
|
||||
$params['g_a_or_b'] = new BigInteger((string) $params['g_a_or_b'], 256);
|
||||
@ -267,13 +268,11 @@ final class VoIPController
|
||||
public function __wakeup(): void
|
||||
{
|
||||
if ($this->callState === CallState::RUNNING) {
|
||||
$this->lastIncomingTimestamp = microtime(true);
|
||||
$this->lastIncomingTimestamp = \microtime(true);
|
||||
$this->startReadLoop();
|
||||
if ($this->voipState === VoIPState::ESTABLISHED) {
|
||||
$diff = (int) ((microtime(true) - $this->lastOutgoingTimestamp) * 1000);
|
||||
Logger::log("Missed $diff milliseconds of audio due to script shutdown...");
|
||||
$this->opusTimestamp += $diff;
|
||||
$this->opusTimestamp -= ($this->opusTimestamp) % 60;
|
||||
$diff = (int) ((\microtime(true) - $this->lastOutgoingTimestamp) * 1000);
|
||||
$this->opusTimestamp += $diff - ($diff % 60);
|
||||
EventLoop::queue($this->startWriteLoop(...));
|
||||
}
|
||||
}
|
||||
@ -291,12 +290,17 @@ final class VoIPController
|
||||
|
||||
/**
|
||||
* Discard call.
|
||||
*
|
||||
* @param int<1, 5> $rating Call rating in stars
|
||||
* @param string $comment Additional comment on call quality.
|
||||
*/
|
||||
public function discard(array $reason = ['_' => 'phoneCallDiscardReasonDisconnect'], array $rating = []): self
|
||||
public function discard(DiscardReason $reason = DiscardReason::HANGUP, ?int $rating = null, ?string $comment = null): self
|
||||
{
|
||||
if ($this->callState === CallState::ENDED) {
|
||||
return $this;
|
||||
}
|
||||
$this->API->cleanupCall($this->public->callID);
|
||||
|
||||
Logger::log("Now closing $this");
|
||||
if (isset($this->timeoutWatcher)) {
|
||||
EventLoop::cancel($this->timeoutWatcher);
|
||||
@ -306,21 +310,26 @@ final class VoIPController
|
||||
foreach ($this->sockets as $socket) {
|
||||
$socket->disconnect();
|
||||
}
|
||||
$this->packetQueue = new SplQueue;
|
||||
Logger::log("Closed all sockets, discarding $this");
|
||||
|
||||
$this->API->logger->logger(\sprintf(Lang::$current_lang['call_discarding'], $this->public->callID), Logger::VERBOSE);
|
||||
try {
|
||||
$this->API->methodCallAsyncRead('phone.discardCall', ['peer' => $this->call, 'duration' => \time() - $this->public->date, 'connection_id' => 0, 'reason' => $reason]);
|
||||
$this->API->methodCallAsyncRead('phone.discardCall', ['peer' => $this->call, 'duration' => \time() - $this->public->date, 'connection_id' => 0, 'reason' => ['_' => match ($reason) {
|
||||
DiscardReason::BUSY => 'phoneCallDiscardReasonBusy',
|
||||
DiscardReason::HANGUP => 'phoneCallDiscardReasonHangup',
|
||||
DiscardReason::DISCONNECTED => 'phoneCallDiscardReasonDisconnect',
|
||||
DiscardReason::MISSED => 'phoneCallDiscardReasonMissed'
|
||||
}]]);
|
||||
} catch (RPCErrorException $e) {
|
||||
if (!\in_array($e->rpc, ['CALL_ALREADY_DECLINED', 'CALL_ALREADY_ACCEPTED'], true)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if (!empty($rating)) {
|
||||
if ($rating !== null) {
|
||||
$this->API->logger->logger(\sprintf('Setting rating for call %s...', $this->call), Logger::VERBOSE);
|
||||
$this->API->methodCallAsyncRead('phone.setCallRating', ['peer' => $this->call, 'rating' => $rating['rating'], 'comment' => $rating['comment']]);
|
||||
$this->API->methodCallAsyncRead('phone.setCallRating', ['peer' => $this->call, 'rating' => $rating, 'comment' => $comment]);
|
||||
}
|
||||
$this->API->cleanupCall($this->public->callID);
|
||||
$this->callState = CallState::ENDED;
|
||||
return $this;
|
||||
}
|
||||
@ -395,7 +404,7 @@ final class VoIPController
|
||||
break;
|
||||
|
||||
case self::PKT_INIT_ACK:
|
||||
if (!$this->bestEndpoint) {
|
||||
if (!isset($this->bestEndpoint)) {
|
||||
$this->bestEndpoint = $socket;
|
||||
$this->startWriteLoop();
|
||||
}
|
||||
@ -436,52 +445,90 @@ final class VoIPController
|
||||
}
|
||||
$this->timeoutWatcher = EventLoop::repeat(10, function (): void {
|
||||
if (\microtime(true) - $this->lastIncomingTimestamp > 10) {
|
||||
$this->discard(['_' => 'phoneCallDiscardReasonDisconnect']);
|
||||
$this->discard(DiscardReason::DISCONNECTED);
|
||||
}
|
||||
});
|
||||
}
|
||||
private bool $muted = false;
|
||||
private bool $playingHold = false;
|
||||
private function pullPacket(): bool
|
||||
{
|
||||
$file = \array_shift($this->inputFiles);
|
||||
if ($file) {
|
||||
$this->playingHold = false;
|
||||
} else {
|
||||
$this->playingHold = true;
|
||||
if (!$this->holdFiles) {
|
||||
return false;
|
||||
}
|
||||
$file = $this->holdFiles[($this->holdIndex++) % \count($this->holdFiles)];
|
||||
}
|
||||
$it = $this->openFile($file);
|
||||
foreach ($it->opusPackets as $packet) {
|
||||
$this->packetQueue->enqueue($packet);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Start write loop.
|
||||
*/
|
||||
private function startWriteLoop(): void
|
||||
{
|
||||
$this->voipState = VoIPState::ESTABLISHED;
|
||||
Logger::log("Call established, sending OPUS data!");
|
||||
Logger::log("Call established in $this, sending OPUS data!");
|
||||
|
||||
$this->tempHoldFiles = [];
|
||||
$delay = $this->muted ? 0.2 : 0.06;
|
||||
$t = \microtime(true) + $delay;
|
||||
while (true) {
|
||||
if ($this->packetQueue->isEmpty()) {
|
||||
$file = \array_shift($this->inputFiles);
|
||||
if (!$file) {
|
||||
if (empty($this->tempHoldFiles)) {
|
||||
$this->tempHoldFiles = $this->holdFiles;
|
||||
}
|
||||
if (empty($this->tempHoldFiles)) {
|
||||
return;
|
||||
}
|
||||
$file = \array_shift($this->tempHoldFiles);
|
||||
if ($this->packetQueue->isEmpty() && !$this->pullPacket()) {
|
||||
if (!$this->muted) {
|
||||
$this->bestEndpoint->writeReliably([
|
||||
'_' => self::PKT_STREAM_STATE,
|
||||
'id' => 0,
|
||||
'enabled' => false
|
||||
]);
|
||||
$this->muted = true;
|
||||
$delay = 0.2;
|
||||
}
|
||||
$it = $this->openFile($file);
|
||||
foreach ($it->opusPackets as $packet) {
|
||||
$this->packetQueue->enqueue($packet);
|
||||
$packet = $this->messageHandler->encryptPacket([
|
||||
'_' => self::PKT_NOP
|
||||
]);
|
||||
} else {
|
||||
if ($this->muted) {
|
||||
$this->bestEndpoint->writeReliably([
|
||||
'_' => self::PKT_STREAM_STATE,
|
||||
'id' => 0,
|
||||
'enabled' => true
|
||||
]);
|
||||
$this->muted = false;
|
||||
$delay = 0.06;
|
||||
$this->opusTimestamp = 0;
|
||||
}
|
||||
}
|
||||
$t = \microtime(true) + 0.060;
|
||||
while (!$this->packetQueue->isEmpty()) {
|
||||
$packet = $this->messageHandler->encryptPacket(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $this->packetQueue->dequeue(), 'timestamp' => $this->opusTimestamp]);
|
||||
|
||||
//Logger::log("Writing {$this->timestamp} in $this!");
|
||||
$cur = \microtime(true);
|
||||
$diff = $t - $cur;
|
||||
if ($diff > 0) {
|
||||
delay($diff);
|
||||
}
|
||||
$this->bestEndpoint->write($packet);
|
||||
$this->lastOutgoingTimestamp = $cur;
|
||||
$t += 0.060;
|
||||
|
||||
$packet = $this->messageHandler->encryptPacket([
|
||||
'_' => self::PKT_STREAM_DATA,
|
||||
'stream_id' => 0,
|
||||
'data' => $this->packetQueue->dequeue(),
|
||||
'timestamp' => $this->opusTimestamp
|
||||
]);
|
||||
$this->opusTimestamp += 60;
|
||||
}
|
||||
//Logger::log("Writing {$this->opusTimestamp} in $this!");
|
||||
$cur = \microtime(true);
|
||||
$diff = $t - $cur;
|
||||
if ($diff > 0) {
|
||||
delay($diff);
|
||||
}
|
||||
|
||||
$this->bestEndpoint->write($packet);
|
||||
|
||||
if ($diff > 0) {
|
||||
$cur += $diff;
|
||||
} else {
|
||||
Logger::log("We're late while sending audio data!");
|
||||
}
|
||||
$this->lastOutgoingTimestamp = $cur;
|
||||
|
||||
$t += $delay;
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -500,6 +547,9 @@ final class VoIPController
|
||||
public function play(string $file): self
|
||||
{
|
||||
$this->inputFiles[] = $file;
|
||||
if ($this->playingHold) {
|
||||
$this->packetQueue = new SplQueue;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -509,13 +559,13 @@ final class VoIPController
|
||||
*/
|
||||
public function then(string $file): self
|
||||
{
|
||||
$this->inputFiles[] = $file;
|
||||
|
||||
return $this;
|
||||
return $this->play($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Files to play on hold.
|
||||
*
|
||||
* @param array<string> $files
|
||||
*/
|
||||
public function playOnHold(array $files): self
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user