Track speaking state in participants list.
This commit is contained in:
parent
3a5b625d64
commit
058199aa0d
|
@ -760,7 +760,7 @@ public:
|
||||||
_content->reorderRows([&](
|
_content->reorderRows([&](
|
||||||
auto &&begin,
|
auto &&begin,
|
||||||
auto &&end) {
|
auto &&end) {
|
||||||
std::sort(begin, end, [&](auto &&a, auto &&b) {
|
std::stable_sort(begin, end, [&](auto &&a, auto &&b) {
|
||||||
return compare(*a, *b);
|
return compare(*a, *b);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,6 +34,8 @@ namespace Calls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMaxInvitePerSlice = 10;
|
constexpr auto kMaxInvitePerSlice = 10;
|
||||||
|
constexpr auto kCheckLastSpokeInterval = 3 * crl::time(1000);
|
||||||
|
constexpr auto kSpeakLevelThreshold = 0.2;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -43,7 +45,8 @@ GroupCall::GroupCall(
|
||||||
const MTPInputGroupCall &inputCall)
|
const MTPInputGroupCall &inputCall)
|
||||||
: _delegate(delegate)
|
: _delegate(delegate)
|
||||||
, _channel(channel)
|
, _channel(channel)
|
||||||
, _api(&_channel->session().mtp()) {
|
, _api(&_channel->session().mtp())
|
||||||
|
, _lastSpokeCheckTimer([=] { checkLastSpoke(); }) {
|
||||||
const auto id = inputCall.c_inputGroupCall().vid().v;
|
const auto id = inputCall.c_inputGroupCall().vid().v;
|
||||||
if (id) {
|
if (id) {
|
||||||
if (const auto call = _channel->call(); call && call->id() == id) {
|
if (const auto call = _channel->call(); call && call->id() == id) {
|
||||||
|
@ -119,9 +122,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||||
using Update = Data::GroupCall::ParticipantUpdate;
|
using Update = Data::GroupCall::ParticipantUpdate;
|
||||||
_channel->call()->participantUpdated(
|
_channel->call()->participantUpdated(
|
||||||
) | rpl::filter([=](const Update &update) {
|
) | rpl::filter([=](const Update &update) {
|
||||||
return (_instance != nullptr) && update.removed;
|
return (_instance != nullptr) && !update.now;
|
||||||
}) | rpl::start_with_next([=](const Update &update) {
|
}) | rpl::start_with_next([=](const Update &update) {
|
||||||
_instance->removeSsrcs({ update.participant.source });
|
Expects(update.was.has_value());
|
||||||
|
|
||||||
|
_instance->removeSsrcs({ update.was->source });
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +409,9 @@ void GroupCall::createAndStartController() {
|
||||||
crl::on_main(weak, [=] { setInstanceConnected(connected); });
|
crl::on_main(weak, [=] { setInstanceConnected(connected); });
|
||||||
},
|
},
|
||||||
.audioLevelsUpdated = [=](const AudioLevels &data) {
|
.audioLevelsUpdated = [=](const AudioLevels &data) {
|
||||||
|
if (!data.empty()) {
|
||||||
crl::on_main(weak, [=] { audioLevelsUpdated(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.
|
if (*myLevel != level) { // Don't send many 0 while we're muted.
|
||||||
|
@ -446,22 +453,77 @@ void GroupCall::createAndStartController() {
|
||||||
//raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
|
//raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::myLevelUpdated(float level) {
|
void GroupCall::handleLevelsUpdated(
|
||||||
_levelUpdates.fire(LevelUpdate{
|
gsl::span<const std::pair<std::uint32_t, float>> data) {
|
||||||
.source = _mySsrc,
|
Expects(!data.empty());
|
||||||
.value = level,
|
|
||||||
.self = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCall::audioLevelsUpdated(
|
auto check = false;
|
||||||
const std::vector<std::pair<std::uint32_t, float>> &data) {
|
auto checkNow = false;
|
||||||
|
const auto now = crl::now();
|
||||||
for (const auto &[source, level] : data) {
|
for (const auto &[source, level] : data) {
|
||||||
_levelUpdates.fire(LevelUpdate{
|
_levelUpdates.fire(LevelUpdate{
|
||||||
.source = source,
|
.source = source,
|
||||||
.value = level,
|
.value = level,
|
||||||
.self = (source == _mySsrc)
|
.self = (source == _mySsrc)
|
||||||
});
|
});
|
||||||
|
if (level <= kSpeakLevelThreshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check = true;
|
||||||
|
const auto i = _lastSpoke.find(source);
|
||||||
|
if (i == _lastSpoke.end()) {
|
||||||
|
_lastSpoke.emplace(source, now);
|
||||||
|
checkNow = true;
|
||||||
|
} else {
|
||||||
|
if (i->second + kCheckLastSpokeInterval / 3 <= now) {
|
||||||
|
checkNow = true;
|
||||||
|
}
|
||||||
|
i->second = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (checkNow) {
|
||||||
|
checkLastSpoke();
|
||||||
|
} else if (check && !_lastSpokeCheckTimer.isActive()) {
|
||||||
|
_lastSpokeCheckTimer.callEach(kCheckLastSpokeInterval / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = _channel->call();
|
||||||
|
if (!real || real->id() != _id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hasRecent = false;
|
||||||
|
const auto now = crl::now();
|
||||||
|
auto list = base::take(_lastSpoke);
|
||||||
|
for (auto i = list.begin(); i != list.end();) {
|
||||||
|
const auto [source, when] = *i;
|
||||||
|
if (when + kCheckLastSpokeInterval >= now) {
|
||||||
|
hasRecent = true;
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
i = list.erase(i);
|
||||||
|
}
|
||||||
|
real->applyLastSpoke(source, when, now);
|
||||||
|
}
|
||||||
|
_lastSpoke = std::move(list);
|
||||||
|
|
||||||
|
if (!hasRecent) {
|
||||||
|
_lastSpokeCheckTimer.cancel();
|
||||||
|
} else if (!_lastSpokeCheckTimer.isActive()) {
|
||||||
|
_lastSpokeCheckTimer.callEach(kCheckLastSpokeInterval / 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,10 @@ private:
|
||||||
void myLevelUpdated(float level);
|
void myLevelUpdated(float level);
|
||||||
void audioLevelsUpdated(
|
void audioLevelsUpdated(
|
||||||
const std::vector<std::pair<std::uint32_t, float>> &data);
|
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();
|
||||||
|
|
||||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||||
|
|
||||||
|
@ -145,6 +148,8 @@ 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::Timer _lastSpokeCheckTimer;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ namespace Calls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kLevelThreshold = 0.2;
|
constexpr auto kLevelThreshold = 0.2;
|
||||||
constexpr auto kLevelActiveTimeout = crl::time(1000);
|
|
||||||
|
|
||||||
struct UpdateLevelResult {
|
struct UpdateLevelResult {
|
||||||
bool levelChanged = false;
|
bool levelChanged = false;
|
||||||
|
@ -49,10 +48,13 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
void updateState(const Data::GroupCall::Participant *participant);
|
void updateState(const Data::GroupCall::Participant *participant);
|
||||||
UpdateLevelResult updateLevel(float level);
|
//UpdateLevelResult updateLevel(float level);
|
||||||
[[nodiscard]] State state() const {
|
[[nodiscard]] State state() const {
|
||||||
return _state;
|
return _state;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool speaking() const {
|
||||||
|
return _speaking;
|
||||||
|
}
|
||||||
|
|
||||||
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
||||||
void stopLastActionRipple() override;
|
void stopLastActionRipple() override;
|
||||||
|
@ -83,8 +85,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshStatus() override;
|
void refreshStatus() override;
|
||||||
void refreshStatus(crl::time now);
|
void setSpeaking(bool speaking);
|
||||||
void resetSpeakingState();
|
|
||||||
|
|
||||||
[[nodiscard]] static State ComputeState(
|
[[nodiscard]] static State ComputeState(
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
|
@ -95,8 +96,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.;
|
bool _speaking = false;
|
||||||
crl::time _markInactiveAt = 0;
|
//float _level = 0.;
|
||||||
|
|
||||||
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
||||||
|
|
||||||
|
@ -130,16 +131,18 @@ private:
|
||||||
const Data::GroupCall::Participant &participant) const;
|
const Data::GroupCall::Participant &participant) const;
|
||||||
|
|
||||||
void prepareRows(not_null<Data::GroupCall*> real);
|
void prepareRows(not_null<Data::GroupCall*> real);
|
||||||
void repaintByTimer();
|
//void repaintByTimer();
|
||||||
|
|
||||||
void setupListChangeViewers(not_null<GroupCall*> call);
|
void setupListChangeViewers(not_null<GroupCall*> call);
|
||||||
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
||||||
void updateRow(
|
void updateRow(
|
||||||
const Data::GroupCall::Participant &participant);
|
const std::optional<Data::GroupCall::Participant> &was,
|
||||||
|
const Data::GroupCall::Participant &now);
|
||||||
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);
|
void checkSpeakingRowPosition(not_null<Row*> row);
|
||||||
|
//void updateRowLevel(not_null<UserData*> user, float level);
|
||||||
Row *findRow(not_null<UserData*> user) const;
|
Row *findRow(not_null<UserData*> user) const;
|
||||||
|
|
||||||
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
|
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
|
||||||
|
@ -155,8 +158,8 @@ private:
|
||||||
rpl::variable<int> _fullCount = 1;
|
rpl::variable<int> _fullCount = 1;
|
||||||
Ui::BoxPointer _addBox;
|
Ui::BoxPointer _addBox;
|
||||||
|
|
||||||
base::flat_map<not_null<UserData*>, crl::time> _repaintByTimer;
|
//base::flat_map<not_null<UserData*>, crl::time> _repaintByTimer;
|
||||||
base::Timer _repaintTimer;
|
//base::Timer _repaintTimer;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
@ -178,54 +181,61 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
|
||||||
setCustomStatus(QString());
|
setCustomStatus(QString());
|
||||||
}
|
}
|
||||||
_state = State::Inactive;
|
_state = State::Inactive;
|
||||||
resetSpeakingState();
|
setSpeaking(false);
|
||||||
} else if (!participant->muted) {
|
} else if (!participant->muted) {
|
||||||
_state = State::Active;
|
_state = State::Active;
|
||||||
|
setSpeaking(participant->speaking);
|
||||||
} else if (participant->canSelfUnmute) {
|
} else if (participant->canSelfUnmute) {
|
||||||
_state = State::Inactive;
|
_state = State::Inactive;
|
||||||
resetSpeakingState();
|
setSpeaking(false);
|
||||||
} else {
|
} else {
|
||||||
_state = State::Muted;
|
_state = State::Muted;
|
||||||
resetSpeakingState();
|
setSpeaking(false);
|
||||||
}
|
}
|
||||||
_st = ComputeIconStyle(_state);
|
_st = ComputeIconStyle(_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::resetSpeakingState() {
|
void Row::setSpeaking(bool speaking) {
|
||||||
_markInactiveAt = 0;
|
if (_speaking == speaking) {
|
||||||
updateLevel(0.);
|
return;
|
||||||
|
}
|
||||||
|
_speaking = speaking;
|
||||||
|
refreshStatus();
|
||||||
|
//if (!_speaking) {
|
||||||
|
// updateLevel(0.);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateLevelResult Row::updateLevel(float level) {
|
//UpdateLevelResult Row::updateLevel(float level) {
|
||||||
if (_level == level) {
|
// if (_level == level) {
|
||||||
return UpdateLevelResult{ .nextUpdateTime = _markInactiveAt };
|
// return UpdateLevelResult{ .nextUpdateTime = _markInactiveAt };
|
||||||
}
|
// }
|
||||||
const auto now = crl::now();
|
// const auto now = crl::now();
|
||||||
const auto stillActive = (now < _markInactiveAt);
|
// const auto stillActive = (now < _markInactiveAt);
|
||||||
const auto wasActive = (_level >= kLevelThreshold) && stillActive;
|
// const auto wasActive = (_level >= kLevelThreshold) && stillActive;
|
||||||
const auto nowActive = (level >= kLevelThreshold);
|
// const auto nowActive = (level >= kLevelThreshold);
|
||||||
if (nowActive) {
|
// if (nowActive) {
|
||||||
_markInactiveAt = now + kLevelActiveTimeout;
|
// _markInactiveAt = now + kLevelActiveTimeout;
|
||||||
if (_state != State::Active) {
|
// if (_state != State::Active) {
|
||||||
_state = State::Active;
|
// _state = State::Active;
|
||||||
_st = ComputeIconStyle(_state);
|
// _st = ComputeIconStyle(_state);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
_level = level;
|
// _level = level;
|
||||||
const auto changed = wasActive != (nowActive || stillActive);
|
// const auto changed = wasActive != (nowActive || stillActive);
|
||||||
if (!changed) {
|
// if (!changed) {
|
||||||
return UpdateLevelResult{
|
// return UpdateLevelResult{
|
||||||
.levelChanged = true,
|
// .levelChanged = true,
|
||||||
.nextUpdateTime = _markInactiveAt,
|
// .nextUpdateTime = _markInactiveAt,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
refreshStatus(now);
|
// refreshStatus(now);
|
||||||
return UpdateLevelResult{
|
// return UpdateLevelResult{
|
||||||
.levelChanged = true,
|
// .levelChanged = true,
|
||||||
.stateChanged = true,
|
// .stateChanged = true,
|
||||||
.nextUpdateTime = _markInactiveAt,
|
// .nextUpdateTime = _markInactiveAt,
|
||||||
};
|
// };
|
||||||
}
|
//}
|
||||||
|
|
||||||
void Row::paintAction(
|
void Row::paintAction(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
@ -251,16 +261,11 @@ void Row::paintAction(
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::refreshStatus() {
|
void Row::refreshStatus() {
|
||||||
refreshStatus(crl::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Row::refreshStatus(crl::time now) {
|
|
||||||
const auto active = (now < _markInactiveAt);
|
|
||||||
setCustomStatus(
|
setCustomStatus(
|
||||||
(active
|
(_speaking
|
||||||
? tr::lng_group_call_active(tr::now)
|
? tr::lng_group_call_active(tr::now)
|
||||||
: tr::lng_group_call_inactive(tr::now)),
|
: tr::lng_group_call_inactive(tr::now)),
|
||||||
active);
|
_speaking);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row::State Row::ComputeState(
|
Row::State Row::ComputeState(
|
||||||
|
@ -315,8 +320,8 @@ void Row::stopLastActionRipple() {
|
||||||
|
|
||||||
MembersController::MembersController(not_null<GroupCall*> call)
|
MembersController::MembersController(not_null<GroupCall*> call)
|
||||||
: _call(call)
|
: _call(call)
|
||||||
, _channel(call->channel())
|
, _channel(call->channel()) {
|
||||||
, _repaintTimer([=] { repaintByTimer(); }) {
|
//, _repaintTimer([=] { repaintByTimer(); }) {
|
||||||
setupListChangeViewers(call);
|
setupListChangeViewers(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,21 +350,21 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
call->levelUpdates(
|
//call->levelUpdates(
|
||||||
) | rpl::start_with_next([=](const LevelUpdate &update) {
|
//) | rpl::start_with_next([=](const LevelUpdate &update) {
|
||||||
const auto findUserBySource = [&](uint32 source) -> UserData* {
|
// const auto findUserBySource = [&](uint32 source) -> UserData* {
|
||||||
if (const auto real = resolvedRealCall()) {
|
// if (const auto real = resolvedRealCall()) {
|
||||||
return real->userBySource(source);
|
// return real->userBySource(source);
|
||||||
}
|
// }
|
||||||
return nullptr;
|
// return nullptr;
|
||||||
};
|
// };
|
||||||
const auto user = update.self
|
// const auto user = update.self
|
||||||
? _channel->session().user().get()
|
// ? _channel->session().user().get()
|
||||||
: findUserBySource(update.source);
|
// : findUserBySource(update.source);
|
||||||
if (user) {
|
// if (user) {
|
||||||
updateRowLevel(user, update.value);
|
// updateRowLevel(user, update.value);
|
||||||
}
|
// }
|
||||||
}, _lifetime);
|
//}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
|
@ -379,8 +384,10 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
using Update = Data::GroupCall::ParticipantUpdate;
|
using Update = Data::GroupCall::ParticipantUpdate;
|
||||||
real->participantUpdated(
|
real->participantUpdated(
|
||||||
) | rpl::start_with_next([=](const Update &update) {
|
) | rpl::start_with_next([=](const Update &update) {
|
||||||
const auto user = update.participant.user;
|
Expects(update.was.has_value() || update.now.has_value());
|
||||||
if (update.removed) {
|
|
||||||
|
const auto user = update.was ? update.was->user : update.now->user;
|
||||||
|
if (!update.now) {
|
||||||
if (const auto row = findRow(user)) {
|
if (const auto row = findRow(user)) {
|
||||||
if (user->isSelf()) {
|
if (user->isSelf()) {
|
||||||
row->updateState(nullptr);
|
row->updateState(nullptr);
|
||||||
|
@ -391,21 +398,60 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateRow(update.participant);
|
updateRow(update.was, *update.now);
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::updateRow(
|
void MembersController::updateRow(
|
||||||
const Data::GroupCall::Participant &participant) {
|
const std::optional<Data::GroupCall::Participant> &was,
|
||||||
if (const auto row = findRow(participant.user)) {
|
const Data::GroupCall::Participant &now) {
|
||||||
updateRow(row, &participant);
|
if (const auto row = findRow(now.user)) {
|
||||||
} else if (auto row = createRow(participant)) {
|
if (now.speaking && (!was || !was->speaking)) {
|
||||||
|
checkSpeakingRowPosition(row);
|
||||||
|
}
|
||||||
|
updateRow(row, &now);
|
||||||
|
} else if (auto row = createRow(now)) {
|
||||||
|
if (now.speaking) {
|
||||||
|
delegate()->peerListPrependRow(std::move(row));
|
||||||
|
} else {
|
||||||
delegate()->peerListAppendRow(std::move(row));
|
delegate()->peerListAppendRow(std::move(row));
|
||||||
|
}
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MembersController::checkSpeakingRowPosition(not_null<Row*> row) {
|
||||||
|
// Check if there are non-speaking rows above this one.
|
||||||
|
const auto count = delegate()->peerListFullRowsCount();
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
const auto above = delegate()->peerListRowAt(i);
|
||||||
|
if (above == row) {
|
||||||
|
// All rows above are speaking.
|
||||||
|
return;
|
||||||
|
} else if (!static_cast<Row*>(above.get())->speaking()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Someone started speaking and has a non-speaking row above him. Sort.
|
||||||
|
const auto proj = [&](const PeerListRow &other) {
|
||||||
|
if (&other == row.get()) {
|
||||||
|
// Bring this new one to the top.
|
||||||
|
return 0;
|
||||||
|
} else if (static_cast<const Row&>(other).speaking()) {
|
||||||
|
// Bring all the speaking ones below him.
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
delegate()->peerListSortRows([&](
|
||||||
|
const PeerListRow &a,
|
||||||
|
const PeerListRow &b) {
|
||||||
|
return proj(a) < proj(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void MembersController::updateRow(
|
void MembersController::updateRow(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
const Data::GroupCall::Participant *participant) const {
|
const Data::GroupCall::Participant *participant) const {
|
||||||
|
@ -413,47 +459,47 @@ void MembersController::updateRow(
|
||||||
delegate()->peerListUpdateRow(row);
|
delegate()->peerListUpdateRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::updateRowLevel(
|
//void MembersController::updateRowLevel(
|
||||||
not_null<UserData*> user,
|
// not_null<UserData*> user,
|
||||||
float level) {
|
// float level) {
|
||||||
if (const auto row = findRow(user)) {
|
// if (const auto row = findRow(user)) {
|
||||||
const auto result = row->updateLevel(level);
|
// const auto result = row->updateLevel(level);
|
||||||
if (result.stateChanged) {
|
// if (result.stateChanged) {
|
||||||
// #TODO calls reorder.
|
// // #TODO calls reorder.
|
||||||
}
|
// }
|
||||||
if (result.stateChanged) {
|
// if (result.stateChanged) {
|
||||||
delegate()->peerListUpdateRow(row);
|
// delegate()->peerListUpdateRow(row);
|
||||||
}
|
// }
|
||||||
if (result.nextUpdateTime) {
|
// if (result.nextUpdateTime) {
|
||||||
_repaintByTimer[user] = result.nextUpdateTime;
|
// _repaintByTimer[user] = result.nextUpdateTime;
|
||||||
if (!_repaintTimer.isActive()) {
|
// if (!_repaintTimer.isActive()) {
|
||||||
_repaintTimer.callOnce(kLevelActiveTimeout);
|
// _repaintTimer.callOnce(kLevelActiveTimeout);
|
||||||
}
|
// }
|
||||||
} else if (_repaintByTimer.remove(user) && _repaintByTimer.empty()) {
|
// } else if (_repaintByTimer.remove(user) && _repaintByTimer.empty()) {
|
||||||
_repaintTimer.cancel();
|
// _repaintTimer.cancel();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
void MembersController::repaintByTimer() {
|
//void MembersController::repaintByTimer() {
|
||||||
const auto now = crl::now();
|
// const auto now = crl::now();
|
||||||
auto next = crl::time(0);
|
// auto next = crl::time(0);
|
||||||
for (auto i = begin(_repaintByTimer); i != end(_repaintByTimer);) {
|
// for (auto i = begin(_repaintByTimer); i != end(_repaintByTimer);) {
|
||||||
if (i->second > now) {
|
// if (i->second > now) {
|
||||||
if (!next || next > i->second) {
|
// if (!next || next > i->second) {
|
||||||
next = i->second;
|
// next = i->second;
|
||||||
}
|
// }
|
||||||
} else if (const auto row = findRow(i->first)) {
|
// } else if (const auto row = findRow(i->first)) {
|
||||||
delegate()->peerListUpdateRow(row);
|
// delegate()->peerListUpdateRow(row);
|
||||||
i = _repaintByTimer.erase(i);
|
// i = _repaintByTimer.erase(i);
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
++i;
|
// ++i;
|
||||||
}
|
// }
|
||||||
if (next) {
|
// if (next) {
|
||||||
_repaintTimer.callOnce(next - now);
|
// _repaintTimer.callOnce(next - now);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
Row *MembersController::findRow(not_null<UserData*> user) const {
|
Row *MembersController::findRow(not_null<UserData*> user) const {
|
||||||
return static_cast<Row*>(delegate()->peerListFindRow(user->id));
|
return static_cast<Row*>(delegate()->peerListFindRow(user->id));
|
||||||
|
@ -681,7 +727,7 @@ void GroupMembers::setupButtons(not_null<GroupCall*> call) {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
_addMember->showOn(Data::CanWriteValue(
|
_addMember->showOn(Data::CanWriteValue(
|
||||||
call->channel()
|
call->channel().get()
|
||||||
));
|
));
|
||||||
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
|
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
|
||||||
_addMemberRequests.fire({});
|
_addMemberRequests.fire({});
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kRequestPerPage = 30;
|
constexpr auto kRequestPerPage = 30;
|
||||||
|
constexpr auto kSpeakStatusKeptFor = crl::time(1000);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -186,8 +187,7 @@ void GroupCall::applyParticipantsSlice(
|
||||||
if (data.is_left()) {
|
if (data.is_left()) {
|
||||||
if (i != end(_participants)) {
|
if (i != end(_participants)) {
|
||||||
auto update = ParticipantUpdate{
|
auto update = ParticipantUpdate{
|
||||||
.participant = *i,
|
.was = *i,
|
||||||
.removed = true,
|
|
||||||
};
|
};
|
||||||
_userBySource.erase(i->source);
|
_userBySource.erase(i->source);
|
||||||
_participants.erase(i);
|
_participants.erase(i);
|
||||||
|
@ -200,10 +200,15 @@ void GroupCall::applyParticipantsSlice(
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto was = (i != end(_participants))
|
||||||
|
? std::make_optional(*i)
|
||||||
|
: std::nullopt;
|
||||||
const auto value = Participant{
|
const auto value = Participant{
|
||||||
.user = user,
|
.user = user,
|
||||||
.date = data.vdate().v,
|
.date = data.vdate().v,
|
||||||
|
.lastActive = was ? was->lastActive : 0,
|
||||||
.source = uint32(data.vsource().v),
|
.source = uint32(data.vsource().v),
|
||||||
|
.speaking = !data.is_muted() && (was ? was->speaking : false),
|
||||||
.muted = data.is_muted(),
|
.muted = data.is_muted(),
|
||||||
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
|
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
|
||||||
};
|
};
|
||||||
|
@ -220,17 +225,11 @@ void GroupCall::applyParticipantsSlice(
|
||||||
*i = value;
|
*i = value;
|
||||||
}
|
}
|
||||||
_participantUpdates.fire({
|
_participantUpdates.fire({
|
||||||
.participant = value,
|
.was = was,
|
||||||
|
.now = value,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ranges::sort(_participants, std::greater<>(), [](const Participant &p) {
|
|
||||||
return p.lastSpoke
|
|
||||||
? p.lastSpoke
|
|
||||||
: p.lastActive
|
|
||||||
? p.lastActive
|
|
||||||
: p.date;
|
|
||||||
});
|
|
||||||
_fullCount = fullCount;
|
_fullCount = fullCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,15 +247,39 @@ void GroupCall::applyParticipantsMutes(
|
||||||
user,
|
user,
|
||||||
&Participant::user);
|
&Participant::user);
|
||||||
if (i != end(_participants)) {
|
if (i != end(_participants)) {
|
||||||
|
const auto was = *i;
|
||||||
i->muted = data.is_muted();
|
i->muted = data.is_muted();
|
||||||
i->canSelfUnmute = !i->muted || data.is_can_self_unmute();
|
i->canSelfUnmute = !i->muted || data.is_can_self_unmute();
|
||||||
|
if (i->muted) {
|
||||||
|
i->speaking = false;
|
||||||
|
}
|
||||||
_participantUpdates.fire({
|
_participantUpdates.fire({
|
||||||
.participant = *i,
|
.was = was,
|
||||||
|
.now = *i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCall::applyLastSpoke(uint32 source, crl::time when, crl::time now) {
|
||||||
|
const auto i = _userBySource.find(source);
|
||||||
|
if (i == end(_userBySource)) {
|
||||||
|
// #TODO calls load participant by source from server.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto j = ranges::find(_participants, i->second, &Participant::user);
|
||||||
|
Assert(j != end(_participants));
|
||||||
|
|
||||||
|
const auto speaking = (when + kSpeakStatusKeptFor >= now) && !j->muted;
|
||||||
|
if (j->speaking != speaking) {
|
||||||
|
const auto was = *j;
|
||||||
|
j->speaking = speaking;
|
||||||
|
_participantUpdates.fire({
|
||||||
|
.was = was,
|
||||||
|
.now = *j,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
|
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
|
||||||
|
|
|
@ -25,14 +25,14 @@ public:
|
||||||
not_null<UserData*> user;
|
not_null<UserData*> user;
|
||||||
TimeId date = 0;
|
TimeId date = 0;
|
||||||
TimeId lastActive = 0;
|
TimeId lastActive = 0;
|
||||||
TimeId lastSpoke = 0;
|
|
||||||
uint32 source = 0;
|
uint32 source = 0;
|
||||||
|
bool speaking = false;
|
||||||
bool muted = false;
|
bool muted = false;
|
||||||
bool canSelfUnmute = false;
|
bool canSelfUnmute = false;
|
||||||
};
|
};
|
||||||
struct ParticipantUpdate {
|
struct ParticipantUpdate {
|
||||||
Participant participant;
|
std::optional<Participant> was;
|
||||||
bool removed = false;
|
std::optional<Participant> now;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] auto participants() const
|
[[nodiscard]] auto participants() const
|
||||||
|
@ -48,6 +48,7 @@ public:
|
||||||
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
|
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
|
||||||
void applyUpdateChecked(
|
void applyUpdateChecked(
|
||||||
const MTPDupdateGroupCallParticipants &update);
|
const MTPDupdateGroupCallParticipants &update);
|
||||||
|
void applyLastSpoke(uint32 source, crl::time when, crl::time now);
|
||||||
|
|
||||||
[[nodiscard]] int fullCount() const;
|
[[nodiscard]] int fullCount() const;
|
||||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||||
|
|
Loading…
Reference in New Issue