From f63f0a7668f62edca4fb2f40ad063ae8f52f7c2d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 29 Dec 2020 18:54:17 +0400 Subject: [PATCH] Mute by me / change participant volume. --- .../SourceFiles/calls/calls_group_call.cpp | 83 ++++++++++++-- Telegram/SourceFiles/calls/calls_group_call.h | 7 ++ .../SourceFiles/calls/calls_group_members.cpp | 104 +++++++++++++++--- .../SourceFiles/calls/calls_group_members.h | 8 ++ .../SourceFiles/calls/calls_group_panel.cpp | 7 ++ Telegram/SourceFiles/data/data_group_call.cpp | 2 + Telegram/SourceFiles/data/data_group_call.h | 3 + 7 files changed, 188 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index bcc1244416..ad8dacfbb7 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -238,11 +238,25 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { using Update = Data::GroupCall::ParticipantUpdate; _peer->groupCall()->participantUpdated( ) | rpl::filter([=](const Update &update) { - return (_instance != nullptr) && !update.now; + return (_instance != nullptr); }) | rpl::start_with_next([=](const Update &update) { - Expects(update.was.has_value()); - - _instance->removeSsrcs({ update.was->ssrc }); + if (!update.now) { + _instance->removeSsrcs({ update.was->ssrc }); + } else { + const auto &now = *update.now; + const auto &was = update.was; + const auto volumeChanged = was + ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) + : (now.volume != Data::GroupCall::kDefaultVolume || now.mutedByMe); + if (volumeChanged) { + _instance->setVolume( + now.ssrc, + (now.mutedByMe + ? 0. + : (now.volume + / float64(Data::GroupCall::kDefaultVolume)))); + } + } }, _lifetime); SubscribeToMigration(_peer, _lifetime, [=](not_null group) { @@ -610,6 +624,7 @@ void GroupCall::createAndStartController() { std::move(descriptor)); updateInstanceMuteState(); + updateInstanceVolumes(); //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); } @@ -622,6 +637,27 @@ void GroupCall::updateInstanceMuteState() { && state != MuteState::PushToTalk); } +void GroupCall::updateInstanceVolumes() { + const auto real = _peer->groupCall(); + if (!real || real->id() != _id) { + return; + } + + const auto &participants = real->participants(); + for (const auto &participant : participants) { + const auto setVolume = participant.mutedByMe + || (participant.volume != Data::GroupCall::kDefaultVolume); + if (setVolume && participant.ssrc) { + _instance->setVolume( + participant.ssrc, + (participant.mutedByMe + ? 0. + : (participant.volume + / float64(Data::GroupCall::kDefaultVolume)))); + } + } +} + void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { Expects(!data.updates.empty()); @@ -792,17 +828,46 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { } } +[[nodiscard]] const Data::GroupCall::Participant *LookupParticipant( + not_null chat, + uint64 id, + not_null user) { + const auto call = chat->groupCall(); + if (!id || !call || call->id() != id) { + return nullptr; + } + const auto &participants = call->participants(); + const auto i = ranges::find( + participants, + user, + &Data::GroupCall::Participant::user); + return (i != end(participants)) ? &*i : nullptr; +} + void GroupCall::toggleMute(not_null user, bool mute) { - if (!_id) { + editParticipant(user, mute, std::nullopt); +} + +void GroupCall::changeVolume(not_null user, int volume) { + editParticipant(user, false, volume); +} + +void GroupCall::editParticipant( + not_null user, + bool mute, + std::optional volume) { + const auto participant = LookupParticipant(_peer, _id, user); + if (!participant) { return; } + using Flag = MTPphone_EditGroupCallMember::Flag; + const auto flags = (mute ? Flag::f_muted : Flag(0)) + | (volume.has_value() ? Flag::f_volume : Flag(0)); _api.request(MTPphone_EditGroupCallMember( - MTP_flags(mute - ? MTPphone_EditGroupCallMember::Flag::f_muted - : MTPphone_EditGroupCallMember::Flag(0)), + MTP_flags(flags), inputCall(), user->inputUser, - MTP_int(100000) // volume + MTP_int(std::clamp(volume.value_or(0), 1, 20000)) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).fail([=](const RPCError &error) { diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 8b6907d2cf..6ec849bfc3 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -132,6 +132,7 @@ public: void setAudioDuckingEnabled(bool enabled); void toggleMute(not_null user, bool mute); + void changeVolume(not_null user, int volume); std::variant> inviteUsers( const std::vector> &users); @@ -163,6 +164,7 @@ private: void maybeSendMutedUpdate(MuteState previous); void sendMutedUpdate(); void updateInstanceMuteState(); + void updateInstanceVolumes(); void applySelfInCallLocally(); void rejoin(); @@ -178,6 +180,11 @@ private: void stopConnectingSound(); void playConnectingSoundOnce(); + void editParticipant( + not_null user, + bool mute, + std::optional volume); + [[nodiscard]] MTPInputGroupCall inputCall() const; const not_null _delegate; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index e311318e01..84e10f7461 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -76,7 +76,8 @@ public: QRect rect, float64 speaking, float64 active, - float64 muted) = 0; + float64 muted, + bool mutedByMe) = 0; }; class Row final : public PeerListRow { @@ -87,6 +88,7 @@ public: Active, Inactive, Muted, + MutedByMe, Invited, }; @@ -106,6 +108,9 @@ public: [[nodiscard]] bool speaking() const { return _speaking; } + [[nodiscard]] int volume() const { + return _volume; + } void addActionRipple(QPoint point, Fn updateCallback) override; void stopLastActionRipple() override; @@ -177,6 +182,7 @@ private: void setSpeaking(bool speaking); void setState(State state); void setSsrc(uint32 ssrc); + void setVolume(int volume); void ensureUserpicCache( std::shared_ptr &view, @@ -190,6 +196,7 @@ private: Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _activeAnimation; // For icon cross animation. uint32 _ssrc = 0; + int _volume = Data::GroupCall::kDefaultVolume; bool _sounding = false; bool _speaking = false; bool _skipLevelUpdate = false; @@ -207,6 +214,7 @@ public: ~MembersController(); using MuteRequest = GroupMembers::MuteRequest; + using VolumeRequest = GroupMembers::VolumeRequest; Main::Session &session() const override; void prepare() override; @@ -221,6 +229,7 @@ public: return _fullCount.value(); } [[nodiscard]] rpl::producer toggleMuteRequests() const; + [[nodiscard]] rpl::producer changeVolumeRequests() const; [[nodiscard]] auto kickMemberRequests() const -> rpl::producer>; @@ -231,7 +240,8 @@ public: QRect rect, float64 speaking, float64 active, - float64 muted) override; + float64 muted, + bool mutedByMe) override; private: [[nodiscard]] std::unique_ptr createSelfRow(); @@ -271,6 +281,7 @@ private: bool _prepared = false; rpl::event_stream _toggleMuteRequests; + rpl::event_stream _changeVolumeRequests; rpl::event_stream> _kickMemberRequests; rpl::variable _fullCount = 1; rpl::variable _fullCountMin = 0; @@ -305,17 +316,20 @@ void Row::setSkipLevelUpdate(bool value) { void Row::updateState(const Data::GroupCall::Participant *participant) { setSsrc(participant ? participant->ssrc : 0); + setVolume(participant + ? participant->volume + : Data::GroupCall::kDefaultVolume); if (!participant) { setState(State::Invited); setSounding(false); setSpeaking(false); } else if (!participant->muted || (participant->sounding && participant->ssrc != 0)) { - setState(State::Active); + setState(participant->mutedByMe ? State::MutedByMe : State::Active); setSounding(participant->sounding && participant->ssrc != 0); setSpeaking(participant->speaking && participant->ssrc != 0); } else if (participant->canSelfUnmute) { - setState(State::Inactive); + setState(participant->mutedByMe ? State::MutedByMe : State::Inactive); setSounding(false); setSpeaking(false); } else { @@ -384,6 +398,10 @@ void Row::setSsrc(uint32 ssrc) { _ssrc = ssrc; } +void Row::setVolume(int volume) { + _volume = volume; +} + void Row::updateLevel(float level) { Expects(_blobsAnimation != nullptr); @@ -451,13 +469,16 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { auto userpic = ensureUserpicView(); return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { if (_blobsAnimation) { + const auto mutedByMe = (_state == State::MutedByMe); const auto shift = QPointF(x + size / 2., y + size / 2.); auto hq = PainterHighQualityEnabler(p); p.translate(shift); - const auto brush = anim::brush( - st::groupCallMemberInactiveStatus, - st::groupCallMemberActiveStatus, - _speakingAnimation.value(_speaking ? 1. : 0.)); + const auto brush = mutedByMe + ? st::groupCallMemberMutedIcon->b + : anim::brush( + st::groupCallMemberInactiveStatus, + st::groupCallMemberActiveStatus, + _speakingAnimation.value(_speaking ? 1. : 0.)); _blobsAnimation->blobs.paint(p, brush); p.translate(-shift); p.setOpacity(1.); @@ -502,7 +523,7 @@ void Row::paintStatusText( int availableWidth, int outerWidth, bool selected) { - if (_state != State::Invited) { + if (_state != State::Invited && _state != State::MutedByMe) { PeerListRow::paintStatusText( p, st, @@ -514,12 +535,18 @@ void Row::paintStatusText( return; } p.setFont(st::normalFont); - p.setPen(st::groupCallMemberNotJoinedStatus); + if (_state == State::MutedByMe) { + p.setPen(st::groupCallMemberMutedIcon); + } else { + p.setPen(st::groupCallMemberNotJoinedStatus); + } p.drawTextLeft( x, y, outerWidth, - (peer()->isSelf() + (_state == State::MutedByMe + ? "muted by me" + : peer()->isSelf() ? tr::lng_status_connecting(tr::now) : tr::lng_group_call_invited_status(tr::now))); } @@ -561,7 +588,8 @@ void Row::paintAction( (_state == State::Active) ? 1. : 0.); const auto muted = _mutedAnimation.value( (_state == State::Muted) ? 1. : 0.); - _delegate->rowPaintIcon(p, iconRect, speaking, active, muted); + const auto mutedByMe = (_state == State::MutedByMe); + _delegate->rowPaintIcon(p, iconRect, speaking, active, muted, mutedByMe); } void Row::refreshStatus() { @@ -984,6 +1012,11 @@ auto MembersController::toggleMuteRequests() const return _toggleMuteRequests.events(); } +auto MembersController::changeVolumeRequests() const +-> rpl::producer { + return _changeVolumeRequests.events(); +} + bool MembersController::rowCanMuteMembers() { return _peer->canManageGroupCall(); } @@ -997,11 +1030,12 @@ void MembersController::rowPaintIcon( QRect rect, float64 speaking, float64 active, - float64 muted) { + float64 muted, + bool mutedByMe) { const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon; const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2; const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2; - if (speaking == 1.) { + if (speaking == 1. && !mutedByMe) { // Just green icon, no cross, no coloring. greenIcon.paintInCenter(p, rect); return; @@ -1029,7 +1063,9 @@ void MembersController::rowPaintIcon( } const auto activeInactiveColor = anim::color( st::groupCallMemberInactiveIcon, - st::groupCallMemberActiveIcon, + (mutedByMe + ? st::groupCallMemberMutedIcon + : st::groupCallMemberActiveIcon), speaking); const auto iconColor = anim::color( activeInactiveColor, @@ -1119,7 +1155,8 @@ base::unique_qptr MembersController::createRowContextMenu( } return false; }(); - const auto mute = admin + const auto amCallAdmin = _peer->canManageGroupCall(); + const auto mute = (admin || !amCallAdmin) ? (muteState == Row::State::Active) : (muteState != Row::State::Muted); const auto toggleMute = crl::guard(this, [=] { @@ -1128,6 +1165,12 @@ base::unique_qptr MembersController::createRowContextMenu( .mute = mute, }); }); + const auto changeVolume = crl::guard(this, [=](int volume) { + _changeVolumeRequests.fire(VolumeRequest{ + .user = user, + .volume = std::clamp(volume, 1, 20000), + }); + }); const auto session = &user->session(); const auto getCurrentWindow = [=]() -> Window::SessionController* { @@ -1179,7 +1222,7 @@ base::unique_qptr MembersController::createRowContextMenu( }); if ((muteState != Row::State::Invited) - && _peer->canManageGroupCall() + && amCallAdmin && (!admin || mute)) { result->addAction( (mute @@ -1187,6 +1230,24 @@ base::unique_qptr MembersController::createRowContextMenu( : tr::lng_group_call_context_unmute(tr::now)), toggleMute); } + if (real->ssrc() != 0) { + if (!amCallAdmin + && ((muteState == Row::State::Active) + || (real->state() == Row::State::MutedByMe))) { + result->addAction( + ((muteState == Row::State::Active) + ? "Mute for me" + : "Unmute for me"), + toggleMute); + } + const auto volume = real->volume(); + result->addAction(QString("Increase volume (%1%)").arg(volume / 100.), [=] { + changeVolume(volume + 2000); + }); + result->addAction(QString("Decrease volume (%1%)").arg(volume / 100.), [=] { + changeVolume(volume - 2000); + }); + } result->addAction( tr::lng_context_view_profile(tr::now), showProfile); @@ -1238,6 +1299,9 @@ std::unique_ptr MembersController::createInvitedRow( } // namespace + +const int GroupMembers::kDefaultVolume = Data::GroupCall::kDefaultVolume; + GroupMembers::GroupMembers( not_null parent, not_null call) @@ -1258,6 +1322,12 @@ auto GroupMembers::toggleMuteRequests() const _listController.get())->toggleMuteRequests(); } +auto GroupMembers::changeVolumeRequests() const +-> rpl::producer { + return static_cast( + _listController.get())->changeVolumeRequests(); +} + auto GroupMembers::kickMemberRequests() const -> rpl::producer> { return static_cast( diff --git a/Telegram/SourceFiles/calls/calls_group_members.h b/Telegram/SourceFiles/calls/calls_group_members.h index e255d2ee45..76ed90e93a 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.h +++ b/Telegram/SourceFiles/calls/calls_group_members.h @@ -30,15 +30,23 @@ public: not_null parent, not_null call); + static const int kDefaultVolume;/* = Data::GroupCall::kDefaultVolume*/ + struct MuteRequest { not_null user; bool mute = false; }; + struct VolumeRequest { + not_null user; + int volume = kDefaultVolume; + bool finalized = true; + }; [[nodiscard]] int desiredHeight() const; [[nodiscard]] rpl::producer desiredHeightValue() const override; [[nodiscard]] rpl::producer fullCountValue() const; [[nodiscard]] rpl::producer toggleMuteRequests() const; + [[nodiscard]] rpl::producer changeVolumeRequests() const; [[nodiscard]] auto kickMemberRequests() const -> rpl::producer>; [[nodiscard]] rpl::producer<> addMembersRequests() const { diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index a6e260bb2a..32826be623 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -511,6 +511,13 @@ void GroupPanel::initWithCall(GroupCall *call) { } }, _callLifetime); + _members->changeVolumeRequests( + ) | rpl::start_with_next([=](GroupMembers::VolumeRequest request) { + if (_call) { + _call->changeVolume(request.user, request.volume); + } + }, _callLifetime); + _members->kickMemberRequests( ) | rpl::start_with_next([=](not_null user) { kickMember(user); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 22a1a5961c..9ca6859792 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -280,8 +280,10 @@ void GroupCall::applyParticipantsSlice( .date = data.vdate().v, .lastActive = lastActive, .ssrc = uint32(data.vsource().v), + .volume = data.vvolume().value_or(kDefaultVolume), .speaking = canSelfUnmute && (was ? was->speaking : false), .muted = data.is_muted(), + .mutedByMe = data.is_muted_by_you(), .canSelfUnmute = canSelfUnmute, }; if (i == end(_participants)) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 2aad686a3e..5bf7e7c40c 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -32,14 +32,17 @@ public: void setPeer(not_null peer); + static constexpr auto kDefaultVolume = 10000; struct Participant { not_null user; TimeId date = 0; TimeId lastActive = 0; uint32 ssrc = 0; + int volume = 0; bool sounding = false; bool speaking = false; bool muted = false; + bool mutedByMe = false; bool canSelfUnmute = false; }; struct ParticipantUpdate {