Divide speaking status and background noise.

This commit is contained in:
John Preston 2020-12-15 14:16:44 +04:00
parent 7f7e7b94d6
commit 15620b5c2d
7 changed files with 139 additions and 93 deletions

View file

@ -245,7 +245,10 @@ Updates::Updates(not_null<Main::Session*> session)
for (const auto [userId, when] : *users) { for (const auto [userId, when] : *users) {
call->applyActiveUpdate( call->applyActiveUpdate(
userId, userId,
when, Data::LastSpokeTimes{
.anything = when,
.voice = when
},
peer->owner().userLoaded(userId)); peer->owner().userLoaded(userId));
} }
} }
@ -928,7 +931,10 @@ void Updates::handleSendActionUpdate(
const auto call = peer->groupCall(); const auto call = peer->groupCall();
const auto now = crl::now(); const auto now = crl::now();
if (call) { if (call) {
call->applyActiveUpdate(userId, now, user); call->applyActiveUpdate(
userId,
Data::LastSpokeTimes{ .anything = now, .voice = now },
user);
} else { } else {
const auto chat = peer->asChat(); const auto chat = peer->asChat();
const auto channel = peer->asChannel(); const auto channel = peer->asChannel();

View file

@ -498,28 +498,30 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
} }
void GroupCall::createAndStartController() { void GroupCall::createAndStartController() {
using AudioLevels = std::vector<std::pair<uint32_t, float>>;
const auto &settings = Core::App().settings(); const auto &settings = Core::App().settings();
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
const auto myLevel = std::make_shared<float>(); const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
tgcalls::GroupInstanceDescriptor descriptor = { tgcalls::GroupInstanceDescriptor descriptor = {
.config = tgcalls::GroupConfig{ .config = tgcalls::GroupConfig{
}, },
.networkStateUpdated = [=](bool connected) { .networkStateUpdated = [=](bool connected) {
crl::on_main(weak, [=] { setInstanceConnected(connected); }); crl::on_main(weak, [=] { setInstanceConnected(connected); });
}, },
.audioLevelsUpdated = [=](const AudioLevels &data) { .audioLevelsUpdated = [=](const tgcalls::GroupLevelsUpdate &data) {
if (!data.empty()) { const auto &updates = data.updates;
crl::on_main(weak, [=] { audioLevelsUpdated(data); }); if (updates.empty()) {
} return;
}, } else if (updates.size() == 1 && !updates.front().ssrc) {
.myAudioLevelUpdated = [=](float level) { const auto &value = updates.front().value;
if (*myLevel != level) { // Don't send many 0 while we're muted. // Don't send many 0 while we're muted.
*myLevel = level; if (myLevel->level == value.level
crl::on_main(weak, [=] { myLevelUpdated(level); }); && myLevel->voice == value.voice) {
return;
}
*myLevel = updates.front().value;
} }
crl::on_main(weak, [=] { audioLevelsUpdated(data); });
}, },
.initialInputDeviceId = settings.callInputDeviceId().toStdString(), .initialInputDeviceId = settings.callInputDeviceId().toStdString(),
.initialOutputDeviceId = settings.callOutputDeviceId().toStdString(), .initialOutputDeviceId = settings.callOutputDeviceId().toStdString(),
@ -555,24 +557,28 @@ void GroupCall::updateInstanceMuteState() {
&& state != MuteState::PushToTalk); && state != MuteState::PushToTalk);
} }
void GroupCall::handleLevelsUpdated( void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
gsl::span<const std::pair<std::uint32_t, float>> data) { Expects(!data.updates.empty());
Expects(!data.empty());
auto check = false; auto check = false;
auto checkNow = false; auto checkNow = false;
const auto now = crl::now(); const auto now = crl::now();
for (const auto &[ssrc, level] : data) { for (const auto &[ssrcOrZero, value] : data.updates) {
const auto ssrc = ssrcOrZero ? ssrcOrZero : _mySsrc;
const auto level = value.level;
const auto voice = value.voice;
const auto self = (ssrc == _mySsrc); const auto self = (ssrc == _mySsrc);
_levelUpdates.fire(LevelUpdate{ _levelUpdates.fire(LevelUpdate{
.ssrc = ssrc, .ssrc = ssrc,
.value = level, .value = level,
.voice = voice,
.self = self .self = self
}); });
if (level <= kSpeakLevelThreshold) { if (level <= kSpeakLevelThreshold) {
continue; continue;
} }
if (self if (self
&& voice
&& (!_lastSendProgressUpdate && (!_lastSendProgressUpdate
|| _lastSendProgressUpdate + kUpdateSendActionEach < now)) { || _lastSendProgressUpdate + kUpdateSendActionEach < now)) {
_lastSendProgressUpdate = now; _lastSendProgressUpdate = now;
@ -584,13 +590,21 @@ void GroupCall::handleLevelsUpdated(
check = true; check = true;
const auto i = _lastSpoke.find(ssrc); const auto i = _lastSpoke.find(ssrc);
if (i == _lastSpoke.end()) { if (i == _lastSpoke.end()) {
_lastSpoke.emplace(ssrc, now); _lastSpoke.emplace(ssrc, Data::LastSpokeTimes{
.anything = now,
.voice = voice ? now : 0,
});
checkNow = true; checkNow = true;
} else { } else {
if (i->second + kCheckLastSpokeInterval / 3 <= now) { if ((i->second.anything + kCheckLastSpokeInterval / 3 <= now)
|| (voice
&& i->second.voice + kCheckLastSpokeInterval / 3 <= now)) {
checkNow = true; checkNow = true;
} }
i->second = now; i->second.anything = now;
if (voice) {
i->second.voice = now;
}
} }
} }
if (checkNow) { if (checkNow) {
@ -600,16 +614,6 @@ void GroupCall::handleLevelsUpdated(
} }
} }
void GroupCall::myLevelUpdated(float level) {
const auto pair = std::pair<std::uint32_t, float>{ _mySsrc, level };
handleLevelsUpdated({ &pair, &pair + 1 });
}
void GroupCall::audioLevelsUpdated(
const std::vector<std::pair<std::uint32_t, float>> &data) {
handleLevelsUpdated(gsl::make_span(data));
}
void GroupCall::checkLastSpoke() { void GroupCall::checkLastSpoke() {
const auto real = _peer->groupCall(); const auto real = _peer->groupCall();
if (!real || real->id() != _id) { if (!real || real->id() != _id) {
@ -621,7 +625,7 @@ void GroupCall::checkLastSpoke() {
auto list = base::take(_lastSpoke); auto list = base::take(_lastSpoke);
for (auto i = list.begin(); i != list.end();) { for (auto i = list.begin(); i != list.end();) {
const auto [ssrc, when] = *i; const auto [ssrc, when] = *i;
if (when + kCheckLastSpokeInterval >= now) { if (when.anything + kCheckLastSpokeInterval >= now) {
hasRecent = true; hasRecent = true;
++i; ++i;
} else { } else {

View file

@ -17,6 +17,7 @@ class History;
namespace tgcalls { namespace tgcalls {
class GroupInstanceImpl; class GroupInstanceImpl;
struct GroupLevelsUpdate;
} // namespace tgcalls } // namespace tgcalls
namespace base { namespace base {
@ -24,6 +25,10 @@ class GlobalShortcutManager;
class GlobalShortcutValue; class GlobalShortcutValue;
} // namespace base } // namespace base
namespace Data {
struct LastSpokeTimes;
} // namespace Data
namespace Calls { namespace Calls {
enum class MuteState { enum class MuteState {
@ -42,6 +47,7 @@ enum class MuteState {
struct LevelUpdate { struct LevelUpdate {
uint32 ssrc = 0; uint32 ssrc = 0;
float value = 0.; float value = 0.;
bool voice = false;
bool self = false; bool self = false;
}; };
@ -146,11 +152,7 @@ private:
void applySelfInCallLocally(); void applySelfInCallLocally();
void rejoin(); void rejoin();
void myLevelUpdated(float level); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
void audioLevelsUpdated(
const std::vector<std::pair<std::uint32_t, float>> &data);
void handleLevelsUpdated(
gsl::span<const std::pair<std::uint32_t, float>> data);
void setInstanceConnected(bool connected); void setInstanceConnected(bool connected);
void checkLastSpoke(); void checkLastSpoke();
void pushToTalkCancel(); void pushToTalkCancel();
@ -178,7 +180,7 @@ private:
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance; std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
rpl::event_stream<LevelUpdate> _levelUpdates; rpl::event_stream<LevelUpdate> _levelUpdates;
base::flat_map<uint32, crl::time> _lastSpoke; base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
base::Timer _lastSpokeCheckTimer; base::Timer _lastSpokeCheckTimer;
base::Timer _checkJoinedTimer; base::Timer _checkJoinedTimer;

View file

@ -97,6 +97,9 @@ public:
[[nodiscard]] uint32 ssrc() const { [[nodiscard]] uint32 ssrc() const {
return _ssrc; return _ssrc;
} }
[[nodiscard]] bool sounding() const {
return _sounding;
}
[[nodiscard]] bool speaking() const { [[nodiscard]] bool speaking() const {
return _speaking; return _speaking;
} }
@ -147,7 +150,7 @@ private:
Ui::Paint::Blobs blobs; Ui::Paint::Blobs blobs;
crl::time lastTime = 0; crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0; crl::time lastSoundingUpdateTime = 0;
float64 enter = 0.; float64 enter = 0.;
QImage userpicCache; QImage userpicCache;
@ -156,6 +159,7 @@ private:
rpl::lifetime lifetime; rpl::lifetime lifetime;
}; };
void refreshStatus() override; void refreshStatus() override;
void setSounding(bool sounding);
void setSpeaking(bool speaking); void setSpeaking(bool speaking);
void setState(State state); void setState(State state);
void setSsrc(uint32 ssrc); void setSsrc(uint32 ssrc);
@ -172,6 +176,7 @@ private:
Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
Ui::Animations::Simple _activeAnimation; // For icon cross animation. Ui::Animations::Simple _activeAnimation; // For icon cross animation.
uint32 _ssrc = 0; uint32 _ssrc = 0;
bool _sounding = false;
bool _speaking = false; bool _speaking = false;
bool _skipLevelUpdate = false; bool _skipLevelUpdate = false;
@ -252,10 +257,10 @@ private:
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden; base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
base::flat_map<uint32, not_null<Row*>> _speakingRowBySsrc; base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
Ui::Animations::Basic _speakingAnimation; Ui::Animations::Basic _soundingAnimation;
crl::time _speakingAnimationHideLastTime = 0; crl::time _soundingAnimationHideLastTime = 0;
bool _skipRowLevelUpdate = false; bool _skipRowLevelUpdate = false;
Ui::CrossLineAnimation _inactiveCrossLine; Ui::CrossLineAnimation _inactiveCrossLine;
@ -284,16 +289,20 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
setCustomStatus(QString()); setCustomStatus(QString());
} }
setState(State::Inactive); setState(State::Inactive);
setSounding(false);
setSpeaking(false); setSpeaking(false);
} else if (!participant->muted } else if (!participant->muted
|| (participant->speaking && participant->ssrc != 0)) { || (participant->sounding && participant->ssrc != 0)) {
setState(State::Active); setState(State::Active);
setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0); setSpeaking(participant->speaking && participant->ssrc != 0);
} else if (participant->canSelfUnmute) { } else if (participant->canSelfUnmute) {
setState(State::Inactive); setState(State::Inactive);
setSounding(false);
setSpeaking(false); setSpeaking(false);
} else { } else {
setState(State::Muted); setState(State::Muted);
setSounding(false);
setSpeaking(false); setSpeaking(false);
} }
} }
@ -308,7 +317,14 @@ void Row::setSpeaking(bool speaking) {
_speaking ? 0. : 1., _speaking ? 0. : 1.,
_speaking ? 1. : 0., _speaking ? 1. : 0.,
st::widgetFadeDuration); st::widgetFadeDuration);
if (!_speaking) { }
void Row::setSounding(bool sounding) {
if (_sounding == sounding) {
return;
}
_sounding = sounding;
if (!_sounding) {
_blobsAnimation = nullptr; _blobsAnimation = nullptr;
} else if (!_blobsAnimation) { } else if (!_blobsAnimation) {
_blobsAnimation = std::make_unique<BlobsAnimation>( _blobsAnimation = std::make_unique<BlobsAnimation>(
@ -358,7 +374,7 @@ void Row::updateLevel(float level) {
} }
if (level >= GroupCall::kSpeakLevelThreshold) { if (level >= GroupCall::kSpeakLevelThreshold) {
_blobsAnimation->lastSpeakingUpdateTime = crl::now(); _blobsAnimation->lastSoundingUpdateTime = crl::now();
} }
_blobsAnimation->blobs.setLevel(level); _blobsAnimation->blobs.setLevel(level);
} }
@ -366,14 +382,14 @@ void Row::updateLevel(float level) {
void Row::updateBlobAnimation(crl::time now) { void Row::updateBlobAnimation(crl::time now) {
Expects(_blobsAnimation != nullptr); Expects(_blobsAnimation != nullptr);
const auto speakingFinishesAt = _blobsAnimation->lastSpeakingUpdateTime const auto soundingFinishesAt = _blobsAnimation->lastSoundingUpdateTime
+ Data::GroupCall::kSpeakStatusKeptFor; + Data::GroupCall::kSoundStatusKeptFor;
const auto speakingStartsFinishing = speakingFinishesAt const auto soundingStartsFinishing = soundingFinishesAt
- kBlobsEnterDuration; - kBlobsEnterDuration;
const auto speakingFinishes = (speakingStartsFinishing < now); const auto soundingFinishes = (soundingStartsFinishing < now);
if (speakingFinishes) { if (soundingFinishes) {
_blobsAnimation->enter = std::clamp( _blobsAnimation->enter = std::clamp(
(speakingFinishesAt - now) / float64(kBlobsEnterDuration), (soundingFinishesAt - now) / float64(kBlobsEnterDuration),
0., 0.,
1.); 1.);
} else if (_blobsAnimation->enter < 1.) { } else if (_blobsAnimation->enter < 1.) {
@ -418,9 +434,15 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (_blobsAnimation) { if (_blobsAnimation) {
const auto shift = QPointF(x + size / 2., y + size / 2.); const auto shift = QPointF(x + size / 2., y + size / 2.);
const auto speaking = _speakingAnimation.value(
_speaking ? 1. : 0.);
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.translate(shift); p.translate(shift);
_blobsAnimation->blobs.paint(p, st::groupCallMemberActiveStatus); const auto brush = anim::brush(
st::groupCallMemberInactiveStatus,
st::groupCallMemberActiveStatus,
speaking);
_blobsAnimation->blobs.paint(p, brush);
p.translate(-shift); p.translate(-shift);
p.setOpacity(1.); p.setOpacity(1.);
@ -537,28 +559,28 @@ MembersController::MembersController(
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) { ) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = !(!animDisabled && !deactivated); const auto hide = !(!animDisabled && !deactivated);
if (!(hide && _speakingAnimationHideLastTime)) { if (!(hide && _soundingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0; _soundingAnimationHideLastTime = hide ? crl::now() : 0;
} }
for (const auto [_, row] : _speakingRowBySsrc) { for (const auto [_, row] : _soundingRowBySsrc) {
if (hide) { if (hide) {
updateRowLevel(row, 0.); updateRowLevel(row, 0.);
} }
row->setSkipLevelUpdate(hide); row->setSkipLevelUpdate(hide);
} }
if (!hide && !_speakingAnimation.animating()) { if (!hide && !_soundingAnimation.animating()) {
_speakingAnimation.start(); _soundingAnimation.start();
} }
_skipRowLevelUpdate = hide; _skipRowLevelUpdate = hide;
}, _lifetime); }, _lifetime);
_speakingAnimation.init([=](crl::time now) { _soundingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0) if (const auto &last = _soundingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) { && (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop(); _soundingAnimation.stop();
return false; return false;
} }
for (const auto [ssrc, row] : _speakingRowBySsrc) { for (const auto [ssrc, row] : _soundingRowBySsrc) {
row->updateBlobAnimation(now); row->updateBlobAnimation(now);
delegate()->peerListUpdateRow(row); delegate()->peerListUpdateRow(row);
} }
@ -600,8 +622,8 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
call->levelUpdates( call->levelUpdates(
) | rpl::start_with_next([=](const LevelUpdate &update) { ) | rpl::start_with_next([=](const LevelUpdate &update) {
const auto i = _speakingRowBySsrc.find(update.ssrc); const auto i = _soundingRowBySsrc.find(update.ssrc);
if (i != end(_speakingRowBySsrc)) { if (i != end(_soundingRowBySsrc)) {
updateRowLevel(i->second, update.value); updateRowLevel(i->second, update.value);
} }
}, _lifetime); }, _lifetime);
@ -699,41 +721,41 @@ void MembersController::checkSpeakingRowPosition(not_null<Row*> row) {
void MembersController::updateRow( void MembersController::updateRow(
not_null<Row*> row, not_null<Row*> row,
const Data::GroupCall::Participant *participant) { const Data::GroupCall::Participant *participant) {
const auto wasSpeaking = row->speaking(); const auto wasSounding = row->sounding();
const auto wasSsrc = row->ssrc(); const auto wasSsrc = row->ssrc();
row->setSkipLevelUpdate(_skipRowLevelUpdate); row->setSkipLevelUpdate(_skipRowLevelUpdate);
row->updateState(participant); row->updateState(participant);
const auto nowSpeaking = row->speaking(); const auto nowSounding = row->sounding();
const auto nowSsrc = row->ssrc(); const auto nowSsrc = row->ssrc();
const auto wasNoSpeaking = _speakingRowBySsrc.empty(); const auto wasNoSounding = _soundingRowBySsrc.empty();
if (wasSsrc == nowSsrc) { if (wasSsrc == nowSsrc) {
if (nowSpeaking != wasSpeaking) { if (nowSounding != wasSounding) {
if (nowSpeaking) { if (nowSounding) {
_speakingRowBySsrc.emplace(nowSsrc, row); _soundingRowBySsrc.emplace(nowSsrc, row);
} else { } else {
_speakingRowBySsrc.remove(nowSsrc); _soundingRowBySsrc.remove(nowSsrc);
} }
} }
} else { } else {
_speakingRowBySsrc.remove(wasSsrc); _soundingRowBySsrc.remove(wasSsrc);
if (nowSpeaking) { if (nowSounding) {
Assert(nowSsrc != 0); Assert(nowSsrc != 0);
_speakingRowBySsrc.emplace(nowSsrc, row); _soundingRowBySsrc.emplace(nowSsrc, row);
} }
} }
const auto nowNoSpeaking = _speakingRowBySsrc.empty(); const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSpeaking && !nowNoSpeaking) { if (wasNoSounding && !nowNoSounding) {
_speakingAnimation.start(); _soundingAnimation.start();
} else if (nowNoSpeaking && !wasNoSpeaking) { } else if (nowNoSounding && !wasNoSounding) {
_speakingAnimation.stop(); _soundingAnimation.stop();
} }
delegate()->peerListUpdateRow(row); delegate()->peerListUpdateRow(row);
} }
void MembersController::removeRow(not_null<Row*> row) { void MembersController::removeRow(not_null<Row*> row) {
_speakingRowBySsrc.remove(row->ssrc()); _soundingRowBySsrc.remove(row->ssrc());
delegate()->peerListRemoveRow(row); delegate()->peerListRemoveRow(row);
} }

View file

@ -342,7 +342,10 @@ void GroupCall::applyParticipantsMutes(
} }
} }
void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) { void GroupCall::applyLastSpoke(
uint32 ssrc,
LastSpokeTimes when,
crl::time now) {
const auto i = _userBySsrc.find(ssrc); const auto i = _userBySsrc.find(ssrc);
if (i == end(_userBySsrc)) { if (i == end(_userBySsrc)) {
_unknownSpokenSsrcs[ssrc] = when; _unknownSpokenSsrcs[ssrc] = when;
@ -353,10 +356,13 @@ void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
Assert(j != end(_participants)); Assert(j != end(_participants));
_speakingByActiveFinishes.remove(j->user); _speakingByActiveFinishes.remove(j->user);
const auto speaking = (when + kSpeakStatusKeptFor >= now) const auto sounding = (when.anything + kSoundStatusKeptFor >= now)
&& j->canSelfUnmute; && j->canSelfUnmute;
if (j->speaking != speaking) { const auto speaking = sounding
&& (when.voice + kSoundStatusKeptFor >= now);
if (j->sounding != sounding || j->speaking != speaking) {
const auto was = *j; const auto was = *j;
j->sounding = sounding;
j->speaking = speaking; j->speaking = speaking;
_participantUpdates.fire({ _participantUpdates.fire({
.was = was, .was = was,
@ -367,7 +373,7 @@ void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
void GroupCall::applyActiveUpdate( void GroupCall::applyActiveUpdate(
UserId userId, UserId userId,
crl::time when, LastSpokeTimes when,
UserData *userLoaded) { UserData *userLoaded) {
if (inCall()) { if (inCall()) {
return; return;
@ -387,9 +393,9 @@ void GroupCall::applyActiveUpdate(
} }
const auto was = std::make_optional(*i); const auto was = std::make_optional(*i);
const auto now = crl::now(); const auto now = crl::now();
const auto elapsed = TimeId((now - when) / crl::time(1000)); const auto elapsed = TimeId((now - when.anything) / crl::time(1000));
const auto lastActive = base::unixtime::now() - elapsed; const auto lastActive = base::unixtime::now() - elapsed;
const auto finishes = when + kSpeakingAfterActive; const auto finishes = when.anything + kSpeakingAfterActive;
if (lastActive <= i->lastActive || finishes <= now) { if (lastActive <= i->lastActive || finishes <= now) {
return; return;
} }
@ -450,7 +456,7 @@ void GroupCall::requestUnknownParticipants() {
if (_unknownSpokenSsrcs.size() < kRequestPerPage) { if (_unknownSpokenSsrcs.size() < kRequestPerPage) {
return base::take(_unknownSpokenSsrcs); return base::take(_unknownSpokenSsrcs);
} }
auto result = base::flat_map<uint32, crl::time>(); auto result = base::flat_map<uint32, LastSpokeTimes>();
result.reserve(kRequestPerPage); result.reserve(kRequestPerPage);
while (result.size() < kRequestPerPage) { while (result.size() < kRequestPerPage) {
const auto [ssrc, when] = _unknownSpokenSsrcs.back(); const auto [ssrc, when] = _unknownSpokenSsrcs.back();
@ -463,7 +469,7 @@ void GroupCall::requestUnknownParticipants() {
if (_unknownSpokenUids.size() + ssrcs.size() < kRequestPerPage) { if (_unknownSpokenUids.size() + ssrcs.size() < kRequestPerPage) {
return base::take(_unknownSpokenUids); return base::take(_unknownSpokenUids);
} }
auto result = base::flat_map<UserId, crl::time>(); auto result = base::flat_map<UserId, LastSpokeTimes>();
const auto available = (kRequestPerPage - int(ssrcs.size())); const auto available = (kRequestPerPage - int(ssrcs.size()));
if (available > 0) { if (available > 0) {
result.reserve(available); result.reserve(available);

View file

@ -16,6 +16,11 @@ class ApiWrap;
namespace Data { namespace Data {
struct LastSpokeTimes {
crl::time anything = 0;
crl::time voice = 0;
};
class GroupCall final { class GroupCall final {
public: public:
GroupCall(not_null<PeerData*> peer, uint64 id, uint64 accessHash); GroupCall(not_null<PeerData*> peer, uint64 id, uint64 accessHash);
@ -32,6 +37,7 @@ public:
TimeId date = 0; TimeId date = 0;
TimeId lastActive = 0; TimeId lastActive = 0;
uint32 ssrc = 0; uint32 ssrc = 0;
bool sounding = false;
bool speaking = false; bool speaking = false;
bool muted = false; bool muted = false;
bool canSelfUnmute = false; bool canSelfUnmute = false;
@ -41,7 +47,7 @@ public:
std::optional<Participant> now; std::optional<Participant> now;
}; };
static constexpr auto kSpeakStatusKeptFor = crl::time(350); static constexpr auto kSoundStatusKeptFor = crl::time(350);
[[nodiscard]] auto participants() const [[nodiscard]] auto participants() const
-> const std::vector<Participant> &; -> const std::vector<Participant> &;
@ -56,10 +62,10 @@ public:
void applyUpdate(const MTPDupdateGroupCallParticipants &update); void applyUpdate(const MTPDupdateGroupCallParticipants &update);
void applyUpdateChecked( void applyUpdateChecked(
const MTPDupdateGroupCallParticipants &update); const MTPDupdateGroupCallParticipants &update);
void applyLastSpoke(uint32 ssrc, crl::time when, crl::time now); void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now);
void applyActiveUpdate( void applyActiveUpdate(
UserId userId, UserId userId,
crl::time when, LastSpokeTimes when,
UserData *userLoaded); UserData *userLoaded);
[[nodiscard]] int fullCount() const; [[nodiscard]] int fullCount() const;
@ -106,8 +112,8 @@ private:
QString _nextOffset; QString _nextOffset;
rpl::variable<int> _fullCount = 0; rpl::variable<int> _fullCount = 0;
base::flat_map<uint32, crl::time> _unknownSpokenSsrcs; base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
base::flat_map<UserId, crl::time> _unknownSpokenUids; base::flat_map<UserId, LastSpokeTimes> _unknownSpokenUids;
mtpRequestId _unknownUsersRequestId = 0; mtpRequestId _unknownUsersRequestId = 0;
rpl::event_stream<ParticipantUpdate> _participantUpdates; rpl::event_stream<ParticipantUpdate> _participantUpdates;

@ -1 +1 @@
Subproject commit e884535f1854aa57616b162a4901bd60c8287575 Subproject commit b892eb58bc941a4a1a67303439df8ffd379d6051