mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-22 09:07:05 +02:00
Mute by me / change participant volume.
This commit is contained in:
parent
b396244606
commit
f63f0a7668
7 changed files with 188 additions and 26 deletions
|
@ -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<ChannelData*> 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<PeerData*> chat,
|
||||
uint64 id,
|
||||
not_null<UserData*> 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<UserData*> user, bool mute) {
|
||||
if (!_id) {
|
||||
editParticipant(user, mute, std::nullopt);
|
||||
}
|
||||
|
||||
void GroupCall::changeVolume(not_null<UserData*> user, int volume) {
|
||||
editParticipant(user, false, volume);
|
||||
}
|
||||
|
||||
void GroupCall::editParticipant(
|
||||
not_null<UserData*> user,
|
||||
bool mute,
|
||||
std::optional<int> 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) {
|
||||
|
|
|
@ -132,6 +132,7 @@ public:
|
|||
void setAudioDuckingEnabled(bool enabled);
|
||||
|
||||
void toggleMute(not_null<UserData*> user, bool mute);
|
||||
void changeVolume(not_null<UserData*> user, int volume);
|
||||
std::variant<int, not_null<UserData*>> inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &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<UserData*> user,
|
||||
bool mute,
|
||||
std::optional<int> volume);
|
||||
|
||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
|
|
@ -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<void()> 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<Data::CloudImageView> &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<MuteRequest> toggleMuteRequests() const;
|
||||
[[nodiscard]] rpl::producer<VolumeRequest> changeVolumeRequests() const;
|
||||
[[nodiscard]] auto kickMemberRequests() const
|
||||
-> rpl::producer<not_null<UserData*>>;
|
||||
|
||||
|
@ -231,7 +240,8 @@ public:
|
|||
QRect rect,
|
||||
float64 speaking,
|
||||
float64 active,
|
||||
float64 muted) override;
|
||||
float64 muted,
|
||||
bool mutedByMe) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::unique_ptr<Row> createSelfRow();
|
||||
|
@ -271,6 +281,7 @@ private:
|
|||
bool _prepared = false;
|
||||
|
||||
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
||||
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
|
||||
rpl::event_stream<not_null<UserData*>> _kickMemberRequests;
|
||||
rpl::variable<int> _fullCount = 1;
|
||||
rpl::variable<int> _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<VolumeRequest> {
|
||||
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<Ui::PopupMenu> 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<Ui::PopupMenu> 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<Ui::PopupMenu> MembersController::createRowContextMenu(
|
|||
});
|
||||
|
||||
if ((muteState != Row::State::Invited)
|
||||
&& _peer->canManageGroupCall()
|
||||
&& amCallAdmin
|
||||
&& (!admin || mute)) {
|
||||
result->addAction(
|
||||
(mute
|
||||
|
@ -1187,6 +1230,24 @@ base::unique_qptr<Ui::PopupMenu> 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<Row> MembersController::createInvitedRow(
|
|||
|
||||
} // namespace
|
||||
|
||||
|
||||
const int GroupMembers::kDefaultVolume = Data::GroupCall::kDefaultVolume;
|
||||
|
||||
GroupMembers::GroupMembers(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call)
|
||||
|
@ -1258,6 +1322,12 @@ auto GroupMembers::toggleMuteRequests() const
|
|||
_listController.get())->toggleMuteRequests();
|
||||
}
|
||||
|
||||
auto GroupMembers::changeVolumeRequests() const
|
||||
-> rpl::producer<GroupMembers::VolumeRequest> {
|
||||
return static_cast<MembersController*>(
|
||||
_listController.get())->changeVolumeRequests();
|
||||
}
|
||||
|
||||
auto GroupMembers::kickMemberRequests() const
|
||||
-> rpl::producer<not_null<UserData*>> {
|
||||
return static_cast<MembersController*>(
|
||||
|
|
|
@ -30,15 +30,23 @@ public:
|
|||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call);
|
||||
|
||||
static const int kDefaultVolume;/* = Data::GroupCall::kDefaultVolume*/
|
||||
|
||||
struct MuteRequest {
|
||||
not_null<UserData*> user;
|
||||
bool mute = false;
|
||||
};
|
||||
struct VolumeRequest {
|
||||
not_null<UserData*> user;
|
||||
int volume = kDefaultVolume;
|
||||
bool finalized = true;
|
||||
};
|
||||
|
||||
[[nodiscard]] int desiredHeight() const;
|
||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
|
||||
[[nodiscard]] rpl::producer<VolumeRequest> changeVolumeRequests() const;
|
||||
[[nodiscard]] auto kickMemberRequests() const
|
||||
-> rpl::producer<not_null<UserData*>>;
|
||||
[[nodiscard]] rpl::producer<> addMembersRequests() const {
|
||||
|
|
|
@ -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<UserData*> user) {
|
||||
kickMember(user);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -32,14 +32,17 @@ public:
|
|||
|
||||
void setPeer(not_null<PeerData*> peer);
|
||||
|
||||
static constexpr auto kDefaultVolume = 10000;
|
||||
struct Participant {
|
||||
not_null<UserData*> 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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue