diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 2075be7ab..7bfef7ba6 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -191,6 +191,7 @@ private: Ended, Failed, }; + void handleRequestError(const RPCError &error); void handleControllerError(const QString &error); void finish( diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 0dcd19e6b..15ff367de 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -25,7 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "calls/calls_panel.h" //#include "webrtc/webrtc_video_track.h" //#include "webrtc/webrtc_media_devices.h" +#include "data/data_changes.h" #include "data/data_channel.h" +#include "data/data_group_call.h" //#include "data/data_session.h" //#include "facades.h" @@ -49,6 +51,7 @@ GroupCall::GroupCall( , _channel(channel) , _api(&_channel->session().mtp()) { if (inputCall.c_inputGroupCall().vid().v) { + _state = State::Joining; join(inputCall); } else { start(); @@ -59,19 +62,51 @@ GroupCall::~GroupCall() { destroyController(); } +void GroupCall::setState(State state) { + if (_state.current() == State::Failed) { + return; + } else if (_state.current() == State::FailedHangingUp + && state != State::Failed) { + return; + } + if (_state.current() == state) { + return; + } + _state = state; + + if (false + || state == State::Ended + || state == State::Failed) { + // Destroy controller before destroying Call Panel, + // so that the panel hide animation is smooth. + destroyController(); + } + switch (state) { + case State::Ended: + _delegate->groupCallFinished(this); + break; + case State::Failed: + _delegate->groupCallFailed(this); + break; + } +} + void GroupCall::start() { const auto randomId = rand_value(); _api.request(MTPphone_CreateGroupCall( _channel->inputChannel, MTP_int(randomId) )).done([=](const MTPUpdates &result) { + _acceptFields = true; _channel->session().api().applyUpdates(result); + _acceptFields = false; }).fail([=](const RPCError &error) { int a = error.code(); }).send(); } void GroupCall::join(const MTPInputGroupCall &inputCall) { + setState(State::Joining); inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; @@ -91,10 +126,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { } auto root = QJsonObject(); + const auto ssrc = payload.ssrc; root.insert("ufrag", QString::fromStdString(payload.ufrag)); root.insert("pwd", QString::fromStdString(payload.pwd)); root.insert("fingerprints", fingerprints); - root.insert("ssrc", int(payload.ssrc)); + root.insert("ssrc", double(payload.ssrc)); const auto json = QJsonDocument(root).toJson( QJsonDocument::Compact); @@ -105,6 +141,9 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { inputCall, MTP_dataJSON(MTP_bytes(json)) )).done([=](const MTPUpdates &updates) { + _mySsrc = ssrc; + setState(State::Joined); + _channel->session().api().applyUpdates(updates); }).fail([=](const RPCError &error) { int a = error.code(); @@ -112,6 +151,77 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { }); }); }); + _channel->setCall(inputCall); + + _channel->session().changes().peerFlagsValue( + _channel, + Data::PeerUpdate::Flag::GroupCall + ) | rpl::start_with_next([=] { + checkParticipants(); + }, _lifetime); +} + +void GroupCall::checkParticipants() { + if (!joined()) { + return; + } + const auto call = _channel->call(); + if (!call || call->id() != _id) { + finish(FinishType::Ended); + return; + } + const auto &sources = call->sources(); + if (sources.size() != call->fullCount() || sources.empty()) { + call->reload(); + return; + } + auto ssrcs = std::vector(); + ssrcs.reserve(sources.size()); + for (const auto source : sources) { + if (source != _mySsrc) { + ssrcs.push_back(source); + } + } + _instance->setSsrcs(std::move(ssrcs)); + _instance->setIsMuted(false); +} + +void GroupCall::hangup() { + finish(FinishType::Ended); +} + +void GroupCall::finish(FinishType type) { + Expects(type != FinishType::None); + + const auto finalState = (type == FinishType::Ended) + ? State::Ended + : State::Failed; + const auto hangupState = (type == FinishType::Ended) + ? State::HangingUp + : State::FailedHangingUp; + const auto state = _state.current(); + if (state == State::HangingUp + || state == State::FailedHangingUp + || state == State::Ended + || state == State::Failed) { + return; + } + if (!joined()) { + setState(finalState); + return; + } + + setState(hangupState); + _api.request(MTPphone_LeaveGroupCall( + inputCall() + )).done([=](const MTPUpdates &result) { + // Here 'this' could be destroyed by updates, so we set Ended after + // updates being handled, but in a guarded way. + crl::on_main(this, [=] { setState(finalState); }); + _channel->session().api().applyUpdates(result); + }).fail([=](const RPCError &error) { + setState(finalState); + }).send(); } void GroupCall::setMuted(bool mute) { @@ -123,7 +233,13 @@ void GroupCall::setMuted(bool mute) { bool GroupCall::handleUpdate(const MTPGroupCall &call) { return call.match([&](const MTPDgroupCall &data) { - if (_id != data.vid().v + if (_acceptFields) { + if (_instance || _id) { + return false; + } + join(MTP_inputGroupCall(data.vid(), data.vaccess_hash())); + return true; + } else if (_id != data.vid().v || _accessHash != data.vaccess_hash().v || !_instance) { return false; @@ -182,35 +298,10 @@ bool GroupCall::handleUpdate(const MTPGroupCall &call) { }); } _instance->setJoinResponsePayload(payload); - _api.request(MTPphone_GetGroupParticipants( - inputCall(), - MTP_int(0), - MTP_int(10) - )).done([=](const MTPphone_GroupParticipants &result) { - auto sources = std::vector(); - result.match([&](const MTPDphone_groupParticipants &data) { - for (const auto &p : data.vparticipants().v) { - p.match([&](const MTPDgroupCallParticipant &data) { - if (data.vuser_id().v != _channel->session().userId()) { - sources.push_back(data.vsource().v); - } - }); - } - }); - _instance->setSsrcs(std::move(sources)); - _instance->setIsMuted(false); - }).fail([=](const RPCError &error) { - int a = error.code(); - }).send(); + checkParticipants(); }); } return true; - }, [&](const MTPDgroupCallPrivate &data) { - if (_instance || _id) { - return false; - } - join(MTP_inputGroupCall(data.vid(), data.vaccess_hash())); - return true; }, [&](const MTPDgroupCallDiscarded &data) { if (data.vid().v != _id) { return false; diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 412a8e168..6286f3de5 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -25,6 +25,9 @@ public: public: virtual ~Delegate() = default; + virtual void groupCallFinished(not_null call) = 0; + virtual void groupCallFailed(not_null call) = 0; + }; GroupCall( @@ -38,6 +41,7 @@ public: } void start(); + void hangup(); void join(const MTPInputGroupCall &inputCall); bool handleUpdate(const MTPGroupCall &call); @@ -48,6 +52,25 @@ public: [[nodiscard]] rpl::producer mutedValue() const { return _muted.value(); } + [[nodiscard]] bool joined() const { + return _mySsrc != 0; + } + + enum State { + Creating, + Joining, + Joined, + FailedHangingUp, + Failed, + HangingUp, + Ended, + }; + [[nodiscard]] State state() const { + return _state.current(); + } + [[nodiscard]] rpl::producer stateValue() const { + return _state.value(); + } void setCurrentAudioDevice(bool input, const QString &deviceId); void setAudioVolume(bool input, float level); @@ -58,22 +81,34 @@ public: } private: + enum class FinishType { + None, + Ended, + Failed, + }; + void handleRequestError(const RPCError &error); void handleControllerError(const QString &error); void createAndStartController(); void destroyController(); + void checkParticipants(); + + void setState(State state); + void finish(FinishType type); [[nodiscard]] MTPInputGroupCall inputCall() const; const not_null _delegate; const not_null _channel; MTP::Sender _api; - crl::time _startTime = 0; + rpl::variable _state = State::Creating; rpl::variable _muted = false; + bool _acceptFields = false; uint64 _id = 0; uint64 _accessHash = 0; + uint32 _mySsrc = 0; std::unique_ptr _instance; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index b0cd55288..9dd83f4ec 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_panel.h" #include "data/data_user.h" +#include "data/data_group_call.h" #include "data/data_channel.h" #include "data/data_session.h" #include "media/audio/media_audio_track.h" @@ -93,6 +94,18 @@ void Instance::callRedial(not_null call) { } } +void Instance::groupCallFinished(not_null call) { + crl::on_main(call, [=] { + destroyGroupCall(call); + }); +} + +void Instance::groupCallFailed(not_null call) { + crl::on_main(call, [=] { + destroyGroupCall(call); + }); +} + void Instance::playSound(Sound sound) { switch (sound) { case Sound::Busy: { @@ -351,6 +364,12 @@ void Instance::handleCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPGroupCall &call) { + const auto callId = call.match([](const auto &data) { + return data.vid().v; + }); + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(call); + } if (_currentGroupCall) { _currentGroupCall->handleUpdate(call); } @@ -359,6 +378,12 @@ void Instance::handleGroupCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPDupdateGroupCallParticipants &update) { + const auto callId = update.vcall().match([](const auto &data) { + return data.vid().v; + }); + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(update); + } } void Instance::handleSignalingData( @@ -424,7 +449,7 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn _currentCall->hangup(); } if (inGroupCall()) { - //_currentGroupCall->hangup(); // #TODO calls + _currentGroupCall->hangup(); } Ui::show(Box(tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), crl::guard(this, [=] { Platform::OpenSystemSettingsForPermission(type); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 390292b97..2c2c2a761 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -70,6 +70,9 @@ private: requestPermissionsOrFail(std::move(onSuccess)); } + void groupCallFinished(not_null call) override; + void groupCallFailed(not_null call) override; + using Sound = Call::Delegate::Sound; void playSound(Sound sound) override; void createCall(not_null user, Call::Type type, bool video); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 147c23ed4..97192f76d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -29,10 +29,19 @@ GroupCall::GroupCall( , _accessHash(accessHash) { } +GroupCall::~GroupCall() { + _channel->session().api().request(_participantsRequestId).cancel(); + _channel->session().api().request(_reloadRequestId).cancel(); +} + uint64 GroupCall::id() const { return _id; } +not_null GroupCall::channel() const { + return _channel; +} + MTPInputGroupCall GroupCall::input() const { return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash)); } @@ -42,8 +51,12 @@ auto GroupCall::participants() const return _participants; } +const base::flat_set &GroupCall::sources() const { + return _sources; +} + void GroupCall::requestParticipants() { - if (_participantsRequestId) { + if (_participantsRequestId || _reloadRequestId) { return; } else if (_participants.size() >= _fullCount && _allReceived) { return; @@ -58,31 +71,9 @@ void GroupCall::requestParticipants() { MTP_int(kRequestPerPage) )).done([=](const MTPphone_GroupParticipants &result) { result.match([&](const MTPDphone_groupParticipants &data) { - _fullCount = data.vcount().v; _channel->owner().processUsers(data.vusers()); - for (const auto &p : data.vparticipants().v) { - p.match([&](const MTPDgroupCallParticipant &data) { - const auto userId = data.vuser_id().v; - const auto user = _channel->owner().user(userId); - const auto value = Participant{ - .user = user, - .date = data.vdate().v, - .source = data.vsource().v, - .muted = data.is_muted(), - .canSelfUnmute = data.is_can_self_unmute(), - .left = data.is_left() - }; - const auto i = ranges::find( - _participants, - user, - &Participant::user); - if (i == end(_participants)) { - _participants.push_back(value); - } else { - *i = value; - } - }); - } + applyParticipantsSlice(data.vparticipants().v); + _fullCount = data.vcount().v; if (!_allReceived && (data.vparticipants().v.size() < kRequestPerPage)) { _allReceived = true; @@ -91,16 +82,17 @@ void GroupCall::requestParticipants() { _fullCount = _participants.size(); } }); - ranges::sort(_participants, std::greater<>(), &Participant::date); _channel->session().changes().peerUpdated( _channel, PeerUpdate::Flag::GroupCall); + _participantsRequestId = 0; }).fail([=](const RPCError &error) { - _allReceived = true; _fullCount = _participants.size(); + _allReceived = true; _channel->session().changes().peerUpdated( _channel, PeerUpdate::Flag::GroupCall); + _participantsRequestId = 0; }).send(); } @@ -112,4 +104,118 @@ bool GroupCall::participantsLoaded() const { return _allReceived; } +void GroupCall::applyUpdate(const MTPGroupCall &update) { + applyCall(update, false); +} + +void GroupCall::applyCall(const MTPGroupCall &call, bool force) { + call.match([&](const MTPDgroupCall &data) { + const auto changed = (_version != data.vversion().v) + || (_fullCount != data.vparticipants_count().v); + if (!force && !changed) { + return; + } else if (!force && _version > data.vversion().v) { + reload(); + return; + } + _version = data.vversion().v; + _fullCount = data.vparticipants_count().v; + }, [&](const MTPDgroupCallDiscarded &data) { + const auto changed = (_duration != data.vduration().v) + || !_finished; + if (!force && !changed) { + return; + } + _finished = true; + _duration = data.vduration().v; + }); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); +} + +void GroupCall::reload() { + if (_reloadRequestId) { + return; + } else if (_participantsRequestId) { + _channel->session().api().request(_participantsRequestId).cancel(); + _participantsRequestId = 0; + } + _reloadRequestId = _channel->session().api().request( + MTPphone_GetGroupCall(input()) + ).done([=](const MTPphone_GroupCall &result) { + result.match([&](const MTPDphone_groupCall &data) { + _channel->owner().processUsers(data.vusers()); + _participants.clear(); + _sources.clear(); + applyParticipantsSlice(data.vparticipants().v); + for (const auto &source : data.vsources().v) { + _sources.emplace(source.v); + } + _fullCount = _sources.size(); + if (_participants.size() > _fullCount) { + _fullCount = _participants.size(); + } + _allReceived = (_fullCount == _participants.size()); + applyCall(data.vcall(), true); + }); + _reloadRequestId = 0; + }).fail([=](const RPCError &error) { + _reloadRequestId = 0; + }).send(); +} + +void GroupCall::applyParticipantsSlice( + const QVector &list) { + for (const auto &participant : list) { + participant.match([&](const MTPDgroupCallParticipant &data) { + const auto userId = data.vuser_id().v; + const auto user = _channel->owner().user(userId); + const auto i = ranges::find( + _participants, + user, + &Participant::user); + if (data.is_left()) { + if (i != end(_participants)) { + _sources.remove(i->source); + _participants.erase(i); + } + if (_fullCount > _participants.size()) { + --_fullCount; + } + return; + } + const auto value = Participant{ + .user = user, + .date = data.vdate().v, + .source = uint32(data.vsource().v), + .muted = data.is_muted(), + .canSelfUnmute = data.is_can_self_unmute(), + }; + if (i == end(_participants)) { + _participants.push_back(value); + ++_fullCount; + } else { + *i = value; + } + _sources.emplace(uint32(data.vsource().v)); + }); + } + ranges::sort(_participants, std::greater<>(), &Participant::date); +} + +void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) { + if (update.vversion().v <= _version) { + return; + } else if (update.vversion().v != _version + 1) { + reload(); + return; + } + _version = update.vversion().v; + applyParticipantsSlice(update.vparticipants().v); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index c72c20de4..add71d960 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -15,38 +15,52 @@ namespace Data { class GroupCall final { public: GroupCall(not_null channel, uint64 id, uint64 accessHash); + ~GroupCall(); [[nodiscard]] uint64 id() const; + [[nodiscard]] not_null channel() const; [[nodiscard]] MTPInputGroupCall input() const; struct Participant { not_null user; TimeId date = 0; - int source = 0; + uint32 source = 0; bool muted = false; bool canSelfUnmute = false; - bool left = false; }; [[nodiscard]] auto participants() const -> const std::vector &; + [[nodiscard]] const base::flat_set &sources() const; void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; + void applyUpdate(const MTPGroupCall &update); + void applyUpdate(const MTPDupdateGroupCallParticipants &update); + [[nodiscard]] int fullCount() const; + void reload(); + [[nodiscard]] bool finished() const; + [[nodiscard]] int duration() const; + private: + void applyCall(const MTPGroupCall &call, bool force); + void applyParticipantsSlice(const QVector &list); + const not_null _channel; const uint64 _id = 0; const uint64 _accessHash = 0; int _version = 0; - UserId _adminId = 0; - uint64 _reflectorId = 0; mtpRequestId _participantsRequestId = 0; + mtpRequestId _reloadRequestId = 0; std::vector _participants; + base::flat_set _sources; int _fullCount = 0; + int _duration = 0; + bool _finished = false; bool _allReceived = false; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 924c61179..c0b5cedf6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name #include "data/stickers/data_stickers.h" #include "data/data_changes.h" +#include "data/data_group_call.h" #include "data/data_media_types.h" #include "data/data_folder.h" #include "data/data_channel.h" @@ -791,6 +792,19 @@ void Session::applyMaximumChatVersions(const MTPVector &data) { } } +void Session::registerGroupCall(not_null call) { + _groupCalls.emplace(call->id(), call); +} + +void Session::unregisterGroupCall(not_null call) { + _groupCalls.remove(call->id()); +} + +GroupCall *Session::groupCall(uint64 callId) const { + const auto i = _groupCalls.find(callId); + return (i != end(_groupCalls)) ? i->second.get() : nullptr; +} + PeerData *Session::peerByUsername(const QString &username) const { const auto uname = username.trimmed(); for (const auto &[peerId, peer] : _peers) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 4474e89b5..fc2fd3b46 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -60,6 +60,7 @@ class Histories; class DocumentMedia; class PhotoMedia; class Stickers; +class GroupCall; class Session final { public: @@ -153,6 +154,10 @@ public: void applyMaximumChatVersions(const MTPVector &data); + void registerGroupCall(not_null call); + void unregisterGroupCall(not_null call); + GroupCall *groupCall(uint64 callId) const; + void enumerateUsers(Fn)> action) const; void enumerateGroups(Fn)> action) const; void enumerateChannels(Fn)> action) const; @@ -906,6 +911,8 @@ private: base::flat_set> _heavyViewParts; + base::flat_map> _groupCalls; + History *_topPromoted = nullptr; NotifySettings _defaultUserNotifySettings;