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) {
call->applyActiveUpdate(
userId,
when,
Data::LastSpokeTimes{
.anything = when,
.voice = when
},
peer->owner().userLoaded(userId));
}
}
@ -928,7 +931,10 @@ void Updates::handleSendActionUpdate(
const auto call = peer->groupCall();
const auto now = crl::now();
if (call) {
call->applyActiveUpdate(userId, now, user);
call->applyActiveUpdate(
userId,
Data::LastSpokeTimes{ .anything = now, .voice = now },
user);
} else {
const auto chat = peer->asChat();
const auto channel = peer->asChannel();

View file

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

View file

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

View file

@ -97,6 +97,9 @@ public:
[[nodiscard]] uint32 ssrc() const {
return _ssrc;
}
[[nodiscard]] bool sounding() const {
return _sounding;
}
[[nodiscard]] bool speaking() const {
return _speaking;
}
@ -147,7 +150,7 @@ private:
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0;
crl::time lastSoundingUpdateTime = 0;
float64 enter = 0.;
QImage userpicCache;
@ -156,6 +159,7 @@ private:
rpl::lifetime lifetime;
};
void refreshStatus() override;
void setSounding(bool sounding);
void setSpeaking(bool speaking);
void setState(State state);
void setSsrc(uint32 ssrc);
@ -172,6 +176,7 @@ private:
Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
Ui::Animations::Simple _activeAnimation; // For icon cross animation.
uint32 _ssrc = 0;
bool _sounding = false;
bool _speaking = false;
bool _skipLevelUpdate = false;
@ -252,10 +257,10 @@ private:
base::unique_qptr<Ui::PopupMenu> _menu;
base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
base::flat_map<uint32, not_null<Row*>> _speakingRowBySsrc;
Ui::Animations::Basic _speakingAnimation;
base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
Ui::Animations::Basic _soundingAnimation;
crl::time _speakingAnimationHideLastTime = 0;
crl::time _soundingAnimationHideLastTime = 0;
bool _skipRowLevelUpdate = false;
Ui::CrossLineAnimation _inactiveCrossLine;
@ -284,16 +289,20 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
setCustomStatus(QString());
}
setState(State::Inactive);
setSounding(false);
setSpeaking(false);
} else if (!participant->muted
|| (participant->speaking && participant->ssrc != 0)) {
|| (participant->sounding && participant->ssrc != 0)) {
setState(State::Active);
setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0);
} else if (participant->canSelfUnmute) {
setState(State::Inactive);
setSounding(false);
setSpeaking(false);
} else {
setState(State::Muted);
setSounding(false);
setSpeaking(false);
}
}
@ -308,7 +317,14 @@ void Row::setSpeaking(bool speaking) {
_speaking ? 0. : 1.,
_speaking ? 1. : 0.,
st::widgetFadeDuration);
if (!_speaking) {
}
void Row::setSounding(bool sounding) {
if (_sounding == sounding) {
return;
}
_sounding = sounding;
if (!_sounding) {
_blobsAnimation = nullptr;
} else if (!_blobsAnimation) {
_blobsAnimation = std::make_unique<BlobsAnimation>(
@ -358,7 +374,7 @@ void Row::updateLevel(float level) {
}
if (level >= GroupCall::kSpeakLevelThreshold) {
_blobsAnimation->lastSpeakingUpdateTime = crl::now();
_blobsAnimation->lastSoundingUpdateTime = crl::now();
}
_blobsAnimation->blobs.setLevel(level);
}
@ -366,14 +382,14 @@ void Row::updateLevel(float level) {
void Row::updateBlobAnimation(crl::time now) {
Expects(_blobsAnimation != nullptr);
const auto speakingFinishesAt = _blobsAnimation->lastSpeakingUpdateTime
+ Data::GroupCall::kSpeakStatusKeptFor;
const auto speakingStartsFinishing = speakingFinishesAt
const auto soundingFinishesAt = _blobsAnimation->lastSoundingUpdateTime
+ Data::GroupCall::kSoundStatusKeptFor;
const auto soundingStartsFinishing = soundingFinishesAt
- kBlobsEnterDuration;
const auto speakingFinishes = (speakingStartsFinishing < now);
if (speakingFinishes) {
const auto soundingFinishes = (soundingStartsFinishing < now);
if (soundingFinishes) {
_blobsAnimation->enter = std::clamp(
(speakingFinishesAt - now) / float64(kBlobsEnterDuration),
(soundingFinishesAt - now) / float64(kBlobsEnterDuration),
0.,
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 {
if (_blobsAnimation) {
const auto shift = QPointF(x + size / 2., y + size / 2.);
const auto speaking = _speakingAnimation.value(
_speaking ? 1. : 0.);
auto hq = PainterHighQualityEnabler(p);
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.setOpacity(1.);
@ -537,28 +559,28 @@ MembersController::MembersController(
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = !(!animDisabled && !deactivated);
if (!(hide && _speakingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
if (!(hide && _soundingAnimationHideLastTime)) {
_soundingAnimationHideLastTime = hide ? crl::now() : 0;
}
for (const auto [_, row] : _speakingRowBySsrc) {
for (const auto [_, row] : _soundingRowBySsrc) {
if (hide) {
updateRowLevel(row, 0.);
}
row->setSkipLevelUpdate(hide);
}
if (!hide && !_speakingAnimation.animating()) {
_speakingAnimation.start();
if (!hide && !_soundingAnimation.animating()) {
_soundingAnimation.start();
}
_skipRowLevelUpdate = hide;
}, _lifetime);
_speakingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
_soundingAnimation.init([=](crl::time now) {
if (const auto &last = _soundingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop();
_soundingAnimation.stop();
return false;
}
for (const auto [ssrc, row] : _speakingRowBySsrc) {
for (const auto [ssrc, row] : _soundingRowBySsrc) {
row->updateBlobAnimation(now);
delegate()->peerListUpdateRow(row);
}
@ -600,8 +622,8 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
call->levelUpdates(
) | rpl::start_with_next([=](const LevelUpdate &update) {
const auto i = _speakingRowBySsrc.find(update.ssrc);
if (i != end(_speakingRowBySsrc)) {
const auto i = _soundingRowBySsrc.find(update.ssrc);
if (i != end(_soundingRowBySsrc)) {
updateRowLevel(i->second, update.value);
}
}, _lifetime);
@ -699,41 +721,41 @@ void MembersController::checkSpeakingRowPosition(not_null<Row*> row) {
void MembersController::updateRow(
not_null<Row*> row,
const Data::GroupCall::Participant *participant) {
const auto wasSpeaking = row->speaking();
const auto wasSounding = row->sounding();
const auto wasSsrc = row->ssrc();
row->setSkipLevelUpdate(_skipRowLevelUpdate);
row->updateState(participant);
const auto nowSpeaking = row->speaking();
const auto nowSounding = row->sounding();
const auto nowSsrc = row->ssrc();
const auto wasNoSpeaking = _speakingRowBySsrc.empty();
const auto wasNoSounding = _soundingRowBySsrc.empty();
if (wasSsrc == nowSsrc) {
if (nowSpeaking != wasSpeaking) {
if (nowSpeaking) {
_speakingRowBySsrc.emplace(nowSsrc, row);
if (nowSounding != wasSounding) {
if (nowSounding) {
_soundingRowBySsrc.emplace(nowSsrc, row);
} else {
_speakingRowBySsrc.remove(nowSsrc);
_soundingRowBySsrc.remove(nowSsrc);
}
}
} else {
_speakingRowBySsrc.remove(wasSsrc);
if (nowSpeaking) {
_soundingRowBySsrc.remove(wasSsrc);
if (nowSounding) {
Assert(nowSsrc != 0);
_speakingRowBySsrc.emplace(nowSsrc, row);
_soundingRowBySsrc.emplace(nowSsrc, row);
}
}
const auto nowNoSpeaking = _speakingRowBySsrc.empty();
if (wasNoSpeaking && !nowNoSpeaking) {
_speakingAnimation.start();
} else if (nowNoSpeaking && !wasNoSpeaking) {
_speakingAnimation.stop();
const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSounding && !nowNoSounding) {
_soundingAnimation.start();
} else if (nowNoSounding && !wasNoSounding) {
_soundingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
void MembersController::removeRow(not_null<Row*> row) {
_speakingRowBySsrc.remove(row->ssrc());
_soundingRowBySsrc.remove(row->ssrc());
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);
if (i == end(_userBySsrc)) {
_unknownSpokenSsrcs[ssrc] = when;
@ -353,10 +356,13 @@ void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
Assert(j != end(_participants));
_speakingByActiveFinishes.remove(j->user);
const auto speaking = (when + kSpeakStatusKeptFor >= now)
const auto sounding = (when.anything + kSoundStatusKeptFor >= now)
&& 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;
j->sounding = sounding;
j->speaking = speaking;
_participantUpdates.fire({
.was = was,
@ -367,7 +373,7 @@ void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
void GroupCall::applyActiveUpdate(
UserId userId,
crl::time when,
LastSpokeTimes when,
UserData *userLoaded) {
if (inCall()) {
return;
@ -387,9 +393,9 @@ void GroupCall::applyActiveUpdate(
}
const auto was = std::make_optional(*i);
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 finishes = when + kSpeakingAfterActive;
const auto finishes = when.anything + kSpeakingAfterActive;
if (lastActive <= i->lastActive || finishes <= now) {
return;
}
@ -450,7 +456,7 @@ void GroupCall::requestUnknownParticipants() {
if (_unknownSpokenSsrcs.size() < kRequestPerPage) {
return base::take(_unknownSpokenSsrcs);
}
auto result = base::flat_map<uint32, crl::time>();
auto result = base::flat_map<uint32, LastSpokeTimes>();
result.reserve(kRequestPerPage);
while (result.size() < kRequestPerPage) {
const auto [ssrc, when] = _unknownSpokenSsrcs.back();
@ -463,7 +469,7 @@ void GroupCall::requestUnknownParticipants() {
if (_unknownSpokenUids.size() + ssrcs.size() < kRequestPerPage) {
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()));
if (available > 0) {
result.reserve(available);

View file

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

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