mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-08 08:04:08 +02:00
Allow pinning video to top of members list.
This commit is contained in:
parent
eb8f709943
commit
b15623d435
6 changed files with 292 additions and 54 deletions
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue