Files
tdesktop/Telegram/SourceFiles/media/view/media_view_video_stream.cpp
2025-12-10 21:28:33 +03:00

380 lines
10 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/view/media_view_video_stream.h"
#include "data/data_message_reactions.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_members.h"
#include "calls/group/calls_group_messages.h"
#include "calls/group/calls_group_messages_ui.h"
#include "calls/group/calls_group_viewport.h"
#include "calls/calls_instance.h"
#include "chat_helpers/compose/compose_show.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "payments/ui/payments_reaction_box.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/painter.h"
#include "styles/style_calls.h"
#include "styles/style_media_view.h"
namespace Media::View {
class VideoStream::Delegate final : public Calls::GroupCall::Delegate {
public:
explicit Delegate(Fn<void()> close);
void groupCallFinished(not_null<Calls::GroupCall*> call) override;
void groupCallFailed(not_null<Calls::GroupCall*> call) override;
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override;
void groupCallPlaySound(GroupCallSound sound) override;
auto groupCallGetVideoCapture(const QString &deviceId)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
FnMut<void()> groupCallAddAsyncWaiter() override;
private:
Fn<void()> _close;
};
class VideoStream::Loading final {
public:
Loading(not_null<QWidget*> parent, not_null<VideoStream*> stream);
void lower();
void setGeometry(int x, int y, int width, int height);
[[nodiscard]] rpl::lifetime &lifetime();
private:
void setup(not_null<QWidget*> parent);
const not_null<VideoStream*> _stream;
std::unique_ptr<Ui::RpWidget> _bg;
std::unique_ptr<Ui::PathShiftGradient> _gradient;
};
auto TopVideoStreamDonors(not_null<Calls::GroupCall*> call)
-> rpl::producer<std::vector<Data::MessageReactionsTopPaid>> {
const auto messages = call->messages();
return rpl::single(rpl::empty) | rpl::then(
messages->starsValueChanges() | rpl::to_empty
) | rpl::map([=] {
const auto &list = messages->starsTop().topDonors;
auto still = Ui::MaxTopPaidDonorsShown();
auto result = std::vector<Data::MessageReactionsTopPaid>();
result.reserve(list.size());
for (const auto &item : list) {
result.push_back({
.peer = item.peer,
.count = uint32(item.stars),
.my = item.my ? 1U : 0U,
});
if (!item.my && !--still) {
break;
}
}
return result;
});
}
auto TopDonorPlaces(not_null<Calls::GroupCall*> call)
-> rpl::producer<std::vector<not_null<PeerData*>>> {
return TopVideoStreamDonors(
call
) | rpl::map([=](const std::vector<Data::MessageReactionsTopPaid> &lst) {
auto result = std::vector<not_null<PeerData*>>();
auto left = Ui::MaxTopPaidDonorsShown();
result.reserve(lst.size());
for (const auto &donor : lst) {
result.push_back(donor.peer);
if (!--left) {
break;
}
}
return result;
});
}
VideoStream::Delegate::Delegate(Fn<void()> close)
: _close(std::move(close)) {
}
void VideoStream::Delegate::groupCallFinished(
not_null<Calls::GroupCall*> call) {
crl::on_main(call, _close);
}
void VideoStream::Delegate::groupCallFailed(not_null<Calls::GroupCall*> call) {
crl::on_main(call, _close);
}
void VideoStream::Delegate::groupCallRequestPermissionsOrFail(
Fn<void()> onSuccess) {
}
void VideoStream::Delegate::groupCallPlaySound(GroupCallSound sound) {
}
auto VideoStream::Delegate::groupCallGetVideoCapture(const QString &deviceId)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
return nullptr;
}
FnMut<void()> VideoStream::Delegate::groupCallAddAsyncWaiter() {
return [] {};
}
VideoStream::Loading::Loading(
not_null<QWidget*> parent,
not_null<VideoStream*> stream)
: _stream(stream) {
setup(parent);
}
void VideoStream::Loading::lower() {
_bg->lower();
}
void VideoStream::Loading::setGeometry(int x, int y, int width, int height) {
_bg->setGeometry(x, y, width, height);
}
rpl::lifetime &VideoStream::Loading::lifetime() {
return _bg->lifetime();
}
void VideoStream::Loading::setup(not_null<QWidget*> parent) {
_bg = std::make_unique<Ui::RpWidget>(parent);
_gradient = std::make_unique<Ui::PathShiftGradient>(
st::storiesComposeBg,
st::storiesComposeBgRipple,
[=] { _bg->update(); });
_bg->show();
_bg->paintRequest() | rpl::on_next([=] {
auto p = QPainter(_bg.get());
auto hq = PainterHighQualityEnabler(p);
_gradient->startFrame(0, _bg->width(), _bg->width() / 3);
_gradient->paint([&](const Ui::PathShiftGradient::Background &bg) {
const auto stroke = style::ConvertScaleExact(2);
if (const auto color = std::get_if<style::color>(&bg)) {
auto pen = (*color)->p;
pen.setWidthF(stroke);
p.setPen(pen);
p.setBrush(*color);
} else {
const auto gradient = v::get<QLinearGradient*>(bg);
auto copy = *gradient;
copy.setStops({
{ 0., st::storiesComposeBg->c },
{ 0.5, st::white->c },
{ 1., st::storiesComposeBg->c },
});
auto pen = QPen(QBrush(copy), stroke);
p.setPen(pen);
p.setBrush(*gradient);
}
const auto half = stroke / 2.;
const auto remove = QMarginsF(half, half, half, half);
const auto rect = QRectF(_bg->rect()).marginsRemoved(remove);
const auto radius = st::storiesRadius - half;
p.drawRoundedRect(rect, radius, radius);
return true;
});
}, _bg->lifetime());
}
VideoStream::VideoStream(
not_null<QWidget*> parent,
not_null<Ui::RpWidgetWrap*> borrowedRp,
bool borrowedOpenGL,
Ui::GL::Backend backend,
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Data::GroupCall> call,
QString callLinkSlug,
MsgId callJoinMessageId)
: _show(std::move(show))
, _delegate(std::make_unique<Delegate>([=] { _closeRequests.fire({}); }))
, _loading(std::make_unique<Loading>(parent, this))
, _call(std::make_unique<Calls::GroupCall>(
_delegate.get(),
Calls::StartConferenceInfo{
.show = _show,
.call = std::move(call),
.linkSlug = callLinkSlug,
.joinMessageId = callJoinMessageId,
}))
, _members(
std::make_unique<Calls::Group::Members>(
parent,
_call.get(),
Calls::Group::PanelMode::VideoStream,
backend))
, _viewport(
std::make_unique<Calls::Group::Viewport>(
parent,
Calls::Group::PanelMode::VideoStream,
backend,
borrowedRp,
borrowedOpenGL))
, _messages(
std::make_unique<Calls::Group::MessagesUi>(
parent,
_show,
Calls::Group::MessagesMode::VideoStream,
_call->messages()->listValue(),
TopDonorPlaces(_call.get()),
_call->messages()->idUpdates(),
_call->canManageValue(),
_commentsShown.value())) {
Core::App().calls().registerVideoStream(_call.get());
setupMembers();
setupVideo();
setupMessages();
}
VideoStream::~VideoStream() {
}
not_null<Calls::GroupCall*> VideoStream::call() const {
return _call.get();
}
rpl::producer<> VideoStream::closeRequests() const {
return _closeRequests.events();
}
void VideoStream::updateGeometry(int x, int y, int width, int height) {
const auto skip = st::groupCallMessageSkip;
_viewport->setGeometry(false, { x, y, width, height });
_messages->move(x + skip, y + height, width - 2 * skip, height / 2);
if (_loading) {
_loading->setGeometry(x, y, width, height);
}
}
void VideoStream::toggleCommentsOn(rpl::producer<bool> shown) {
_commentsShown = std::move(shown);
}
void VideoStream::ensureBorrowedRenderer(QOpenGLFunctions &f) {
_viewport->ensureBorrowedRenderer(f);
}
void VideoStream::borrowedPaint(QOpenGLFunctions &f) {
_viewport->borrowedPaint(f);
}
void VideoStream::ensureBorrowedRenderer() {
_viewport->ensureBorrowedRenderer();
}
void VideoStream::borrowedPaint(Painter &p, const QRegion &clip) {
_viewport->borrowedPaint(p, clip);
}
rpl::lifetime &VideoStream::lifetime() {
return _lifetime;
}
void VideoStream::setupMembers() {
}
void VideoStream::setupVideo() {
const auto setupTile = [=](
const Calls::VideoEndpoint &endpoint,
const std::unique_ptr<Calls::GroupCall::VideoTrack> &track) {
using namespace rpl::mappers;
const auto row = endpoint.rtmp()
? _members->rtmpFakeRow(Calls::GroupCall::TrackPeer(track)).get()
: _members->lookupRow(Calls::GroupCall::TrackPeer(track));
Assert(row != nullptr);
auto pinned = rpl::combine(
_call->videoEndpointLargeValue(),
_call->videoEndpointPinnedValue()
) | rpl::map(_1 == endpoint && _2);
const auto self = (endpoint.peer == _call->joinAs());
_viewport->add(
endpoint,
Calls::Group::VideoTileTrack{
Calls::GroupCall::TrackPointer(track),
row,
},
Calls::GroupCall::TrackSizeValue(track),
std::move(pinned),
self);
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
setupTile(endpoint, track);
}
_call->videoStreamActiveUpdates(
) | rpl::on_next([=](const Calls::VideoStateToggle &update) {
if (update.value) {
// Add async (=> the participant row is definitely in Members).
const auto endpoint = update.endpoint;
crl::on_main(_viewport->widget(), [=] {
const auto &tracks = _call->activeVideoTracks();
const auto i = tracks.find(endpoint);
if (i != end(tracks)) {
setupTile(endpoint, i->second);
}
});
} else {
// Remove sync.
_viewport->remove(update.endpoint);
}
}, _viewport->lifetime());
_viewport->qualityRequests(
) | rpl::on_next([=](const Calls::VideoQualityRequest &request) {
_call->requestVideoQuality(request.endpoint, request.quality);
}, _viewport->lifetime());
_loading->lower();
_viewport->widget()->lower();
_viewport->setControlsShown(0.);
_call->hasVideoWithFramesValue(
) | rpl::on_next([=](bool has) {
if (has) {
_loading = nullptr;
}
}, _loading->lifetime());
setVolume(Core::App().settings().videoVolume());
}
void VideoStream::setupMessages() {
_messages->hiddenShowRequested() | rpl::on_next([=] {
_call->messages()->requestHiddenShow();
}, _messages->lifetime());
_messages->deleteRequests(
) | rpl::on_next([=](Calls::Group::MessageDeleteRequest event) {
_call->messages()->deleteConfirmed(event);
}, _messages->lifetime());
}
void VideoStream::setVolume(float64 volume) {
const auto value = volume * Calls::Group::kDefaultVolume;
_call->changeVolume({
.peer = _call->peer(),
.volume = int(base::SafeRound(value)),
});
}
} // namespace Media::View