diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 387dfb026a..f3f35b017d 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -364,15 +364,20 @@ void GroupCall::createAndStartController() { const auto &settings = Core::App().settings(); const auto weak = base::make_weak(this); + const auto myLevel = std::make_shared(); tgcalls::GroupInstanceDescriptor descriptor = { .config = tgcalls::GroupConfig{ }, .networkStateUpdated = [=](bool) { }, .audioLevelsUpdated = [=](const AudioLevels &data) { + crl::on_main(weak, [=] { audioLevelsUpdated(data); }); }, .myAudioLevelUpdated = [=](float level) { - crl::on_main(weak, [=] { myLevelUpdated(level); }); + if (*myLevel != level) { // Don't send many 0 while we're muted. + *myLevel = level; + crl::on_main(weak, [=] { myLevelUpdated(level); }); + } }, .initialInputDeviceId = settings.callInputDeviceId().toStdString(), .initialOutputDeviceId = settings.callOutputDeviceId().toStdString(), @@ -409,7 +414,22 @@ void GroupCall::createAndStartController() { } void GroupCall::myLevelUpdated(float level) { - LOG(("Level: %1").arg(level)); + _levelUpdates.fire(LevelUpdate{ + .source = _mySsrc, + .value = level, + .self = true + }); +} + +void GroupCall::audioLevelsUpdated( + const std::vector> &data) { + for (const auto &[source, level] : data) { + _levelUpdates.fire(LevelUpdate{ + .source = source, + .value = level, + .self = (source == _mySsrc) + }); + } } void GroupCall::sendMutedUpdate() { diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 9069c7a374..f38f216d9a 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -25,6 +25,12 @@ enum class MuteState { ForceMuted, }; +struct LevelUpdate { + uint32 source = 0; + float value = 0.; + bool self = false; +}; + class GroupCall final : public base::has_weak_ptr { public: class Delegate { @@ -82,6 +88,10 @@ public: return _state.value(); } + [[nodiscard]] rpl::producer levelUpdates() const { + return _levelUpdates.events(); + } + void setCurrentAudioDevice(bool input, const QString &deviceId); //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); @@ -111,6 +121,8 @@ private: void rejoin(); void myLevelUpdated(float level); + void audioLevelsUpdated( + const std::vector> &data); [[nodiscard]] MTPInputGroupCall inputCall() const; @@ -128,6 +140,7 @@ private: mtpRequestId _updateMuteRequestId = 0; std::unique_ptr _instance; + rpl::event_stream _levelUpdates; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 1ebc208185..e226e84f1e 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -26,6 +26,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Calls { namespace { +constexpr auto kLevelThreshold = 0.01; +constexpr auto kLevelActiveTimeout = crl::time(1000); + +enum class UpdateLevelResult { + NothingChanged, + LevelChanged, + StateChanged, +}; + class Row final : public PeerListRow { public: Row(not_null channel, not_null user); @@ -37,6 +46,7 @@ public: }; void updateState(const Data::GroupCall::Participant *participant); + UpdateLevelResult updateLevel(float level); [[nodiscard]] State state() const { return _state; } @@ -70,6 +80,8 @@ public: private: void refreshStatus() override; + void refreshStatus(crl::time now); + void resetSpeakingState(); [[nodiscard]] static State ComputeState( not_null channel, @@ -80,6 +92,8 @@ private: State _state = State::Inactive; not_null _channel; not_null _st; + float _level = 0.; + crl::time _markInactiveAt = 0; std::unique_ptr _actionRipple; @@ -121,10 +135,18 @@ private: void updateRow( not_null row, const Data::GroupCall::Participant *participant) const; + void updateRowLevel(not_null user, float level) const; + Row *findRow(not_null user) const; + + [[nodiscard]] Data::GroupCall *resolvedRealCall() const; const base::weak_ptr _call; const not_null _channel; + // Use only resolvedRealCall() method, not this value directly. + Data::GroupCall *_realCallRawValue = nullptr; + uint64 _realId = 0; + rpl::event_stream _toggleMuteRequests; rpl::variable _fullCount = 1; Ui::BoxPointer _addBox; @@ -148,16 +170,48 @@ void Row::updateState(const Data::GroupCall::Participant *participant) { setCustomStatus(QString()); } _state = State::Inactive; + resetSpeakingState(); } else if (!participant->muted) { _state = State::Active; } else if (participant->canSelfUnmute) { _state = State::Inactive; + resetSpeakingState(); } else { _state = State::Muted; + resetSpeakingState(); } _st = ComputeIconStyle(_state); } +void Row::resetSpeakingState() { + _markInactiveAt = 0; + updateLevel(0.); +} + +UpdateLevelResult Row::updateLevel(float level) { + if (_level == level) { + return UpdateLevelResult::NothingChanged; + } + const auto now = crl::now(); + const auto stillActive = (now < _markInactiveAt); + const auto wasActive = (_level >= kLevelThreshold) && stillActive; + const auto nowActive = (level >= kLevelThreshold); + if (nowActive) { + _markInactiveAt = now + kLevelActiveTimeout; + if (_state != State::Active) { + _state = State::Active; + _st = ComputeIconStyle(_state); + } + } + _level = level; + const auto changed = wasActive != (nowActive || stillActive); + if (!changed) { + return UpdateLevelResult::LevelChanged; + } + refreshStatus(now); + return UpdateLevelResult::StateChanged; +} + void Row::paintAction( Painter &p, int x, @@ -182,14 +236,16 @@ void Row::paintAction( } void Row::refreshStatus() { - setCustomStatus([&] { - switch (_state) { - case State::Inactive: - case State::Muted: return tr::lng_group_call_inactive(tr::now); - case State::Active: return tr::lng_group_call_active(tr::now); - } - Unexpected("State in Row::refreshStatus."); - }(), (_state == State::Active)); + refreshStatus(crl::now()); +} + +void Row::refreshStatus(crl::time now) { + const auto active = (now < _markInactiveAt); + setCustomStatus( + (active + ? tr::lng_group_call_active(tr::now) + : tr::lng_group_call_inactive(tr::now)), + active); } Row::State Row::ComputeState( @@ -272,9 +328,28 @@ void MembersController::setupListChangeViewers(not_null call) { //updateRow(channel->session().user()); } }, _lifetime); + + call->levelUpdates( + ) | rpl::start_with_next([=](const LevelUpdate &update) { + const auto findUserBySource = [&](uint32 source) -> UserData* { + if (const auto real = resolvedRealCall()) { + return real->userBySource(source); + } + return nullptr; + }; + const auto user = update.self + ? _channel->session().user().get() + : findUserBySource(update.source); + if (user) { + updateRowLevel(user, update.value); + } + }, _lifetime); } void MembersController::subscribeToChanges(not_null real) { + _realCallRawValue = real; + _realId = real->id(); + _fullCount = real->fullCountValue( ) | rpl::map([](int value) { return std::max(value, 1); @@ -290,9 +365,9 @@ void MembersController::subscribeToChanges(not_null real) { ) | rpl::start_with_next([=](const Update &update) { const auto user = update.participant.user; if (update.removed) { - if (auto row = delegate()->peerListFindRow(user->id)) { + if (const auto row = findRow(user)) { if (user->isSelf()) { - static_cast(row)->updateState(nullptr); + row->updateState(nullptr); delegate()->peerListUpdateRow(row); } else { delegate()->peerListRemoveRow(row); @@ -307,8 +382,8 @@ void MembersController::subscribeToChanges(not_null real) { void MembersController::updateRow( const Data::GroupCall::Participant &participant) { - if (auto row = delegate()->peerListFindRow(participant.user->id)) { - updateRow(static_cast(row), &participant); + if (const auto row = findRow(participant.user)) { + updateRow(row, &participant); } else if (auto row = createRow(participant)) { delegate()->peerListAppendRow(std::move(row)); delegate()->peerListRefreshRows(); @@ -322,6 +397,30 @@ void MembersController::updateRow( delegate()->peerListUpdateRow(row); } +void MembersController::updateRowLevel( + not_null user, + float level) const { + if (const auto row = findRow(user)) { + const auto result = row->updateLevel(level); + if (result == UpdateLevelResult::StateChanged) { + // #TODO calls reorder. + } + delegate()->peerListUpdateRow(row); + } +} + +Row *MembersController::findRow(not_null user) const { + return static_cast(delegate()->peerListFindRow(user->id)); +} + +Data::GroupCall *MembersController::resolvedRealCall() const { + return (_realCallRawValue + && (_channel->call() == _realCallRawValue) + && (_realCallRawValue->id() == _realId)) + ? _realCallRawValue + : nullptr; +} + Main::Session &MembersController::session() const { return _call->channel()->session(); } @@ -473,7 +572,9 @@ int GroupMembers::desiredHeight() const { auto count = [this] { if (const auto call = _call.get()) { if (const auto real = call->channel()->call()) { - return real->fullCount(); + if (call->id() == real->id()) { + return real->fullCount(); + } } } return 0; diff --git a/Telegram/SourceFiles/calls/calls_group_members.h b/Telegram/SourceFiles/calls/calls_group_members.h index 9cc8fcc8d7..f5481a4868 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.h +++ b/Telegram/SourceFiles/calls/calls_group_members.h @@ -71,7 +71,7 @@ private: void addMember(); void updateHeaderControlsGeometry(int newWidth); - base::weak_ptr _call; + const base::weak_ptr _call; object_ptr _scroll; std::unique_ptr _listController; object_ptr _header = { nullptr }; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index f5e6ec96fe..e30fb9458d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -105,6 +105,11 @@ bool GroupCall::participantsLoaded() const { return _allReceived; } +UserData *GroupCall::userBySource(uint32 source) const { + const auto i = _userBySource.find(source); + return (i != end(_userBySource)) ? i->second.get() : nullptr; +} + rpl::producer<> GroupCall::participantsSliceAdded() { return _participantsSliceAdded.events(); } @@ -183,6 +188,7 @@ void GroupCall::applyParticipantsSlice( .participant = *i, .removed = true, }; + _userBySource.erase(i->source); _participants.erase(i); if (sendIndividualUpdates) { _participantUpdates.fire(std::move(update)); @@ -201,9 +207,14 @@ void GroupCall::applyParticipantsSlice( .canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(), }; if (i == end(_participants)) { + _userBySource.emplace(value.source, value.user); _participants.push_back(value); ++fullCount; } else { + if (i->source != value.source) { + _userBySource.erase(i->source); + _userBySource.emplace(value.source, value.user); + } *i = value; } _participantUpdates.fire({ diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 164989b58f..4ccda796c9 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -39,6 +39,7 @@ public: -> const std::vector &; void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; + [[nodiscard]] UserData *userBySource(uint32 source) const; [[nodiscard]] rpl::producer<> participantsSliceAdded(); [[nodiscard]] rpl::producer participantUpdated() const; @@ -72,6 +73,7 @@ private: mtpRequestId _reloadRequestId = 0; std::vector _participants; + base::flat_map> _userBySource; QString _nextOffset; rpl::variable _fullCount = 0;