Update speaking status based on audio level.

This commit is contained in:
John Preston 2020-11-28 12:01:50 +03:00
parent b54a2aa40b
commit d1c821973a
6 changed files with 163 additions and 16 deletions

View file

@ -364,15 +364,20 @@ void GroupCall::createAndStartController() {
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>();
tgcalls::GroupInstanceDescriptor descriptor = { tgcalls::GroupInstanceDescriptor descriptor = {
.config = tgcalls::GroupConfig{ .config = tgcalls::GroupConfig{
}, },
.networkStateUpdated = [=](bool) { .networkStateUpdated = [=](bool) {
}, },
.audioLevelsUpdated = [=](const AudioLevels &data) { .audioLevelsUpdated = [=](const AudioLevels &data) {
crl::on_main(weak, [=] { audioLevelsUpdated(data); });
}, },
.myAudioLevelUpdated = [=](float level) { .myAudioLevelUpdated = [=](float level) {
if (*myLevel != level) { // Don't send many 0 while we're muted.
*myLevel = level;
crl::on_main(weak, [=] { myLevelUpdated(level); }); crl::on_main(weak, [=] { myLevelUpdated(level); });
}
}, },
.initialInputDeviceId = settings.callInputDeviceId().toStdString(), .initialInputDeviceId = settings.callInputDeviceId().toStdString(),
.initialOutputDeviceId = settings.callOutputDeviceId().toStdString(), .initialOutputDeviceId = settings.callOutputDeviceId().toStdString(),
@ -409,7 +414,22 @@ void GroupCall::createAndStartController() {
} }
void GroupCall::myLevelUpdated(float level) { 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<std::pair<std::uint32_t, float>> &data) {
for (const auto &[source, level] : data) {
_levelUpdates.fire(LevelUpdate{
.source = source,
.value = level,
.self = (source == _mySsrc)
});
}
} }
void GroupCall::sendMutedUpdate() { void GroupCall::sendMutedUpdate() {

View file

@ -25,6 +25,12 @@ enum class MuteState {
ForceMuted, ForceMuted,
}; };
struct LevelUpdate {
uint32 source = 0;
float value = 0.;
bool self = false;
};
class GroupCall final : public base::has_weak_ptr { class GroupCall final : public base::has_weak_ptr {
public: public:
class Delegate { class Delegate {
@ -82,6 +88,10 @@ public:
return _state.value(); return _state.value();
} }
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
return _levelUpdates.events();
}
void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentAudioDevice(bool input, const QString &deviceId);
//void setAudioVolume(bool input, float level); //void setAudioVolume(bool input, float level);
void setAudioDuckingEnabled(bool enabled); void setAudioDuckingEnabled(bool enabled);
@ -111,6 +121,8 @@ private:
void rejoin(); void rejoin();
void myLevelUpdated(float level); void myLevelUpdated(float level);
void audioLevelsUpdated(
const std::vector<std::pair<std::uint32_t, float>> &data);
[[nodiscard]] MTPInputGroupCall inputCall() const; [[nodiscard]] MTPInputGroupCall inputCall() const;
@ -128,6 +140,7 @@ private:
mtpRequestId _updateMuteRequestId = 0; mtpRequestId _updateMuteRequestId = 0;
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance; std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
rpl::event_stream<LevelUpdate> _levelUpdates;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

View file

@ -26,6 +26,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls { namespace Calls {
namespace { namespace {
constexpr auto kLevelThreshold = 0.01;
constexpr auto kLevelActiveTimeout = crl::time(1000);
enum class UpdateLevelResult {
NothingChanged,
LevelChanged,
StateChanged,
};
class Row final : public PeerListRow { class Row final : public PeerListRow {
public: public:
Row(not_null<ChannelData*> channel, not_null<UserData*> user); Row(not_null<ChannelData*> channel, not_null<UserData*> user);
@ -37,6 +46,7 @@ public:
}; };
void updateState(const Data::GroupCall::Participant *participant); void updateState(const Data::GroupCall::Participant *participant);
UpdateLevelResult updateLevel(float level);
[[nodiscard]] State state() const { [[nodiscard]] State state() const {
return _state; return _state;
} }
@ -70,6 +80,8 @@ public:
private: private:
void refreshStatus() override; void refreshStatus() override;
void refreshStatus(crl::time now);
void resetSpeakingState();
[[nodiscard]] static State ComputeState( [[nodiscard]] static State ComputeState(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
@ -80,6 +92,8 @@ private:
State _state = State::Inactive; State _state = State::Inactive;
not_null<ChannelData*> _channel; not_null<ChannelData*> _channel;
not_null<const style::IconButton*> _st; not_null<const style::IconButton*> _st;
float _level = 0.;
crl::time _markInactiveAt = 0;
std::unique_ptr<Ui::RippleAnimation> _actionRipple; std::unique_ptr<Ui::RippleAnimation> _actionRipple;
@ -121,10 +135,18 @@ private:
void updateRow( void updateRow(
not_null<Row*> row, not_null<Row*> row,
const Data::GroupCall::Participant *participant) const; const Data::GroupCall::Participant *participant) const;
void updateRowLevel(not_null<UserData*> user, float level) const;
Row *findRow(not_null<UserData*> user) const;
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
const base::weak_ptr<GroupCall> _call; const base::weak_ptr<GroupCall> _call;
const not_null<ChannelData*> _channel; const not_null<ChannelData*> _channel;
// Use only resolvedRealCall() method, not this value directly.
Data::GroupCall *_realCallRawValue = nullptr;
uint64 _realId = 0;
rpl::event_stream<MuteRequest> _toggleMuteRequests; rpl::event_stream<MuteRequest> _toggleMuteRequests;
rpl::variable<int> _fullCount = 1; rpl::variable<int> _fullCount = 1;
Ui::BoxPointer _addBox; Ui::BoxPointer _addBox;
@ -148,16 +170,48 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
setCustomStatus(QString()); setCustomStatus(QString());
} }
_state = State::Inactive; _state = State::Inactive;
resetSpeakingState();
} else if (!participant->muted) { } else if (!participant->muted) {
_state = State::Active; _state = State::Active;
} else if (participant->canSelfUnmute) { } else if (participant->canSelfUnmute) {
_state = State::Inactive; _state = State::Inactive;
resetSpeakingState();
} else { } else {
_state = State::Muted; _state = State::Muted;
resetSpeakingState();
} }
_st = ComputeIconStyle(_state); _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( void Row::paintAction(
Painter &p, Painter &p,
int x, int x,
@ -182,14 +236,16 @@ void Row::paintAction(
} }
void Row::refreshStatus() { void Row::refreshStatus() {
setCustomStatus([&] { refreshStatus(crl::now());
switch (_state) { }
case State::Inactive:
case State::Muted: return tr::lng_group_call_inactive(tr::now); void Row::refreshStatus(crl::time now) {
case State::Active: return tr::lng_group_call_active(tr::now); const auto active = (now < _markInactiveAt);
} setCustomStatus(
Unexpected("State in Row::refreshStatus."); (active
}(), (_state == State::Active)); ? tr::lng_group_call_active(tr::now)
: tr::lng_group_call_inactive(tr::now)),
active);
} }
Row::State Row::ComputeState( Row::State Row::ComputeState(
@ -272,9 +328,28 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
//updateRow(channel->session().user()); //updateRow(channel->session().user());
} }
}, _lifetime); }, _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<Data::GroupCall*> real) { void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
_realCallRawValue = real;
_realId = real->id();
_fullCount = real->fullCountValue( _fullCount = real->fullCountValue(
) | rpl::map([](int value) { ) | rpl::map([](int value) {
return std::max(value, 1); return std::max(value, 1);
@ -290,9 +365,9 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
) | rpl::start_with_next([=](const Update &update) { ) | rpl::start_with_next([=](const Update &update) {
const auto user = update.participant.user; const auto user = update.participant.user;
if (update.removed) { if (update.removed) {
if (auto row = delegate()->peerListFindRow(user->id)) { if (const auto row = findRow(user)) {
if (user->isSelf()) { if (user->isSelf()) {
static_cast<Row*>(row)->updateState(nullptr); row->updateState(nullptr);
delegate()->peerListUpdateRow(row); delegate()->peerListUpdateRow(row);
} else { } else {
delegate()->peerListRemoveRow(row); delegate()->peerListRemoveRow(row);
@ -307,8 +382,8 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
void MembersController::updateRow( void MembersController::updateRow(
const Data::GroupCall::Participant &participant) { const Data::GroupCall::Participant &participant) {
if (auto row = delegate()->peerListFindRow(participant.user->id)) { if (const auto row = findRow(participant.user)) {
updateRow(static_cast<Row*>(row), &participant); updateRow(row, &participant);
} else if (auto row = createRow(participant)) { } else if (auto row = createRow(participant)) {
delegate()->peerListAppendRow(std::move(row)); delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
@ -322,6 +397,30 @@ void MembersController::updateRow(
delegate()->peerListUpdateRow(row); delegate()->peerListUpdateRow(row);
} }
void MembersController::updateRowLevel(
not_null<UserData*> 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<UserData*> user) const {
return static_cast<Row*>(delegate()->peerListFindRow(user->id));
}
Data::GroupCall *MembersController::resolvedRealCall() const {
return (_realCallRawValue
&& (_channel->call() == _realCallRawValue)
&& (_realCallRawValue->id() == _realId))
? _realCallRawValue
: nullptr;
}
Main::Session &MembersController::session() const { Main::Session &MembersController::session() const {
return _call->channel()->session(); return _call->channel()->session();
} }
@ -473,9 +572,11 @@ int GroupMembers::desiredHeight() const {
auto count = [this] { auto count = [this] {
if (const auto call = _call.get()) { if (const auto call = _call.get()) {
if (const auto real = call->channel()->call()) { if (const auto real = call->channel()->call()) {
if (call->id() == real->id()) {
return real->fullCount(); return real->fullCount();
} }
} }
}
return 0; return 0;
}(); }();
desired += std::max(count, _list->fullRowsCount()) desired += std::max(count, _list->fullRowsCount())

View file

@ -71,7 +71,7 @@ private:
void addMember(); void addMember();
void updateHeaderControlsGeometry(int newWidth); void updateHeaderControlsGeometry(int newWidth);
base::weak_ptr<GroupCall> _call; const base::weak_ptr<GroupCall> _call;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<PeerListController> _listController; std::unique_ptr<PeerListController> _listController;
object_ptr<Ui::RpWidget> _header = { nullptr }; object_ptr<Ui::RpWidget> _header = { nullptr };

View file

@ -105,6 +105,11 @@ bool GroupCall::participantsLoaded() const {
return _allReceived; 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() { rpl::producer<> GroupCall::participantsSliceAdded() {
return _participantsSliceAdded.events(); return _participantsSliceAdded.events();
} }
@ -183,6 +188,7 @@ void GroupCall::applyParticipantsSlice(
.participant = *i, .participant = *i,
.removed = true, .removed = true,
}; };
_userBySource.erase(i->source);
_participants.erase(i); _participants.erase(i);
if (sendIndividualUpdates) { if (sendIndividualUpdates) {
_participantUpdates.fire(std::move(update)); _participantUpdates.fire(std::move(update));
@ -201,9 +207,14 @@ void GroupCall::applyParticipantsSlice(
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(), .canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
}; };
if (i == end(_participants)) { if (i == end(_participants)) {
_userBySource.emplace(value.source, value.user);
_participants.push_back(value); _participants.push_back(value);
++fullCount; ++fullCount;
} else { } else {
if (i->source != value.source) {
_userBySource.erase(i->source);
_userBySource.emplace(value.source, value.user);
}
*i = value; *i = value;
} }
_participantUpdates.fire({ _participantUpdates.fire({

View file

@ -39,6 +39,7 @@ public:
-> const std::vector<Participant> &; -> const std::vector<Participant> &;
void requestParticipants(); void requestParticipants();
[[nodiscard]] bool participantsLoaded() const; [[nodiscard]] bool participantsLoaded() const;
[[nodiscard]] UserData *userBySource(uint32 source) const;
[[nodiscard]] rpl::producer<> participantsSliceAdded(); [[nodiscard]] rpl::producer<> participantsSliceAdded();
[[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const; [[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const;
@ -72,6 +73,7 @@ private:
mtpRequestId _reloadRequestId = 0; mtpRequestId _reloadRequestId = 0;
std::vector<Participant> _participants; std::vector<Participant> _participants;
base::flat_map<uint32, not_null<UserData*>> _userBySource;
QString _nextOffset; QString _nextOffset;
rpl::variable<int> _fullCount = 0; rpl::variable<int> _fullCount = 0;