diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 0fbb39e44d..920e4483ca 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -318,6 +318,8 @@ PRIVATE calls/calls_controller.cpp calls/calls_controller.h calls/calls_controller_tgvoip.h + calls/calls_controller_webrtc.cpp + calls/calls_controller_webrtc.h calls/calls_emoji_fingerprint.cpp calls/calls_emoji_fingerprint.h calls/calls_instance.cpp diff --git a/Telegram/Patches/build_ffmpeg_win.sh b/Telegram/Patches/build_ffmpeg_win.sh index 05a6db0ec1..1b24980ee1 100644 --- a/Telegram/Patches/build_ffmpeg_win.sh +++ b/Telegram/Patches/build_ffmpeg_win.sh @@ -13,6 +13,8 @@ pacman --noconfirm -S pkg-config PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH" ./configure --toolchain=msvc \ +--extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ +--extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ --extra-ldflags="-libpath:$FullExecPath/../opus/win32/VS2015/Win32/Release" \ --disable-programs \ --disable-doc \ diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index f6caa032d4..596c2c0f0a 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1726,8 +1726,9 @@ void Updates::feedUpdate(const MTPUpdate &update) { auto &d = update.c_updateEncryptedMessagesRead(); } break; - case mtpc_updatePhoneCall: { - Core::App().calls().handleUpdate(&session(), update.c_updatePhoneCall()); + case mtpc_updatePhoneCall: + case mtpc_updatePhoneCallSignalingData: { + Core::App().calls().handleUpdate(&session(), update); } break; case mtpc_updateUserBlocked: { diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 0a72867281..41ef4b96de 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -32,6 +32,7 @@ namespace { constexpr auto kMinLayer = 65; constexpr auto kHangupTimeoutMs = 5000; constexpr auto kSha256Size = 32; +const auto kDefaultVersion = "2.4.4"_q; void AppendEndpoint( std::vector &list, @@ -71,10 +72,6 @@ uint64 ComputeFingerprint(bytes::const_span authKey) { | (gsl::to_integer(hash[12])); } -[[nodiscard]] std::vector CollectVersions() { - return { TgVoip::getVersion() }; -} - [[nodiscard]] QVector WrapVersions( const std::vector &data) { auto result = QVector(); @@ -86,13 +83,11 @@ uint64 ComputeFingerprint(bytes::const_span authKey) { } [[nodiscard]] QVector CollectVersionsForApi() { - return WrapVersions(CollectVersions()); + return WrapVersions(CollectControllerVersions()); } } // namespace -Call::Delegate::~Delegate() = default; - Call::Call( not_null delegate, not_null user, @@ -170,7 +165,7 @@ void Call::startOutgoing() { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(TgVoip::getConnectionMaxLayer()), + MTP_int(ControllerMaxLayer()), MTP_vector(CollectVersionsForApi())) )).done([=](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); @@ -251,7 +246,7 @@ void Call::actuallyAnswer() { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(TgVoip::getConnectionMaxLayer()), + MTP_int(ControllerMaxLayer()), MTP_vector(CollectVersionsForApi())) )).done([=](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); @@ -322,6 +317,25 @@ void Call::startWaitingTrack() { _waitingTrack->playInLoop(); } +void Call::sendSignalingData(const QByteArray &data) { + _api.request(MTPphone_SendSignalingData( + MTP_inputPhoneCall( + MTP_long(_id), + MTP_long(_accessHash)), + MTP_bytes(data) + )).done([=](const MTPBool &result) { + if (!mtpIsTrue(result)) { + finish(FinishType::Failed); + } + }).fail([=](const RPCError &error) { + handleRequestError(error); + }).send(); +} + +void Call::displayNextFrame(QImage frame) { + _frames.fire(std::move(frame)); +} + float64 Call::getWaitingSoundPeakValue() const { if (_waitingTrack) { auto when = crl::now() + kSoundSampleMs / 4; @@ -462,6 +476,14 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { Unexpected("phoneCall type inside an existing call handleUpdate()"); } +bool Call::handleSignalingData( + const MTPDupdatePhoneCallSignalingData &data) { + if (data.vphone_call_id().v != _id || !_controller) { + return false; + } + return _controller->receiveSignalingData(data.vdata().v); +} + void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { Expects(_type == Type::Outgoing); @@ -494,9 +516,9 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(TgVoip::getConnectionMaxLayer()), + MTP_int(ControllerMaxLayer()), MTP_vector(CollectVersionsForApi())) - )).done([this](const MTPphone_PhoneCall &result) { + )).done([=](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); auto &call = result.c_phone_phoneCall(); @@ -508,7 +530,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { } createAndStartController(call.vphone_call().c_phoneCall()); - }).fail([this](const RPCError &error) { + }).fail([=](const RPCError &error) { handleRequestError(error); }).send(); } @@ -597,14 +619,20 @@ void Call::createAndStartController(const MTPDphoneCall &call) { return static_cast(byte); }) | ranges::to_vector; + const auto version = call.vprotocol().match([&]( + const MTPDphoneCallProtocol &data) { + return data.vlibrary_versions().v; + }).value(0, MTP_bytes(kDefaultVersion)).v; _controller = MakeController( - "2.4.4", + version.toStdString(), config, TgVoipPersistentState(), endpoints, proxy.host.empty() ? nullptr : &proxy, TgVoipNetworkType::Unknown, - encryptionKey); + encryptionKey, + [=](QByteArray data) { sendSignalingData(data); }, + [=](QImage frame) { displayNextFrame(frame); }); const auto raw = _controller.get(); raw->setOnStateUpdated([=](TgVoipState state) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 265e74671b..f82e4ed70a 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -48,7 +48,7 @@ public: virtual void playSound(Sound sound) = 0; virtual void requestMicrophonePermissionOrFail(Fn result) = 0; - virtual ~Delegate(); + virtual ~Delegate() = default; }; @@ -70,6 +70,7 @@ public: void start(bytes::const_span random); bool handleUpdate(const MTPPhoneCall &call); + bool handleSignalingData(const MTPDupdatePhoneCallSignalingData &data); enum State { Starting, @@ -110,6 +111,10 @@ public: return _muteChanged; } + rpl::producer frames() const { + return _frames.events(); + } + crl::time getDurationMs() const; float64 getWaitingSoundPeakValue() const; @@ -144,6 +149,8 @@ private: void startOutgoing(); void startIncoming(); void startWaitingTrack(); + void sendSignalingData(const QByteArray &data); + void displayNextFrame(QImage frame); void generateModExpFirst(bytes::const_span randomSeed); void handleControllerStateChange( @@ -181,6 +188,7 @@ private: bool _mute = false; base::Observable _muteChanged; + rpl::event_stream _frames; DhConfig _dhConfig; bytes::vector _ga; diff --git a/Telegram/SourceFiles/calls/calls_controller.cpp b/Telegram/SourceFiles/calls/calls_controller.cpp index d20f65e5dd..9b82a8e6c4 100644 --- a/Telegram/SourceFiles/calls/calls_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_controller.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_controller.h" #include "calls/calls_controller_tgvoip.h" +#include "calls/calls_controller_webrtc.h" namespace Calls { @@ -18,7 +19,20 @@ namespace Calls { const std::vector &endpoints, const TgVoipProxy *proxy, TgVoipNetworkType initialNetworkType, - const TgVoipEncryptionKey &encryptionKey) { + const TgVoipEncryptionKey &encryptionKey, + Fn sendSignalingData, + Fn displayNextFrame) { + if (version == WebrtcController::Version()) { + return std::make_unique( + config, + persistentState, + endpoints, + proxy, + initialNetworkType, + encryptionKey, + std::move(sendSignalingData), + std::move(displayNextFrame)); + } return std::make_unique( config, persistentState, @@ -28,4 +42,12 @@ namespace Calls { encryptionKey); } +std::vector CollectControllerVersions() { + return { WebrtcController::Version(), TgVoipController::Version() }; +} + +int ControllerMaxLayer() { + return TgVoip::getConnectionMaxLayer(); +} + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_controller.h b/Telegram/SourceFiles/calls/calls_controller.h index 9c98d91be8..cab175e25e 100644 --- a/Telegram/SourceFiles/calls/calls_controller.h +++ b/Telegram/SourceFiles/calls/calls_controller.h @@ -26,6 +26,7 @@ public: virtual void setInputVolume(float level) = 0; virtual void setOutputVolume(float level) = 0; virtual void setAudioOutputDuckingEnabled(bool enabled) = 0; + virtual bool receiveSignalingData(const QByteArray &data) = 0; virtual std::string getLastError() = 0; virtual std::string getDebugInfo() = 0; @@ -48,6 +49,11 @@ public: const std::vector &endpoints, const TgVoipProxy *proxy, TgVoipNetworkType initialNetworkType, - const TgVoipEncryptionKey &encryptionKey); + const TgVoipEncryptionKey &encryptionKey, + Fn sendSignalingData, + Fn displayNextFrame); + +[[nodiscard]] std::vector CollectControllerVersions(); +[[nodiscard]] int ControllerMaxLayer(); } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_controller_tgvoip.h b/Telegram/SourceFiles/calls/calls_controller_tgvoip.h index ebcb2bdd1f..2576600435 100644 --- a/Telegram/SourceFiles/calls/calls_controller_tgvoip.h +++ b/Telegram/SourceFiles/calls/calls_controller_tgvoip.h @@ -33,7 +33,7 @@ public: return TgVoip::getVersion(); } - [[nodiscard]] std::string version() override { + std::string version() override { return Version(); } void setNetworkType(TgVoipNetworkType networkType) override { @@ -63,6 +63,9 @@ public: void setAudioOutputDuckingEnabled(bool enabled) override { _impl->setAudioOutputDuckingEnabled(enabled); } + bool receiveSignalingData(const QByteArray &data) override { + return false; + } std::string getLastError() override { return _impl->getLastError(); } @@ -81,8 +84,7 @@ public: void setOnStateUpdated(Fn onStateUpdated) override { _impl->setOnStateUpdated(std::move(onStateUpdated)); } - void setOnSignalBarsUpdated( - Fn onSignalBarsUpdated) override { + void setOnSignalBarsUpdated(Fn onSignalBarsUpdated) override { _impl->setOnSignalBarsUpdated(std::move(onSignalBarsUpdated)); } TgVoipFinalState stop() override { diff --git a/Telegram/SourceFiles/calls/calls_controller_webrtc.cpp b/Telegram/SourceFiles/calls/calls_controller_webrtc.cpp new file mode 100644 index 0000000000..f29b068530 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_controller_webrtc.cpp @@ -0,0 +1,175 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "calls/calls_controller_webrtc.h" + +#include "webrtc/webrtc_call_context.h" + +namespace Calls { +namespace { + +using namespace Webrtc; + +[[nodiscard]] CallConnectionDescription ConvertEndpoint(const TgVoipEndpoint &data) { + return CallConnectionDescription{ + .ip = QString::fromStdString(data.host.ipv4), + .ipv6 = QString::fromStdString(data.host.ipv6), + .peerTag = QByteArray( + reinterpret_cast(data.peerTag), + base::array_size(data.peerTag)), + .connectionId = data.endpointId, + .port = data.port, + }; +} + +[[nodiscard]] CallContext::Config MakeContextConfig( + const TgVoipConfig &config, + const TgVoipPersistentState &persistentState, + const std::vector &endpoints, + const TgVoipProxy *proxy, + TgVoipNetworkType initialNetworkType, + const TgVoipEncryptionKey &encryptionKey, + Fn sendSignalingData, + Fn displayNextFrame) { + Expects(!endpoints.empty()); + + auto result = CallContext::Config{ + .proxy = (proxy + ? ProxyServer{ + .host = QString::fromStdString(proxy->host), + .username = QString::fromStdString(proxy->login), + .password = QString::fromStdString(proxy->password), + .port = proxy->port } + : ProxyServer()), + .dataSaving = (config.dataSaving != TgVoipDataSaving::Never), + .key = QByteArray( + reinterpret_cast(encryptionKey.value.data()), + encryptionKey.value.size()), + .outgoing = encryptionKey.isOutgoing, + .primary = ConvertEndpoint(endpoints.front()), + .alternatives = endpoints | ranges::view::drop( + 1 + ) | ranges::view::transform(ConvertEndpoint) | ranges::to_vector, + .maxLayer = config.maxApiLayer, + .allowP2P = config.enableP2P, + .sendSignalingData = std::move(sendSignalingData), + .displayNextFrame = std::move(displayNextFrame), + }; + return result; +} + +} // namespace + +WebrtcController::WebrtcController( + const TgVoipConfig &config, + const TgVoipPersistentState &persistentState, + const std::vector &endpoints, + const TgVoipProxy *proxy, + TgVoipNetworkType initialNetworkType, + const TgVoipEncryptionKey &encryptionKey, + Fn sendSignalingData, + Fn displayNextFrame) +: _impl(std::make_unique(MakeContextConfig( + config, + persistentState, + endpoints, + proxy, + initialNetworkType, + encryptionKey, + std::move(sendSignalingData), + std::move(displayNextFrame)))) { +} + +WebrtcController::~WebrtcController() = default; + +std::string WebrtcController::Version() { + return CallContext::Version().toStdString(); +} + +std::string WebrtcController::version() { + return Version(); +} + +void WebrtcController::setNetworkType(TgVoipNetworkType networkType) { +} + +void WebrtcController::setMuteMicrophone(bool muteMicrophone) { + _impl->setIsMuted(muteMicrophone); +} + +void WebrtcController::setAudioOutputGainControlEnabled(bool enabled) { +} + +void WebrtcController::setEchoCancellationStrength(int strength) { +} + +void WebrtcController::setAudioInputDevice(std::string id) { +} + +void WebrtcController::setAudioOutputDevice(std::string id) { +} + +void WebrtcController::setInputVolume(float level) { +} + +void WebrtcController::setOutputVolume(float level) { +} + +void WebrtcController::setAudioOutputDuckingEnabled(bool enabled) { +} + +bool WebrtcController::receiveSignalingData(const QByteArray &data) { + return _impl->receiveSignalingData(data); +} + +std::string WebrtcController::getLastError() { + return {}; +} + +std::string WebrtcController::getDebugInfo() { + return _impl->getDebugInfo().toStdString(); +} + +int64_t WebrtcController::getPreferredRelayId() { + return 0; +} + +TgVoipTrafficStats WebrtcController::getTrafficStats() { + return {}; +} + +TgVoipPersistentState WebrtcController::getPersistentState() { + return TgVoipPersistentState{}; +} + +void WebrtcController::setOnStateUpdated( + Fn onStateUpdated) { + _stateUpdatedLifetime.destroy(); + _impl->state().changes( + ) | rpl::start_with_next([=](CallState state) { + onStateUpdated([&] { + switch (state) { + case CallState::Initializing: return TgVoipState::WaitInit; + case CallState::Reconnecting: return TgVoipState::Reconnecting; + case CallState::Connected: return TgVoipState::Established; + case CallState::Failed: return TgVoipState::Failed; + } + Unexpected("State value in Webrtc::CallContext::state."); + }()); + }, _stateUpdatedLifetime); +} + +void WebrtcController::setOnSignalBarsUpdated( + Fn onSignalBarsUpdated) { +} + +TgVoipFinalState WebrtcController::stop() { + _impl->stop(); + return TgVoipFinalState(); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_controller_webrtc.h b/Telegram/SourceFiles/calls/calls_controller_webrtc.h new file mode 100644 index 0000000000..2cb99c6691 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_controller_webrtc.h @@ -0,0 +1,60 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "calls/calls_controller.h" + +namespace Webrtc { +class CallContext; +} // namespace Webrtc + +namespace Calls { + +class WebrtcController final : public Controller { +public: + WebrtcController( + const TgVoipConfig &config, + const TgVoipPersistentState &persistentState, + const std::vector &endpoints, + const TgVoipProxy *proxy, + TgVoipNetworkType initialNetworkType, + const TgVoipEncryptionKey &encryptionKey, + Fn sendSignalingData, + Fn displayNextFrame); + ~WebrtcController(); + + [[nodiscard]] static std::string Version(); + + std::string version() override; + void setNetworkType(TgVoipNetworkType networkType) override; + void setMuteMicrophone(bool muteMicrophone) override; + void setAudioOutputGainControlEnabled(bool enabled) override; + void setEchoCancellationStrength(int strength) override; + void setAudioInputDevice(std::string id) override; + void setAudioOutputDevice(std::string id) override; + void setInputVolume(float level) override; + void setOutputVolume(float level) override; + void setAudioOutputDuckingEnabled(bool enabled) override; + bool receiveSignalingData(const QByteArray &data) override; + std::string getLastError() override; + std::string getDebugInfo() override; + int64_t getPreferredRelayId() override; + TgVoipTrafficStats getTrafficStats() override; + TgVoipPersistentState getPersistentState() override; + void setOnStateUpdated(Fn onStateUpdated) override; + void setOnSignalBarsUpdated(Fn onSignalBarsUpdated) override; + TgVoipFinalState stop() override; + +private: + const std::unique_ptr _impl; + + rpl::lifetime _stateUpdatedLifetime; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 5c700db204..c1aab32fbc 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -232,13 +232,19 @@ void Instance::refreshServerConfig(not_null session) { UpdateConfig(std::string(json.data(), json.size())); }).fail([=](const RPCError &error) { _serverConfigRequestSession = nullptr; - }).send(); + }).send(); } void Instance::handleUpdate( not_null session, - const MTPDupdatePhoneCall& update) { - handleCallUpdate(session, update.vphone_call()); + const MTPUpdate &update) { + update.match([&](const MTPDupdatePhoneCall &data) { + handleCallUpdate(session, data.vphone_call()); + }, [&](const MTPDupdatePhoneCallSignalingData &data) { + handleSignalingData(data); + }, [](const auto &) { + Unexpected("Update type in Calls::Instance::handleUpdate."); + }); } void Instance::showInfoPanel(not_null call) { @@ -291,6 +297,14 @@ void Instance::handleCallUpdate( } } +void Instance::handleSignalingData( + const MTPDupdatePhoneCallSignalingData &data) { + if (!_currentCall || !_currentCall->handleSignalingData(data)) { + DEBUG_LOG(("API Warning: unexpected call signaling data %1" + ).arg(data.vphone_call_id().v)); + } +} + bool Instance::alreadyInCall() { return (_currentCall && _currentCall->state() != Call::State::Busy); } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 92a6ad58a5..41dac34007 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -35,7 +35,7 @@ public: void startOutgoingCall(not_null user); void handleUpdate( not_null session, - const MTPDupdatePhoneCall &update); + const MTPUpdate &update); void showInfoPanel(not_null call); [[nodiscard]] Call *currentCall() const; [[nodiscard]] rpl::producer currentCallValue() const; @@ -59,6 +59,8 @@ private: void destroyCurrentPanel(); void requestMicrophonePermissionOrFail(Fn onSuccess) override; + void handleSignalingData(const MTPDupdatePhoneCallSignalingData &data); + void refreshDhConfig(); void refreshServerConfig(not_null session); bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index b44612e632..1bfdc9ebaa 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -400,6 +400,11 @@ void Panel::initControls() { _decline->finishAnimating(); _cancel->finishAnimating(); + + _call->frames() | rpl::start_with_next([=](QImage frame) { + _videoFrame = std::move(frame); + update(); + }, lifetime()); } void Panel::reinitControls() { @@ -742,6 +747,17 @@ void Panel::paintEvent(QPaintEvent *e) { p.fillRect(0, _contentTop, width(), height() - _contentTop, brush); } + if (!_videoFrame.isNull()) { + const auto to = rect().marginsRemoved(_padding); + p.save(); + p.setClipRect(to); + const auto big = _videoFrame.size().scaled(to.size(), Qt::KeepAspectRatioByExpanding); + const auto pos = QPoint((to.width() - big.width()) / 2, (to.height() - big.height()) / 2); + auto hq = PainterHighQualityEnabler(p); + p.drawImage(QRect(pos, big), _videoFrame); + p.restore(); + } + if (_signalBars->isDisplayed()) { paintSignalBarsBg(p); } diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 697fe26a31..d518bb87da 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -158,6 +158,8 @@ private: QPixmap _bottomCache; QPixmap _cache; + QImage _videoFrame; + }; } // namespace Calls diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 2b1cd3b5b0..eb5be18405 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 2b1cd3b5b0c909834aea00e0590435a63d318468 +Subproject commit eb5be18405d47a226f6a197280176f82b3e903bd