mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Update speaking status based on audio level.
This commit is contained in:
parent
b54a2aa40b
commit
d1c821973a
6 changed files with 163 additions and 16 deletions
|
@ -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) {
|
||||||
crl::on_main(weak, [=] { myLevelUpdated(level); });
|
if (*myLevel != level) { // Don't send many 0 while we're muted.
|
||||||
|
*myLevel = 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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,7 +572,9 @@ 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()) {
|
||||||
return real->fullCount();
|
if (call->id() == real->id()) {
|
||||||
|
return real->fullCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue