Allow pinning video to top of members list.

This commit is contained in:
John Preston 2021-04-23 18:47:03 +04:00
parent eb8f709943
commit b15623d435
6 changed files with 292 additions and 54 deletions

View file

@ -1998,6 +1998,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_raised_hand_status" = "wants to speak"; "lng_group_call_raised_hand_status" = "wants to speak";
"lng_group_call_settings" = "Settings"; "lng_group_call_settings" = "Settings";
"lng_group_call_share_button" = "Share"; "lng_group_call_share_button" = "Share";
"lng_group_call_screen_share" = "Share";
"lng_group_call_unmute_small" = "Unmute";
"lng_group_call_you_are_live_small" = "Mute";
"lng_group_call_force_muted_small" = "Muted";
"lng_group_call_more" = "More";
"lng_group_call_unmute" = "Unmute"; "lng_group_call_unmute" = "Unmute";
"lng_group_call_unmute_sub" = "or hold spacebar to talk"; "lng_group_call_unmute_sub" = "or hold spacebar to talk";
"lng_group_call_you_are_live" = "You are Live"; "lng_group_call_you_are_live" = "You are Live";
@ -2059,6 +2064,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_context_remove_hand" = "Cancel request to speak"; "lng_group_call_context_remove_hand" = "Cancel request to speak";
"lng_group_call_context_mute_for_me" = "Mute for me"; "lng_group_call_context_mute_for_me" = "Mute for me";
"lng_group_call_context_unmute_for_me" = "Unmute for me"; "lng_group_call_context_unmute_for_me" = "Unmute for me";
"lng_group_call_context_pin_video" = "Pin video";
"lng_group_call_context_unpin_video" = "Unpin video";
"lng_group_call_context_remove" = "Remove"; "lng_group_call_context_remove" = "Remove";
"lng_group_call_remove_channel" = "Remove {channel} from the voice chat?"; "lng_group_call_remove_channel" = "Remove {channel} from the voice chat?";
"lng_group_call_duration_days#one" = "{count} day"; "lng_group_call_duration_days#one" = "{count} day";

View file

@ -111,6 +111,14 @@ private:
}; };
struct GroupCall::LargeTrack {
LargeTrack() : track(Webrtc::VideoState::Active) {
}
Webrtc::VideoTrack track;
std::shared_ptr<Webrtc::SinkInterface> sink;
};
[[nodiscard]] bool IsGroupCallAdmin( [[nodiscard]] bool IsGroupCallAdmin(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<PeerData*> participantPeer) { not_null<PeerData*> participantPeer) {
@ -427,6 +435,53 @@ void GroupCall::subscribeToReal(not_null<Data::GroupCall*> real) {
) | rpl::start_with_next([=](TimeId date) { ) | rpl::start_with_next([=](TimeId date) {
setScheduledDate(date); setScheduledDate(date);
}, _lifetime); }, _lifetime);
using Update = Data::GroupCall::ParticipantUpdate;
real->participantUpdated(
) | rpl::start_with_next([=](const Update &data) {
const auto nowSpeaking = data.now && data.now->speaking;
const auto nowSounding = data.now && data.now->sounding;
const auto wasSpeaking = data.was && data.was->speaking;
const auto wasSounding = data.was && data.was->sounding;
if (nowSpeaking == wasSpeaking && nowSounding == wasSounding) {
return;
} else if (_videoStreamPinned) {
return;
}
const auto videoLargeSsrc = _videoStreamLarge.current();
const auto &participants = real->participants();
if ((wasSpeaking || wasSounding)
&& (data.was->ssrc == videoLargeSsrc)) {
auto bestWithVideoSsrc = uint32(0);
for (const auto &participant : participants) {
if (!participant.sounding
|| !participant.ssrc
|| !_videoStreamSsrcs.contains(participant.ssrc)) {
continue;
}
if (participant.speaking) {
bestWithVideoSsrc = participant.ssrc;
break;
} else if (!bestWithVideoSsrc) {
bestWithVideoSsrc = participant.ssrc;
}
}
if (bestWithVideoSsrc) {
_videoStreamLarge = bestWithVideoSsrc;
}
} else if ((nowSpeaking || nowSounding)
&& (data.now->ssrc != videoLargeSsrc)) {
const auto i = ranges::find(
participants,
videoLargeSsrc,
&Data::GroupCallParticipant::ssrc);
const auto speaking = (i != end(participants)) && i->speaking;
const auto sounding = (i != end(participants)) && i->sounding;
if ((nowSpeaking && !speaking) || (nowSounding && !sounding)) {
_videoStreamLarge = data.now->ssrc;
}
}
}, _lifetime);
} }
void GroupCall::checkGlobalShortcutAvailability() { void GroupCall::checkGlobalShortcutAvailability() {
@ -1531,6 +1586,22 @@ void GroupCall::ensureControllerCreated() {
LOG(("Call Info: Creating group instance")); LOG(("Call Info: Creating group instance"));
_instance = std::make_unique<tgcalls::GroupInstanceCustomImpl>( _instance = std::make_unique<tgcalls::GroupInstanceCustomImpl>(
std::move(descriptor)); std::move(descriptor));
_videoStreamLarge.changes(
) | rpl::start_with_next([=](uint32 ssrc) {
_instance->setFullSizeVideoSsrc(ssrc);
if (!ssrc) {
_videoLargeTrack = nullptr;
_videoLargeTrackWrap = nullptr;
return;
}
if (!_videoLargeTrackWrap) {
_videoLargeTrackWrap = std::make_unique<LargeTrack>();
_videoLargeTrack = &_videoLargeTrackWrap->track;
}
_videoLargeTrackWrap->sink = Webrtc::CreateProxySink(
_videoLargeTrackWrap->track.sink());
_instance->addIncomingVideoOutput(ssrc, _videoLargeTrackWrap->sink);
}, _lifetime);
updateInstanceMuteState(); updateInstanceMuteState();
updateInstanceVolumes(); updateInstanceVolumes();
@ -1641,15 +1712,14 @@ void GroupCall::requestParticipantsInformation(
} }
void GroupCall::setVideoStreams(const std::vector<std::uint32_t> &ssrcs) { void GroupCall::setVideoStreams(const std::vector<std::uint32_t> &ssrcs) {
const auto real = lookupReal();
const auto large = _videoStreamLarge.current(); const auto large = _videoStreamLarge.current();
auto newLarge = large; auto newLarge = large;
if (large && !ranges::contains(ssrcs, large)) { if (large && !ranges::contains(ssrcs, large)) {
newLarge = 0; newLarge = 0;
_videoStreamPinned = 0; _videoStreamPinned = 0;
} }
auto lastSpokeVoice = crl::time(0);
auto lastSpokeVoiceSsrc = uint32(0); auto lastSpokeVoiceSsrc = uint32(0);
auto lastSpokeAnything = crl::time(0);
auto lastSpokeAnythingSsrc = uint32(0); auto lastSpokeAnythingSsrc = uint32(0);
auto removed = _videoStreamSsrcs; auto removed = _videoStreamSsrcs;
for (const auto ssrc : ssrcs) { for (const auto ssrc : ssrcs) {
@ -1660,30 +1730,41 @@ void GroupCall::setVideoStreams(const std::vector<std::uint32_t> &ssrcs) {
_videoStreamSsrcs.emplace(ssrc); _videoStreamSsrcs.emplace(ssrc);
_streamsVideoUpdated.fire({ ssrc, true }); _streamsVideoUpdated.fire({ ssrc, true });
} }
if (!newLarge) { if (!newLarge && real) {
const auto j = _lastSpoke.find(ssrc); const auto &participants = real->participants();
if (j != end(_lastSpoke)) { const auto i = ranges::find(
if (!lastSpokeVoiceSsrc participants,
|| lastSpokeVoice < j->second.voice) { ssrc,
&Data::GroupCallParticipant::ssrc);
if (i != end(participants)) {
if (!lastSpokeVoiceSsrc && i->speaking) {
lastSpokeVoiceSsrc = ssrc; lastSpokeVoiceSsrc = ssrc;
lastSpokeVoice = j->second.voice;
} }
if (!lastSpokeAnythingSsrc if (!lastSpokeAnythingSsrc && i->sounding) {
|| lastSpokeAnything < j->second.anything) {
lastSpokeAnythingSsrc = ssrc; lastSpokeAnythingSsrc = ssrc;
lastSpokeAnything = j->second.anything;
} }
} }
} }
} }
if (!newLarge) { if (!newLarge && real) {
const auto find = [&] {
const auto &participants = real->participants();
for (const auto ssrc : ssrcs) {
const auto i = ranges::find(
participants,
ssrc,
&Data::GroupCallParticipant::ssrc);
if (i != end(participants)) {
return ssrc;
}
}
return std::uint32_t(0);
};
_videoStreamLarge = lastSpokeVoiceSsrc _videoStreamLarge = lastSpokeVoiceSsrc
? lastSpokeVoiceSsrc ? lastSpokeVoiceSsrc
: lastSpokeAnythingSsrc : lastSpokeAnythingSsrc
? lastSpokeAnythingSsrc ? lastSpokeAnythingSsrc
: ssrcs.empty() : find();
? 0
: ssrcs.front();
} }
for (const auto ssrc : removed) { for (const auto ssrc : removed) {
_streamsVideoUpdated.fire({ ssrc, false }); _streamsVideoUpdated.fire({ ssrc, false });
@ -1932,6 +2013,15 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) {
}).send(); }).send();
} }
void GroupCall::pinVideoStream(uint32 ssrc) {
if (!ssrc || _videoStreamSsrcs.contains(ssrc)) {
_videoStreamPinned = ssrc;
if (ssrc) {
_videoStreamLarge = ssrc;
}
}
}
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
if (input) { if (input) {
_mediaDevices->switchToAudioInput(deviceId); _mediaDevices->switchToAudioInput(deviceId);

View file

@ -210,12 +210,26 @@ public:
-> rpl::producer<StreamsVideoUpdate> { -> rpl::producer<StreamsVideoUpdate> {
return _streamsVideoUpdated.events(); return _streamsVideoUpdated.events();
} }
[[nodiscard]] bool streamsVideo(uint32 ssrc) const {
return _videoStreamSsrcs.contains(ssrc);
}
[[nodiscard]] uint32 videoStreamPinned() const {
return _videoStreamPinned;
}
void pinVideoStream(uint32 ssrc);
[[nodiscard]] uint32 videoStreamLarge() const { [[nodiscard]] uint32 videoStreamLarge() const {
return _videoStreamLarge.current(); return _videoStreamLarge.current();
} }
[[nodiscard]] rpl::producer<uint32> videoStreamLargeValue() const { [[nodiscard]] rpl::producer<uint32> videoStreamLargeValue() const {
return _videoStreamLarge.value(); return _videoStreamLarge.value();
} }
[[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const {
return _videoLargeTrack.current();
}
[[nodiscard]] auto videoLargeTrackValue() const
-> rpl::producer<Webrtc::VideoTrack*> {
return _videoLargeTrack.value();
}
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const { [[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
return _rejoinEvents.events(); return _rejoinEvents.events();
} }
@ -256,6 +270,7 @@ public:
private: private:
using GlobalShortcutValue = base::GlobalShortcutValue; using GlobalShortcutValue = base::GlobalShortcutValue;
struct LargeTrack;
struct LoadingPart { struct LoadingPart {
std::shared_ptr<LoadPartTask> task; std::shared_ptr<LoadPartTask> task;
@ -385,6 +400,8 @@ private:
base::flat_set<uint32> _videoStreamSsrcs; base::flat_set<uint32> _videoStreamSsrcs;
rpl::variable<uint32> _videoStreamLarge = 0; rpl::variable<uint32> _videoStreamLarge = 0;
uint32 _videoStreamPinned = 0; uint32 _videoStreamPinned = 0;
std::unique_ptr<LargeTrack> _videoLargeTrackWrap;
rpl::variable<Webrtc::VideoTrack*> _videoLargeTrack;
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke; base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
rpl::event_stream<Group::RejoinEvent> _rejoinEvents; rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
rpl::event_stream<> _allowedToSpeakNotifications; rpl::event_stream<> _allowedToSpeakNotifications;

View file

@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "window/window_controller.h" // Controller::sessionController. #include "window/window_controller.h" // Controller::sessionController.
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h" #include "styles/style_calls.h"
@ -1147,7 +1148,6 @@ void MembersController::setupListChangeViewers() {
} }
}, _lifetime); }, _lifetime);
_call->videoStreamLargeValue( _call->videoStreamLargeValue(
) | rpl::filter([=](uint32 largeSsrc) { ) | rpl::filter([=](uint32 largeSsrc) {
return (_largeSsrc != largeSsrc); return (_largeSsrc != largeSsrc);
@ -1844,6 +1844,17 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
_kickParticipantRequests.fire_copy(participantPeer); _kickParticipantRequests.fire_copy(participantPeer);
}); });
const auto ssrc = real->ssrc();
if (ssrc != 0 && _call->streamsVideo(ssrc)) {
const auto pinned = (_call->videoStreamPinned() == ssrc);
const auto phrase = pinned
? tr::lng_group_call_context_unpin_video(tr::now)
: tr::lng_group_call_context_pin_video(tr::now);
result->addAction(phrase, [=] {
_call->pinVideoStream(pinned ? 0 : ssrc);
});
}
if (real->ssrc() != 0 if (real->ssrc() != 0
&& (!isMe(participantPeer) || _peer->canManageGroupCall())) { && (!isMe(participantPeer) || _peer->canManageGroupCall())) {
addMuteActionsToContextMenu(result, participantPeer, admin, real); addMuteActionsToContextMenu(result, participantPeer, admin, real);
@ -2056,9 +2067,13 @@ Members::Members(
: RpWidget(parent) : RpWidget(parent)
, _call(call) , _call(call)
, _scroll(this) , _scroll(this)
, _listController(std::make_unique<MembersController>(call, parent)) { , _listController(std::make_unique<MembersController>(call, parent))
, _layout(_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
, _pinnedVideo(_layout->add(object_ptr<Ui::RpWidget>(_layout.get()))) {
setupAddMember(call); setupAddMember(call);
setupList(); setupList();
setupPinnedVideo();
setContent(_list); setContent(_list);
setupFakeRoundCorners(); setupFakeRoundCorners();
_listController->setDelegate(static_cast<PeerListDelegate*>(this)); _listController->setDelegate(static_cast<PeerListDelegate*>(this));
@ -2083,15 +2098,17 @@ auto Members::kickParticipantRequests() const
} }
int Members::desiredHeight() const { int Members::desiredHeight() const {
const auto top = _addMember ? _addMember->height() : 0; const auto addMember = _addMemberButton.current();
auto count = [&] { const auto top = _pinnedVideo->height()
+ (addMember ? addMember->height() : 0);
const auto count = [&] {
if (const auto real = _call->lookupReal()) { if (const auto real = _call->lookupReal()) {
return real->fullCount(); return real->fullCount();
} }
return 0; return 0;
}(); }();
const auto use = std::max(count, _list->fullRowsCount()); const auto use = std::max(count, _list->fullRowsCount());
const auto single = (_mode == PanelMode::Wide) const auto single = (_mode.current() == PanelMode::Wide)
? (st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip) ? (st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip)
: st::groupCallMembersList.item.height; : st::groupCallMembersList.item.height;
return top return top
@ -2137,34 +2154,33 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
_canAddMembers.value( _canAddMembers.value(
) | rpl::start_with_next([=](bool can) { ) | rpl::start_with_next([=](bool can) {
if (!can) { if (!can) {
delete _addMemberButton.current();
_addMemberButton = nullptr; _addMemberButton = nullptr;
_addMember.destroy();
updateControlsGeometry(); updateControlsGeometry();
return; return;
} else if (_addMemberButton.current()) {
return;
} }
_addMember = Settings::CreateButton( auto addMember = Settings::CreateButton(
this, this,
tr::lng_group_call_invite(), tr::lng_group_call_invite(),
st::groupCallAddMember, st::groupCallAddMember,
&st::groupCallAddMemberIcon, &st::groupCallAddMemberIcon,
st::groupCallAddMemberIconLeft); st::groupCallAddMemberIconLeft);
_addMember->show(); addMember->show();
addMember->addClickHandler([=] { // TODO throttle(ripple duration)
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
_addMemberRequests.fire({}); _addMemberRequests.fire({});
}); });
_addMemberButton = _addMember.data(); _addMemberButton = _layout->insert(1, std::move(addMember));
resizeToList();
}, lifetime()); }, lifetime());
} }
void Members::setMode(PanelMode mode) { void Members::setMode(PanelMode mode) {
if (_mode == mode) { if (_mode.current() == mode) {
return; return;
} }
_mode = mode; _mode = mode;
_list->setMode((_mode == PanelMode::Wide) _list->setMode((mode == PanelMode::Wide)
? PeerListContent::Mode::Custom ? PeerListContent::Mode::Custom
: PeerListContent::Mode::Default); : PeerListContent::Mode::Default);
} }
@ -2176,25 +2192,139 @@ rpl::producer<int> Members::fullCountValue() const {
void Members::setupList() { void Members::setupList() {
_listController->setStyleOverrides(&st::groupCallMembersList); _listController->setStyleOverrides(&st::groupCallMembersList);
_list = _scroll->setOwnedWidget(object_ptr<ListWidget>( _list = _layout->add(object_ptr<ListWidget>(
this, this,
_listController.get())); _listController.get()));
_list->heightValue( _layout->heightValue(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
resizeToList(); resizeToList();
}, _list->lifetime()); }, _layout->lifetime());
rpl::combine( rpl::combine(
_scroll->scrollTopValue(), _scroll->scrollTopValue(),
_scroll->heightValue() _scroll->heightValue()
) | rpl::start_with_next([=](int scrollTop, int scrollHeight) { ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) {
_list->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight); _layout->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight);
}, _scroll->lifetime()); }, _scroll->lifetime());
updateControlsGeometry(); updateControlsGeometry();
} }
void Members::setupPinnedVideo() {
using namespace rpl::mappers;
// New video was pinned or mode changed.
rpl::merge(
_mode.changes() | rpl::filter(
_1 == PanelMode::Default
) | rpl::to_empty,
_call->videoStreamLargeValue() | rpl::filter([=](uint32 ssrc) {
return ssrc == _call->videoStreamPinned();
}) | rpl::to_empty
) | rpl::start_with_next([=] {
_scroll->scrollToY(0);
}, _scroll->lifetime());
rpl::combine(
_mode.value(),
_call->videoLargeTrackValue()
) | rpl::map([](PanelMode mode, Webrtc::VideoTrack *track) {
return (mode == PanelMode::Default) ? track : nullptr;
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Webrtc::VideoTrack *track) {
_pinnedTrackLifetime.destroy();
if (!track) {
_pinnedVideo->resize(_pinnedVideo->width(), 0);
return;
}
const auto frameSize = _pinnedTrackLifetime.make_state<QSize>();
const auto applyFrameSize = [=](QSize size) {
const auto width = _pinnedVideo->width();
if (size.isEmpty() || !width) {
return;
}
const auto heightMin = (width * 9) / 16;
const auto heightMax = (width * 3) / 4;
const auto scaled = size.scaled(
QSize(width, heightMax),
Qt::KeepAspectRatio);
_pinnedVideo->resize(
width,
std::max(scaled.height(), heightMin));
};
track->renderNextFrame(
) | rpl::start_with_next([=] {
const auto size = track->frameSize();
if (size.isEmpty()) {
track->markFrameShown();
} else {
if (*frameSize != size) {
*frameSize = size;
applyFrameSize(size);
}
_pinnedVideo->update();
}
}, _pinnedTrackLifetime);
_layout->widthValue(
) | rpl::start_with_next([=] {
applyFrameSize(track->frameSize());
}, _pinnedTrackLifetime);
_pinnedVideo->paintRequest(
) | rpl::start_with_next([=] {
const auto [image, rotation]
= track->frameOriginalWithRotation();
if (image.isNull()) {
return;
}
auto p = QPainter(_pinnedVideo);
auto hq = PainterHighQualityEnabler(p);
using namespace Media::View;
const auto size = _pinnedVideo->size();
const auto scaled = FlipSizeByRotation(
image.size(),
rotation
).scaled(size, Qt::KeepAspectRatio);
const auto left = (size.width() - scaled.width()) / 2;
const auto top = (size.height() - scaled.height()) / 2;
const auto target = QRect(QPoint(left, top), scaled);
if (UsePainterRotation(rotation)) {
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(RotatedRect(target, rotation), image);
if (rotation) {
p.restore();
}
} else if (rotation) {
p.drawImage(target, RotateFrameImage(image, rotation));
} else {
p.drawImage(target, image);
}
if (left > 0) {
p.fillRect(0, 0, left, size.height(), Qt::black);
}
if (const auto right = left + scaled.width()
; right < size.width()) {
const auto fill = size.width() - right;
p.fillRect(right, 0, fill, size.height(), Qt::black);
}
if (top > 0) {
p.fillRect(0, 0, size.width(), top, Qt::black);
}
if (const auto bottom = top + scaled.height()
; bottom < size.height()) {
const auto fill = size.height() - bottom;
p.fillRect(0, bottom, size.width(), fill, Qt::black);
}
track->markFrameShown();
}, _pinnedTrackLifetime);
}, lifetime());
}
void Members::resizeEvent(QResizeEvent *e) { void Members::resizeEvent(QResizeEvent *e) {
updateControlsGeometry(); updateControlsGeometry();
} }
@ -2203,11 +2333,8 @@ void Members::resizeToList() {
if (!_list) { if (!_list) {
return; return;
} }
const auto listHeight = _list->height(); const auto newHeight = (_list->height() > 0)
const auto newHeight = (listHeight > 0) ? (_layout->height() + st::lineWidth)
? ((_addMember ? _addMember->height() : 0)
+ listHeight
+ st::lineWidth)
: 0; : 0;
if (height() == newHeight) { if (height() == newHeight) {
updateControlsGeometry(); updateControlsGeometry();
@ -2217,17 +2344,8 @@ void Members::resizeToList() {
} }
void Members::updateControlsGeometry() { void Members::updateControlsGeometry() {
if (!_list) { _scroll->setGeometry(rect());
return; _layout->resizeToWidth(width());
}
auto topSkip = 0;
if (_addMember) {
_addMember->resizeToWidth(width());
_addMember->move(0, 0);
topSkip = _addMember->height();
}
_scroll->setGeometry(0, topSkip, width(), height() - topSkip);
_list->resizeToWidth(width());
} }
void Members::setupFakeRoundCorners() { void Members::setupFakeRoundCorners() {

View file

@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
namespace Ui { namespace Ui {
class RpWidget;
class ScrollArea; class ScrollArea;
class VerticalLayout;
class SettingsButton; class SettingsButton;
} // namespace Ui } // namespace Ui
@ -74,21 +76,25 @@ private:
void setupAddMember(not_null<GroupCall*> call); void setupAddMember(not_null<GroupCall*> call);
void resizeToList(); void resizeToList();
void setupList(); void setupList();
void setupPinnedVideo();
void setupFakeRoundCorners(); void setupFakeRoundCorners();
void updateControlsGeometry(); void updateControlsGeometry();
const not_null<GroupCall*> _call; const not_null<GroupCall*> _call;
PanelMode _mode = PanelMode(); rpl::variable<PanelMode> _mode = PanelMode();
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<PeerListController> _listController; std::unique_ptr<PeerListController> _listController;
object_ptr<Ui::SettingsButton> _addMember = { nullptr }; not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::RpWidget*> _pinnedVideo;
rpl::variable<Ui::SettingsButton*> _addMemberButton = nullptr; rpl::variable<Ui::SettingsButton*> _addMemberButton = nullptr;
ListWidget *_list = { nullptr }; ListWidget *_list = nullptr;
rpl::event_stream<> _addMemberRequests; rpl::event_stream<> _addMemberRequests;
rpl::variable<bool> _canAddMembers; rpl::variable<bool> _canAddMembers;
rpl::lifetime _pinnedTrackLifetime;
}; };
} // namespace Calls } // namespace Calls

@ -1 +1 @@
Subproject commit 5270a1dbbdbee643e187e175f798595b4bc49996 Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28