From df666ff7245fc1c5e11ed562e52fd57e3ff75923 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Thu, 27 May 2021 16:43:12 +0400 Subject: [PATCH] Implement more robust reconnect management. --- .../calls/group/calls_group_call.cpp | 269 +++++++++++------- .../calls/group/calls_group_call.h | 28 +- .../calls/group/calls_group_panel.cpp | 4 +- Telegram/ThirdParty/tgcalls | 2 +- 4 files changed, 199 insertions(+), 104 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 5958489e2..2a669251f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -396,7 +396,7 @@ GroupCall::GroupCall( if (_instance) { updateInstanceMuteState(); } - if (_mySsrc + if (_joinState.ssrc && (!_initialMuteStateSent || state == MuteState::Active)) { _initialMuteStateSent = true; maybeSendMutedUpdate(previous); @@ -632,13 +632,19 @@ void GroupCall::checkGlobalShortcutAvailability() { } void GroupCall::setState(State state) { - if (_state.current() == State::Failed) { + const auto current = _state.current(); + if (current == State::Failed) { return; - } else if (_state.current() == State::FailedHangingUp + } else if (current == State::Ended && state != State::Failed) { + return; + } else if (current == State::FailedHangingUp && state != State::Failed) { + return; + } else if (current == State::HangingUp + && state != State::Ended && state != State::Failed) { return; } - if (_state.current() == state) { + if (current == state) { return; } _state = state; @@ -902,14 +908,6 @@ void GroupCall::rejoinWithHash(const QString &hash) { } void GroupCall::setJoinAs(not_null<PeerData*> as) { - if (_joinAs != as) { - if (_cameraOutgoing) { - _cameraOutgoing->setState(Webrtc::VideoState::Inactive); - } - if (_screenOutgoing) { - _screenOutgoing->setState(Webrtc::VideoState::Inactive); - } - } _joinAs = as; if (const auto chat = _peer->asChat()) { chat->setGroupCallDefaultJoinAs(_joinAs->id); @@ -931,13 +929,22 @@ void GroupCall::rejoin(not_null<PeerData*> as) { && state() != State::Joined && state() != State::Connecting) { return; + } else if (_joinState.action != JoinAction::None) { + return; } - _mySsrc = 0; + if (_joinAs != as) { + toggleVideo(false); + toggleScreenSharing(std::nullopt); + } + + _joinState.action = JoinAction::Joining; + _joinState.ssrc = 0; _initialMuteStateSent = false; setState(State::Joining); - ensureControllerCreated(); - setInstanceMode(InstanceMode::None); + if (!tryCreateController()) { + setInstanceMode(InstanceMode::None); + } applyMeInCallLocally(); LOG(("Call Info: Requesting join payload.")); @@ -945,7 +952,12 @@ void GroupCall::rejoin(not_null<PeerData*> as) { const auto weak = base::make_weak(&_instanceGuard); _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { - crl::on_main(weak, [=, payload = std::move(payload)]{ + crl::on_main(weak, [=, payload = std::move(payload)] { + if (state() != State::Joining) { + _joinState.finish(); + checkNextJoinAction(); + return; + } const auto ssrc = payload.audioSsrc; LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(ssrc)); @@ -970,8 +982,9 @@ void GroupCall::rejoin(not_null<PeerData*> as) { MTP_string(_joinHash), MTP_dataJSON(MTP_bytes(json)) )).done([=](const MTPUpdates &updates) { - _mySsrc = ssrc; + _joinState.finish(ssrc); _mySsrcs.emplace(ssrc); + setState((_instanceState.current() == InstanceState::Disconnected) ? State::Connecting @@ -984,11 +997,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) { if (wasVideoMuted == isSharingCamera()) { sendSelfUpdate(SendUpdateType::VideoMuted); } - if (_screenSsrc && isSharingScreen()) { - LOG(("Call Info: Screen rejoin after rejoin().")); - rejoinPresentation(); - } + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); }).fail([=](const MTP::Error &error) { + _joinState.finish(); + const auto type = error.type(); LOG(("Call Error: Could not join, error: %1").arg(type)); @@ -1012,52 +1025,91 @@ void GroupCall::rejoin(not_null<PeerData*> as) { }); } -void GroupCall::joinLeavePresentation() { - if (_screenOutgoing - && _screenOutgoing->state() == Webrtc::VideoState::Active) { - rejoinPresentation(); +void GroupCall::checkNextJoinAction() { + if (_joinState.action != JoinAction::None) { + return; + } else if (_joinState.nextActionPending) { + _joinState.nextActionPending = false; + const auto state = _state.current(); + if (state != State::HangingUp && state != State::FailedHangingUp) { + rejoin(); + } else { + leave(); + } + } else if (!_joinState.ssrc) { + rejoin(); + } else if (_screenJoinState.action != JoinAction::None + || !_screenJoinState.nextActionPending) { + return; } else { - leavePresentation(); + _screenJoinState.nextActionPending = false; + if (isSharingScreen()) { + rejoinPresentation(); + } else { + leavePresentation(); + } } } void GroupCall::rejoinPresentation() { - _screenSsrc = 0; - ensureScreencastCreated(); - setScreenInstanceMode(InstanceMode::None); - LOG(("Call Info: Requesting join payload.")); + if (!_joinState.ssrc + || _screenJoinState.action == JoinAction::Joining + || !isSharingScreen()) { + return; + } else if (_screenJoinState.action != JoinAction::None) { + _screenJoinState.nextActionPending = true; + return; + } + + _screenJoinState.action = JoinAction::Joining; + _screenJoinState.ssrc = 0; + if (!tryCreateScreencast()) { + setScreenInstanceMode(InstanceMode::None); + } + LOG(("Call Info: Requesting join screen payload.")); const auto weak = base::make_weak(&_screenInstanceGuard); _screenInstance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { crl::on_main(weak, [=, payload = std::move(payload)]{ - if (!_screenInstance) { + if (!isSharingScreen() || !_joinState.ssrc) { + _screenJoinState.finish(); + checkNextJoinAction(); return; } + const auto withMainSsrc = _joinState.ssrc; const auto ssrc = payload.audioSsrc; - LOG(("Call Info: Join payload received, joining with ssrc: %1." + LOG(("Call Info: Join screen payload received, ssrc: %1." ).arg(ssrc)); const auto json = QByteArray::fromStdString(payload.json); - _api.request(MTPphone_JoinGroupCallPresentation( - inputCall(), - MTP_dataJSON(MTP_bytes(json)) - )).done([=](const MTPUpdates &updates) { - _screenSsrc = ssrc; + _api.request( + MTPphone_JoinGroupCallPresentation( + inputCall(), + MTP_dataJSON(MTP_bytes(json))) + ).done([=](const MTPUpdates &updates) { + _screenJoinState.finish(ssrc); _mySsrcs.emplace(ssrc); + _peer->session().api().applyUpdates(updates); + checkNextJoinAction(); }).fail([=](const MTP::Error &error) { + _screenJoinState.finish(); + const auto type = error.type(); - LOG(("Call Error: " - "Could not screen join, error: %1").arg(type)); if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { - rejoinPresentation(); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); } else if (type == u"GROUPCALL_JOIN_MISSING"_q || type == u"GROUPCALL_FORBIDDEN"_q) { - _screenSsrc = ssrc; - rejoin(); + if (_joinState.ssrc != withMainSsrc) { + // We've rejoined, rejoin presentation again. + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); + } } else { - _screenSsrc = 0; - setScreenEndpoint(std::string()); + LOG(("Call Error: " + "Could not screen join, error: %1").arg(type)); + _screenOutgoing->setState(Webrtc::VideoState::Inactive); } }).send(); }); @@ -1066,21 +1118,31 @@ void GroupCall::rejoinPresentation() { void GroupCall::leavePresentation() { destroyScreencast(); - if (!_screenSsrc) { + if (!_screenJoinState.ssrc) { + setScreenEndpoint(std::string()); + return; + } else if (_screenJoinState.action == JoinAction::Leaving) { + return; + } else if (_screenJoinState.action != JoinAction::None) { + _screenJoinState.nextActionPending = true; return; } - _api.request(MTPphone_LeaveGroupCallPresentation( - inputCall() - )).done([=](const MTPUpdates &updates) { - _screenSsrc = 0; - setScreenEndpoint(std::string()); + _api.request( + MTPphone_LeaveGroupCallPresentation(inputCall()) + ).done([=](const MTPUpdates &updates) { + _screenJoinState.finish(); + _peer->session().api().applyUpdates(updates); + setScreenEndpoint(std::string()); + checkNextJoinAction(); }).fail([=](const MTP::Error &error) { + _screenJoinState.finish(); + const auto type = error.type(); LOG(("Call Error: " "Could not screen leave, error: %1").arg(type)); - _screenSsrc = 0; setScreenEndpoint(std::string()); + checkNextJoinAction(); }).send(); } @@ -1111,7 +1173,7 @@ void GroupCall::applyMeInCallLocally() { : nullptr; const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) - | (_mySsrc ? Flag(0) : Flag::f_left) + | (_joinState.ssrc ? Flag(0) : Flag::f_left) | Flag::f_self | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. @@ -1131,7 +1193,7 @@ void GroupCall::applyMeInCallLocally() { peerToMTP(_joinAs->id), MTP_int(date), MTP_int(lastActive), - MTP_int(_mySsrc), + MTP_int(_joinState.ssrc), MTP_int(volume), MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), @@ -1255,13 +1317,23 @@ void GroupCall::finish(FinishType type) { || state == State::Ended || state == State::Failed) { return; - } - if (!_mySsrc) { + } else if (_joinState.action == JoinAction::None && !_joinState.ssrc) { setState(finalState); return; } - setState(hangupState); + _joinState.nextActionPending = true; + checkNextJoinAction(); +} + +void GroupCall::leave() { + Expects(_joinState.action == JoinAction::None); + + _joinState.action = JoinAction::Leaving; + + const auto finalState = (_state.current() == State::HangingUp) + ? State::Ended + : State::Failed; // We want to leave request still being sent and processed even if // the call is already destroyed. @@ -1269,7 +1341,7 @@ void GroupCall::finish(FinishType type) { const auto weak = base::make_weak(this); session->api().request(MTPphone_LeaveGroupCall( inputCall(), - MTP_int(_mySsrc) + MTP_int(base::take(_joinState.ssrc)) )).done([=](const MTPUpdates &result) { // Here 'this' could be destroyed by updates, so we set Ended after // updates being handled, but in a guarded way. @@ -1322,8 +1394,8 @@ void GroupCall::setMuted(MuteState mute) { applyMeInCallLocally(); } if (mutedByAdmin()) { - toggleVideo(false); - toggleScreenSharing(std::nullopt); + //toggleVideo(false); + //toggleScreenSharing(std::nullopt); } }; if (mute == MuteState::Active || mute == MuteState::PushToTalk) { @@ -1427,7 +1499,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse( void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { if (data.vid().v == _id) { LOG(("Call Info: Hangup after groupCallDiscarded.")); - _mySsrc = 0; + _joinState.finish(); hangup(); } } @@ -1510,7 +1582,7 @@ void GroupCall::applyQueuedSelfUpdates() { void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { if (data.is_left()) { - if (data.vsource().v == _mySsrc) { + if (data.vsource().v == _joinState.ssrc) { // I was removed from the call, rejoin. LOG(("Call Info: " "Rejoin after got 'left' with my ssrc.")); @@ -1518,20 +1590,20 @@ void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { rejoin(); } return; - } else if (data.vsource().v != _mySsrc) { + } else if (data.vsource().v != _joinState.ssrc) { if (!_mySsrcs.contains(data.vsource().v)) { // I joined from another device, hangup. LOG(("Call Info: " "Hangup after '!left' with ssrc %1, my %2." ).arg(data.vsource().v - ).arg(_mySsrc)); - _mySsrc = 0; + ).arg(_joinState.ssrc)); + _joinState.finish(); hangup(); } else { LOG(("Call Info: " "Some old 'self' with '!left' and ssrc %1, my %2." ).arg(data.vsource().v - ).arg(_mySsrc)); + ).arg(_joinState.ssrc)); } return; } @@ -1646,13 +1718,10 @@ void GroupCall::ensureOutgoingVideo() { _instance->setVideoCapture(_cameraCapture); } _cameraCapture->setState(tgcalls::VideoState::Active); - markEndpointActive({ _joinAs, _cameraEndpoint }, true); - } else { - if (_cameraCapture) { - _cameraCapture->setState(tgcalls::VideoState::Inactive); - } - markEndpointActive({ _joinAs, _cameraEndpoint }, false); + } else if (_cameraCapture) { + _cameraCapture->setState(tgcalls::VideoState::Inactive); } + markEndpointActive({ _joinAs, _cameraEndpoint }, isSharingCamera()); sendSelfUpdate(SendUpdateType::VideoMuted); applyMeInCallLocally(); }, _lifetime); @@ -1686,14 +1755,12 @@ void GroupCall::ensureOutgoingVideo() { _screenInstance->setVideoCapture(_screenCapture); } _screenCapture->setState(tgcalls::VideoState::Active); - markEndpointActive({ _joinAs, _screenEndpoint }, true); - } else { - if (_screenCapture) { - _screenCapture->setState(tgcalls::VideoState::Inactive); - } - markEndpointActive({ _joinAs, _screenEndpoint }, false); + } else if (_screenCapture) { + _screenCapture->setState(tgcalls::VideoState::Inactive); } - joinLeavePresentation(); + markEndpointActive({ _joinAs, _screenEndpoint }, isSharingScreen()); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); }, _lifetime); } @@ -1741,9 +1808,9 @@ void GroupCall::toggleRecording(bool enabled, const QString &title) { }).send(); } -void GroupCall::ensureControllerCreated() { +bool GroupCall::tryCreateController() { if (_instance) { - return; + return false; } const auto &settings = Core::App().settings(); @@ -1830,11 +1897,12 @@ void GroupCall::ensureControllerCreated() { _instance->addIncomingVideoOutput(endpoint, std::move(sink.data)); } //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); + return true; } -void GroupCall::ensureScreencastCreated() { +bool GroupCall::tryCreateScreencast() { if (_screenInstance) { - return; + return false; } //const auto &settings = Core::App().settings(); @@ -1870,6 +1938,7 @@ void GroupCall::ensureScreencastCreated() { LOG(("Call Info: Creating group screen instance")); _screenInstance = std::make_unique<tgcalls::GroupInstanceCustomImpl>( std::move(descriptor)); + return true; } void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) { @@ -2136,10 +2205,13 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { auto checkNow = false; const auto now = crl::now(); for (const auto &[ssrcOrZero, value] : data.updates) { - const auto ssrc = ssrcOrZero ? ssrcOrZero : _mySsrc; + const auto ssrc = ssrcOrZero ? ssrcOrZero : _joinState.ssrc; + if (!ssrc) { + continue; + } const auto level = value.level; const auto voice = value.voice; - const auto me = (ssrc == _mySsrc); + const auto me = (ssrc == _joinState.ssrc); _levelUpdates.fire(LevelUpdate{ .ssrc = ssrc, .value = level, @@ -2205,7 +2277,7 @@ void GroupCall::checkLastSpoke() { } // Ignore my levels from microphone if I'm already muted. - if (ssrc != _mySsrc + if (ssrc != _joinState.ssrc || muted() == MuteState::Active || muted() == MuteState::PushToTalk) { real->applyLastSpoke(ssrc, when, now); @@ -2221,34 +2293,37 @@ void GroupCall::checkLastSpoke() { } void GroupCall::checkJoined() { - if (state() != State::Connecting || !_id || !_mySsrc) { + if (state() != State::Connecting || !_id || !_joinState.ssrc) { return; } - auto sources = QVector<MTPint>(1, MTP_int(_mySsrc)); - if (_screenSsrc) { - sources.push_back(MTP_int(_screenSsrc)); + auto sources = QVector<MTPint>(1, MTP_int(_joinState.ssrc)); + if (_screenJoinState.ssrc) { + sources.push_back(MTP_int(_screenJoinState.ssrc)); } _api.request(MTPphone_CheckGroupCall( inputCall(), MTP_vector<MTPint>(std::move(sources)) )).done([=](const MTPVector<MTPint> &result) { - if (!ranges::contains(result.v, MTP_int(_mySsrc))) { + if (!ranges::contains(result.v, MTP_int(_joinState.ssrc))) { LOG(("Call Info: Rejoin after no _mySsrc in checkGroupCall.")); - rejoin(); + _joinState.nextActionPending = true; + checkNextJoinAction(); } else { if (state() == State::Connecting) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } - if (_screenSsrc - && !ranges::contains(result.v, MTP_int(_screenSsrc)) - && isSharingScreen()) { + if (_screenJoinState.ssrc + && !ranges::contains( + result.v, + MTP_int(_screenJoinState.ssrc))) { LOG(("Call Info: " "Screen rejoin after _screenSsrc not found.")); - rejoinPresentation(); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); } } }).fail([=](const MTP::Error &error) { - LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." + LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); }).send(); @@ -2343,7 +2418,7 @@ void GroupCall::setScreenInstanceMode(InstanceMode mode) { using Mode = tgcalls::GroupConnectionMode; _screenInstance->setConnectionMode([&] { - switch (_instanceMode) { + switch (_screenInstanceMode) { case InstanceMode::None: return Mode::GroupConnectionModeNone; case InstanceMode::Rtc: return Mode::GroupConnectionModeRtc; case InstanceMode::Stream: return Mode::GroupConnectionModeBroadcast; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 9e15e760e..91cdf02a6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -369,6 +369,8 @@ private: using GlobalShortcutValue = base::GlobalShortcutValue; struct SinkPointer; + static constexpr uint32 kDisabledSsrc = uint32(-1); + struct LoadingPart { std::shared_ptr<LoadPartTask> task; mtpRequestId requestId = 0; @@ -389,6 +391,21 @@ private: RaiseHand, VideoMuted, }; + enum class JoinAction { + None, + Joining, + Leaving, + }; + struct JoinState { + uint32 ssrc = 0; + JoinAction action = JoinAction::None; + bool nextActionPending = false; + + void finish(uint32 updatedSsrc = 0) { + action = JoinAction::None; + ssrc = updatedSsrc; + } + }; [[nodiscard]] bool mediaChannelDescriptionsFill( not_null<MediaChannelDescriptionsTask*> task, @@ -399,9 +416,9 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); - void ensureControllerCreated(); + bool tryCreateController(); void destroyController(); - void ensureScreencastCreated(); + bool tryCreateScreencast(); void destroyScreencast(); void setState(State state); @@ -412,14 +429,15 @@ private: void updateInstanceVolumes(); void applyMeInCallLocally(); void rejoin(); + void leave(); void rejoin(not_null<PeerData*> as); void setJoinAs(not_null<PeerData*> as); void saveDefaultJoinAs(not_null<PeerData*> as); void subscribeToReal(not_null<Data::GroupCall*> real); void setScheduledDate(TimeId date); - void joinLeavePresentation(); void rejoinPresentation(); void leavePresentation(); + void checkNextJoinAction(); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); @@ -496,8 +514,8 @@ private: uint64 _id = 0; uint64 _accessHash = 0; - uint32 _mySsrc = 0; - uint32 _screenSsrc = 0; + JoinState _joinState; + JoinState _screenJoinState; std::string _cameraEndpoint; std::string _screenEndpoint; TimeId _scheduleDate = 0; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 5ba925bd2..965e8d271 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1394,7 +1394,9 @@ void Panel::refreshTopButton() { } void Panel::chooseShareScreenSource() { - Ui::DesktopCapture::ChooseSource(this); + if (!_call->mutedByAdmin()) { + Ui::DesktopCapture::ChooseSource(this); + } } void Panel::chooseJoinAs() { diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index a41c973ba..d3eab9af8 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit a41c973baa5a6681d7495c093fa48f3d1495d591 +Subproject commit d3eab9af84bad9dd9a0853078feee3e53d365ef5