Add blob animations to group call participants.

This commit is contained in:
John Preston 2020-12-01 12:06:06 +03:00
parent 80597e190a
commit 498d6226e3
6 changed files with 216 additions and 171 deletions

View File

@ -660,3 +660,6 @@ groupCallSettingsAttentionButton: SettingsButton(groupCallSettingsButton) {
groupCallBoxLabel: FlatLabel(boxLabel) {
textFg: groupCallMembersFg;
}
groupCallRowBlobMinRadius: 26px;
groupCallRowBlobMaxRadius: 30px;

View File

@ -152,7 +152,7 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
}) | rpl::start_with_next([=](const Update &update) {
Expects(update.was.has_value());
_instance->removeSsrcs({ update.was->source });
_instance->removeSsrcs({ update.was->ssrc });
}, _lifetime);
}
@ -190,7 +190,7 @@ void GroupCall::rejoin() {
root.insert("fingerprints", fingerprints);
root.insert("ssrc", double(payload.ssrc));
LOG(("Call Info: Join payload received, joining with source: %1."
LOG(("Call Info: Join payload received, joining with ssrc: %1."
).arg(ssrc));
const auto json = QJsonDocument(root).toJson(
@ -418,12 +418,12 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
}
if (data.is_left() && data.vsource().v == _mySsrc) {
// I was removed from the call, rejoin.
LOG(("Call Info: Rejoin after got 'left' with my source."));
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
setState(State::Joining);
rejoin();
} else if (!data.is_left() && data.vsource().v != _mySsrc) {
// I joined from another device, hangup.
LOG(("Call Info: Hangup after '!left' with source %1, my %2."
LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
_mySsrc = 0;
@ -495,10 +495,10 @@ void GroupCall::handleLevelsUpdated(
auto check = false;
auto checkNow = false;
const auto now = crl::now();
for (const auto &[source, level] : data) {
const auto self = (source == _mySsrc);
for (const auto &[ssrc, level] : data) {
const auto self = (ssrc == _mySsrc);
_levelUpdates.fire(LevelUpdate{
.source = source,
.ssrc = ssrc,
.value = level,
.self = self
});
@ -515,9 +515,9 @@ void GroupCall::handleLevelsUpdated(
}
check = true;
const auto i = _lastSpoke.find(source);
const auto i = _lastSpoke.find(ssrc);
if (i == _lastSpoke.end()) {
_lastSpoke.emplace(source, now);
_lastSpoke.emplace(ssrc, now);
checkNow = true;
} else {
if (i->second + kCheckLastSpokeInterval / 3 <= now) {
@ -553,14 +553,14 @@ void GroupCall::checkLastSpoke() {
const auto now = crl::now();
auto list = base::take(_lastSpoke);
for (auto i = list.begin(); i != list.end();) {
const auto [source, when] = *i;
const auto [ssrc, when] = *i;
if (when + kCheckLastSpokeInterval >= now) {
hasRecent = true;
++i;
} else {
i = list.erase(i);
}
real->applyLastSpoke(source, when, now);
real->applyLastSpoke(ssrc, when, now);
}
_lastSpoke = std::move(list);

View File

@ -28,7 +28,7 @@ enum class MuteState {
};
struct LevelUpdate {
uint32 source = 0;
uint32 ssrc = 0;
float value = 0.;
bool self = false;
};

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_group_call.h"
#include "data/data_peer_values.h" // Data::CanWriteValue.
#include "ui/paint/blobs.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h"
@ -32,12 +33,37 @@ namespace Calls {
namespace {
constexpr auto kLevelThreshold = 0.2;
constexpr auto kRowBlobRadiusFactor = (float)(50. / 57.);
constexpr auto kLevelDuration = 100. + 500. * 0.33;
constexpr auto kScaleSmall = 0.704 - 0.1;
constexpr auto kScaleSmallMin = 0.926;
constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
constexpr auto kMaxLevel = 1.;
struct UpdateLevelResult {
bool levelChanged = false;
bool stateChanged = false;
crl::time nextUpdateTime = 0;
};
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kScaleSmallMin / kScaleSmallMax,
.minRadius = st::groupCallRowBlobMinRadius
* kRowBlobRadiusFactor,
.maxRadius = st::groupCallRowBlobMaxRadius
* kRowBlobRadiusFactor,
.speedScale = 1.,
.alpha = (76. / 255.),
},
{
.segmentsCount = 8,
.minScale = kScaleSmallMin / kScaleSmallMax,
.minRadius = st::groupCallRowBlobMinRadius
* kRowBlobRadiusFactor,
.maxRadius = st::groupCallRowBlobMaxRadius
* kRowBlobRadiusFactor,
.speedScale = 1.,
.alpha = (76. / 255.),
},
} };
}
class Row final : public PeerListRow {
public:
@ -50,10 +76,14 @@ public:
};
void updateState(const Data::GroupCall::Participant *participant);
//UpdateLevelResult updateLevel(float level);
void updateLevel(float level);
void updateBlobAnimation(crl::time now);
[[nodiscard]] State state() const {
return _state;
}
[[nodiscard]] uint32 ssrc() const {
return _ssrc;
}
[[nodiscard]] bool speaking() const {
return _speaking;
}
@ -85,9 +115,12 @@ public:
bool selected,
bool actionSelected) override;
auto generatePaintUserpicCallback() -> PaintRoundImageCallback override;
private:
void refreshStatus() override;
void setSpeaking(bool speaking);
void setSsrc(uint32 ssrc);
[[nodiscard]] static State ComputeState(
not_null<ChannelData*> channel,
@ -98,10 +131,12 @@ private:
State _state = State::Inactive;
not_null<ChannelData*> _channel;
not_null<const style::IconButton*> _st;
bool _speaking = false;
//float _level = 0.;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
std::unique_ptr<Ui::Paint::Blobs> _blobs;
crl::time _blobsLastTime = 0;
uint32 _ssrc = 0;
float _level = 0.;
bool _speaking = false;
};
@ -132,9 +167,9 @@ public:
-> rpl::producer<not_null<UserData*>>;
private:
[[nodiscard]] std::unique_ptr<PeerListRow> createSelfRow() const;
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
const Data::GroupCall::Participant &participant) const;
[[nodiscard]] std::unique_ptr<Row> createSelfRow();
[[nodiscard]] std::unique_ptr<Row> createRow(
const Data::GroupCall::Participant &participant);
void prepareRows(not_null<Data::GroupCall*> real);
//void repaintByTimer();
@ -146,9 +181,10 @@ private:
const Data::GroupCall::Participant &now);
void updateRow(
not_null<Row*> row,
const Data::GroupCall::Participant *participant) const;
const Data::GroupCall::Participant *participant);
void removeRow(not_null<Row*> row);
void updateRowLevel(not_null<Row*> row, float level);
void checkSpeakingRowPosition(not_null<Row*> row);
//void updateRowLevel(not_null<UserData*> user, float level);
Row *findRow(not_null<UserData*> user) const;
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
@ -167,8 +203,8 @@ private:
not_null<QWidget*> _menuParent;
base::unique_qptr<Ui::PopupMenu> _menu;
//base::flat_map<not_null<UserData*>, crl::time> _repaintByTimer;
//base::Timer _repaintTimer;
base::flat_map<uint32, not_null<Row*>> _speakingRowBySsrc;
Ui::Animations::Basic _speakingAnimation;
rpl::lifetime _lifetime;
@ -183,6 +219,7 @@ Row::Row(not_null<ChannelData*> channel, not_null<UserData*> user)
}
void Row::updateState(const Data::GroupCall::Participant *participant) {
setSsrc(participant ? participant->ssrc : 0);
if (!participant) {
if (peer()->isSelf()) {
setCustomStatus(tr::lng_group_call_connecting(tr::now));
@ -193,7 +230,7 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
setSpeaking(false);
} else if (!participant->muted) {
_state = State::Active;
setSpeaking(participant->speaking);
setSpeaking(participant->speaking && participant->ssrc != 0);
} else if (participant->canSelfUnmute) {
_state = State::Inactive;
setSpeaking(false);
@ -209,42 +246,49 @@ void Row::setSpeaking(bool speaking) {
return;
}
_speaking = speaking;
if (!_speaking) {
_blobs = nullptr;
}
refreshStatus();
//if (!_speaking) {
// updateLevel(0.);
//}
}
//UpdateLevelResult Row::updateLevel(float level) {
// if (_level == level) {
// return UpdateLevelResult{ .nextUpdateTime = _markInactiveAt };
// }
// 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 = true,
// .nextUpdateTime = _markInactiveAt,
// };
// }
// refreshStatus(now);
// return UpdateLevelResult{
// .levelChanged = true,
// .stateChanged = true,
// .nextUpdateTime = _markInactiveAt,
// };
//}
void Row::setSsrc(uint32 ssrc) {
_ssrc = ssrc;
}
void Row::updateLevel(float level) {
Expects(_speaking);
if (!_blobs) {
_blobs = std::make_unique<Ui::Paint::Blobs>(
RowBlobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
_blobsLastTime = crl::now();
}
_blobs->setLevel(level + 0.5);
}
void Row::updateBlobAnimation(crl::time now) {
if (_blobs) {
_blobs->updateLevel(now - _blobsLastTime);
_blobsLastTime = now;
}
}
auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
auto userpic = ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (_blobs) {
const auto shift = QPointF(x + size / 2., y + size / 2.);
p.translate(shift);
_blobs->paint(p, st::groupCallLive1);
p.translate(-shift);
p.setOpacity(1.);
}
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
};
}
void Row::paintAction(
Painter &p,
@ -333,8 +377,15 @@ MembersController::MembersController(
: _call(call)
, _channel(call->channel())
, _menuParent(menuParent) {
//, _repaintTimer([=] { repaintByTimer(); }) {
setupListChangeViewers(call);
_speakingAnimation.init([=](crl::time now) {
for (const auto [ssrc, row] : _speakingRowBySsrc) {
row->updateBlobAnimation(now);
delegate()->peerListUpdateRow(row);
}
return true;
});
}
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
@ -362,21 +413,13 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
}
}, _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);
call->levelUpdates(
) | rpl::start_with_next([=](const LevelUpdate &update) {
const auto i = _speakingRowBySsrc.find(update.ssrc);
if (i != end(_speakingRowBySsrc)) {
updateRowLevel(i->second, update.value);
}
}, _lifetime);
}
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
@ -402,10 +445,9 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
if (!update.now) {
if (const auto row = findRow(user)) {
if (user->isSelf()) {
row->updateState(nullptr);
delegate()->peerListUpdateRow(row);
updateRow(row, nullptr);
} else {
delegate()->peerListRemoveRow(row);
removeRow(row);
delegate()->peerListRefreshRows();
}
}
@ -424,7 +466,7 @@ void MembersController::updateRow(
}
updateRow(row, &now);
} else if (auto row = createRow(now)) {
if (now.speaking) {
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
delegate()->peerListAppendRow(std::move(row));
@ -466,49 +508,49 @@ void MembersController::checkSpeakingRowPosition(not_null<Row*> row) {
void MembersController::updateRow(
not_null<Row*> row,
const Data::GroupCall::Participant *participant) const {
const Data::GroupCall::Participant *participant) {
const auto wasSpeaking = row->speaking();
const auto wasSsrc = row->ssrc();
row->updateState(participant);
const auto nowSpeaking = row->speaking();
const auto nowSsrc = row->ssrc();
const auto wasNoSpeaking = _speakingRowBySsrc.empty();
if (wasSsrc == nowSsrc) {
if (nowSpeaking != wasSpeaking) {
if (nowSpeaking) {
_speakingRowBySsrc.emplace(nowSsrc, row);
} else {
_speakingRowBySsrc.remove(nowSsrc);
}
}
} else {
_speakingRowBySsrc.remove(wasSsrc);
if (nowSpeaking) {
Assert(nowSsrc != 0);
_speakingRowBySsrc.emplace(nowSsrc, row);
}
}
const auto nowNoSpeaking = _speakingRowBySsrc.empty();
if (wasNoSpeaking && !nowNoSpeaking) {
_speakingAnimation.start();
} else if (nowNoSpeaking && !wasNoSpeaking) {
_speakingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
//void MembersController::updateRowLevel(
// not_null<UserData*> user,
// float level) {
// if (const auto row = findRow(user)) {
// const auto result = row->updateLevel(level);
// if (result.stateChanged) {
// delegate()->peerListUpdateRow(row);
// }
// if (result.nextUpdateTime) {
// _repaintByTimer[user] = result.nextUpdateTime;
// if (!_repaintTimer.isActive()) {
// _repaintTimer.callOnce(kLevelActiveTimeout);
// }
// } else if (_repaintByTimer.remove(user) && _repaintByTimer.empty()) {
// _repaintTimer.cancel();
// }
// }
//}
void MembersController::removeRow(not_null<Row*> row) {
_speakingRowBySsrc.remove(row->ssrc());
delegate()->peerListRemoveRow(row);
}
//void MembersController::repaintByTimer() {
// const auto now = crl::now();
// auto next = crl::time(0);
// for (auto i = begin(_repaintByTimer); i != end(_repaintByTimer);) {
// if (i->second > now) {
// if (!next || next > i->second) {
// next = i->second;
// }
// } else if (const auto row = findRow(i->first)) {
// delegate()->peerListUpdateRow(row);
// i = _repaintByTimer.erase(i);
// continue;
// }
// ++i;
// }
// if (next) {
// _repaintTimer.callOnce(next - now);
// }
//}
void MembersController::updateRowLevel(
not_null<Row*> row,
float level) {
row->updateLevel(level);
}
Row *MembersController::findRow(not_null<UserData*> user) const {
return static_cast<Row*>(delegate()->peerListFindRow(user->id));
@ -564,7 +606,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
++i;
} else {
changed = true;
delegate()->peerListRemoveRow(row);
removeRow(static_cast<Row*>(row.get()));
--count;
}
}
@ -708,15 +750,15 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
return result;
}
std::unique_ptr<PeerListRow> MembersController::createSelfRow() const {
std::unique_ptr<Row> MembersController::createSelfRow() {
const auto self = _channel->session().user();
auto result = std::make_unique<Row>(_channel, self);
updateRow(result.get(), nullptr);
return result;
}
std::unique_ptr<PeerListRow> MembersController::createRow(
const Data::GroupCall::Participant &participant) const {
std::unique_ptr<Row> MembersController::createRow(
const Data::GroupCall::Participant &participant) {
auto result = std::make_unique<Row>(_channel, participant.user);
updateRow(result.get(), &participant);
return result;

View File

@ -31,7 +31,7 @@ GroupCall::GroupCall(
}
GroupCall::~GroupCall() {
api().request(_unknownSourcesRequestId).cancel();
api().request(_unknownSsrcsRequestId).cancel();
api().request(_participantsRequestId).cancel();
api().request(_reloadRequestId).cancel();
}
@ -65,7 +65,7 @@ void GroupCall::requestParticipants() {
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
input(),
MTP_vector<MTPint>(), // ids
MTP_vector<MTPint>(), // sources
MTP_vector<MTPint>(), // ssrcs
MTP_string(_nextOffset),
MTP_int(kRequestPerPage)
)).done([=](const MTPphone_GroupParticipants &result) {
@ -105,9 +105,9 @@ bool GroupCall::participantsLoaded() const {
return _allReceived;
}
UserData *GroupCall::userBySource(uint32 source) const {
const auto i = _userBySource.find(source);
return (i != end(_userBySource)) ? i->second.get() : nullptr;
UserData *GroupCall::userBySsrc(uint32 ssrc) const {
const auto i = _userBySsrc.find(ssrc);
return (i != end(_userBySsrc)) ? i->second.get() : nullptr;
}
rpl::producer<> GroupCall::participantsSliceAdded() {
@ -163,7 +163,7 @@ void GroupCall::reload() {
result.match([&](const MTPDphone_groupCall &data) {
_channel->owner().processUsers(data.vusers());
_participants.clear();
_userBySource.clear();
_userBySsrc.clear();
applyParticipantsSlice(
data.vparticipants().v,
ApplySliceSource::SliceLoaded);
@ -194,7 +194,7 @@ void GroupCall::applyParticipantsSlice(
auto update = ParticipantUpdate{
.was = *i,
};
_userBySource.erase(i->source);
_userBySsrc.erase(i->ssrc);
_participants.erase(i);
if (sliceSource != ApplySliceSource::SliceLoaded) {
_participantUpdates.fire(std::move(update));
@ -212,20 +212,20 @@ void GroupCall::applyParticipantsSlice(
.user = user,
.date = data.vdate().v,
.lastActive = was ? was->lastActive : 0,
.source = uint32(data.vsource().v),
.ssrc = uint32(data.vsource().v),
.speaking = !data.is_muted() && (was ? was->speaking : false),
.muted = data.is_muted(),
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
};
if (i == end(_participants)) {
_userBySource.emplace(value.source, user);
_userBySsrc.emplace(value.ssrc, user);
_participants.push_back(value);
_channel->owner().unregisterInvitedToCallUser(_id, user);
++changedCount;
} else {
if (i->source != value.source) {
_userBySource.erase(i->source);
_userBySource.emplace(value.source, user);
if (i->ssrc != value.ssrc) {
_userBySsrc.erase(i->ssrc);
_userBySsrc.emplace(value.ssrc, user);
}
*i = value;
}
@ -271,11 +271,11 @@ void GroupCall::applyParticipantsMutes(
}
}
void GroupCall::applyLastSpoke(uint32 source, crl::time when, crl::time now) {
const auto i = _userBySource.find(source);
if (i == end(_userBySource)) {
_unknownSpokenSources.emplace(source, when);
requestUnknownSources();
void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
const auto i = _userBySsrc.find(ssrc);
if (i == end(_userBySsrc)) {
_unknownSpokenSsrcs.emplace(ssrc, when);
requestUnknownSsrcs();
return;
}
const auto j = ranges::find(_participants, i->second, &Participant::user);
@ -292,32 +292,32 @@ void GroupCall::applyLastSpoke(uint32 source, crl::time when, crl::time now) {
}
}
void GroupCall::requestUnknownSources() {
if (_unknownSourcesRequestId || _unknownSpokenSources.empty()) {
void GroupCall::requestUnknownSsrcs() {
if (_unknownSsrcsRequestId || _unknownSpokenSsrcs.empty()) {
return;
}
const auto sources = [&] {
if (_unknownSpokenSources.size() < kRequestPerPage) {
return base::take(_unknownSpokenSources);
const auto ssrcs = [&] {
if (_unknownSpokenSsrcs.size() < kRequestPerPage) {
return base::take(_unknownSpokenSsrcs);
}
auto result = base::flat_map<uint32, crl::time>();
result.reserve(kRequestPerPage);
while (result.size() < kRequestPerPage) {
const auto [source, when] = _unknownSpokenSources.back();
result.emplace(source, when);
_unknownSpokenSources.erase(_unknownSpokenSources.end() - 1);
const auto [ssrc, when] = _unknownSpokenSsrcs.back();
result.emplace(ssrc, when);
_unknownSpokenSsrcs.erase(_unknownSpokenSsrcs.end() - 1);
}
return result;
}();
auto sourceInputs = QVector<MTPint>();
sourceInputs.reserve(sources.size());
for (const auto [source, when] : sources) {
sourceInputs.push_back(MTP_int(source));
auto inputs = QVector<MTPint>();
inputs.reserve(ssrcs.size());
for (const auto [ssrc, when] : ssrcs) {
inputs.push_back(MTP_int(ssrc));
}
_unknownSourcesRequestId = api().request(MTPphone_GetGroupParticipants(
_unknownSsrcsRequestId = api().request(MTPphone_GetGroupParticipants(
input(),
MTP_vector<MTPint>(), // ids
MTP_vector<MTPint>(sourceInputs),
MTP_vector<MTPint>(inputs),
MTP_string(QString()),
MTP_int(kRequestPerPage)
)).done([=](const MTPphone_GroupParticipants &result) {
@ -327,19 +327,19 @@ void GroupCall::requestUnknownSources() {
data.vparticipants().v,
ApplySliceSource::UnknownLoaded);
});
_unknownSourcesRequestId = 0;
_unknownSsrcsRequestId = 0;
const auto now = crl::now();
for (const auto [source, when] : sources) {
applyLastSpoke(source, when, now);
_unknownSpokenSources.remove(source);
for (const auto [ssrc, when] : ssrcs) {
applyLastSpoke(ssrc, when, now);
_unknownSpokenSsrcs.remove(ssrc);
}
requestUnknownSources();
requestUnknownSsrcs();
}).fail([=](const RPCError &error) {
_unknownSourcesRequestId = 0;
for (const auto [source, when] : sources) {
_unknownSpokenSources.remove(source);
_unknownSsrcsRequestId = 0;
for (const auto [ssrc, when] : ssrcs) {
_unknownSpokenSsrcs.remove(ssrc);
}
requestUnknownSources();
requestUnknownSsrcs();
}).send();
}

View File

@ -27,7 +27,7 @@ public:
not_null<UserData*> user;
TimeId date = 0;
TimeId lastActive = 0;
uint32 source = 0;
uint32 ssrc = 0;
bool speaking = false;
bool muted = false;
bool canSelfUnmute = false;
@ -41,7 +41,7 @@ public:
-> const std::vector<Participant> &;
void requestParticipants();
[[nodiscard]] bool participantsLoaded() const;
[[nodiscard]] UserData *userBySource(uint32 source) const;
[[nodiscard]] UserData *userBySsrc(uint32 ssrc) const;
[[nodiscard]] rpl::producer<> participantsSliceAdded();
[[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const;
@ -50,7 +50,7 @@ public:
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
void applyUpdateChecked(
const MTPDupdateGroupCallParticipants &update);
void applyLastSpoke(uint32 source, crl::time when, crl::time now);
void applyLastSpoke(uint32 ssrc, crl::time when, crl::time now);
[[nodiscard]] int fullCount() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
@ -75,7 +75,7 @@ private:
ApplySliceSource sliceSource);
void applyParticipantsMutes(
const MTPDupdateGroupCallParticipants &update);
void requestUnknownSources();
void requestUnknownSsrcs();
const not_null<ChannelData*> _channel;
const uint64 _id = 0;
@ -86,12 +86,12 @@ private:
mtpRequestId _reloadRequestId = 0;
std::vector<Participant> _participants;
base::flat_map<uint32, not_null<UserData*>> _userBySource;
base::flat_map<uint32, not_null<UserData*>> _userBySsrc;
QString _nextOffset;
rpl::variable<int> _fullCount = 0;
base::flat_map<uint32, crl::time> _unknownSpokenSources;
mtpRequestId _unknownSourcesRequestId = 0;
base::flat_map<uint32, crl::time> _unknownSpokenSsrcs;
mtpRequestId _unknownSsrcsRequestId = 0;
rpl::event_stream<ParticipantUpdate> _participantUpdates;
rpl::event_stream<> _participantsSliceAdded;