From f18e157e4692587d34267ebe44a19fc115681c5e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 15 Jun 2021 12:33:21 +0400
Subject: [PATCH] Add video placeholder if can't receive it.

---
 Telegram/Resources/langs/lang.strings         |   2 +
 Telegram/SourceFiles/calls/calls.style        |   3 +
 .../calls/group/calls_group_call.cpp          |  82 +++++++----
 .../calls/group/calls_group_call.h            |  12 +-
 .../calls/group/calls_group_members.cpp       | 130 +++++++++++++++++-
 .../calls/group/calls_group_members.h         |   1 +
 .../calls/group/calls_group_panel.cpp         |   1 +
 .../calls/group/calls_group_viewport.cpp      |  14 +-
 .../calls/group/calls_group_viewport.h        |   6 +-
 .../group/calls_group_viewport_opengl.cpp     |  30 ++--
 .../calls/group/calls_group_viewport_tile.cpp |  18 +--
 .../calls/group/calls_group_viewport_tile.h   |   1 +
 Telegram/SourceFiles/core/application.cpp     |   8 +-
 .../media/view/media_view_overlay_widget.cpp  |   1 -
 .../media/view/media_view_pip_opengl.cpp      |  17 +--
 Telegram/ThirdParty/tgcalls                   |   2 +-
 16 files changed, 249 insertions(+), 79 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 3b2170c77..30ccd0944 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2058,6 +2058,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_group_call_ptt_delay" = "Push to Talk release delay: {delay}";
 "lng_group_call_share" = "Share Invite Link";
 "lng_group_call_noise_suppression" = "Enable Noise Suppression";
+"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
+"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
 "lng_group_call_share_speaker" = "Users with this link can speak";
 "lng_group_call_copy_speaker_link" = "Copy Speaker Link";
 "lng_group_call_copy_listener_link" = "Copy Listener Link";
diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style
index 288610e65..ddcea65fc 100644
--- a/Telegram/SourceFiles/calls/calls.style
+++ b/Telegram/SourceFiles/calls/calls.style
@@ -1229,6 +1229,9 @@ groupCallVideoTile: GroupCallVideoTile {
 
 groupCallVideoSmallSkip: 4px;
 groupCallVideoLargeSkip: 6px;
+groupCallVideoPlaceholderHeight: 212px;
+groupCallVideoPlaceholderIconTop: 50px;
+groupCallVideoPlaceholderTextTop: 120px;
 
 groupCallTooltip: Tooltip(defaultTooltip) {
 	textBg: groupCallMembersBg;
diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
index dfb86b422..cd40c9202 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
@@ -821,6 +821,9 @@ void GroupCall::setState(State state) {
 		if (const auto call = _peer->groupCall(); call && call->id() == _id) {
 			call->setInCall();
 		}
+		if (!videoIsWorking()) {
+			refreshHasNotShownVideo();
+		}
 	}
 
 	if (false
@@ -1043,6 +1046,9 @@ void GroupCall::markEndpointActive(
 		bool paused) {
 	if (!endpoint) {
 		return;
+	} else if (active && !videoIsWorking()) {
+		refreshHasNotShownVideo();
+		return;
 	}
 	const auto i = _activeVideoTracks.find(endpoint);
 	const auto changed = active
@@ -1067,26 +1073,34 @@ void GroupCall::markEndpointActive(
 				.peer = endpoint.peer,
 			}).first;
 		const auto track = i->second.track.get();
-		if (!track->frameSize().isEmpty()
-			|| track->state() == Webrtc::VideoState::Paused) {
+
+		track->renderNextFrame(
+		) | rpl::start_with_next([=] {
+			auto &activeTrack = _activeVideoTracks[endpoint];
+			const auto size = track->frameSize();
+			if (size.isEmpty()) {
+				track->markFrameShown();
+			} else if (!activeTrack.shown) {
+				activeTrack.shown = true;
+				markTrackShown(endpoint, true);
+			}
+			activeTrack.trackSize = size;
+		}, i->second.lifetime);
+
+		const auto size = track->frameSize();
+		i->second.trackSize = size;
+		if (!size.isEmpty() || paused) {
+			i->second.shown = true;
 			shown = true;
 		} else {
-			auto hasFrame = track->renderNextFrame() | rpl::map([=] {
-				return !track->frameSize().isEmpty();
-			});
-			auto isPaused = track->stateValue(
-			) | rpl::map([=](Webrtc::VideoState state) {
-				return (state == Webrtc::VideoState::Paused);
-			});
-			rpl::merge(
-				std::move(hasFrame),
-				std::move(isPaused)
-			) | rpl::filter([=](bool shouldShow) {
-				return shouldShow;
+			track->stateValue(
+			) | rpl::filter([=](Webrtc::VideoState state) {
+				return (state == Webrtc::VideoState::Paused)
+					&& !_activeVideoTracks[endpoint].shown;
 			}) | rpl::start_with_next([=] {
-				_activeVideoTracks[endpoint].shownTrackingLifetime.destroy();
+				_activeVideoTracks[endpoint].shown = true;
 				markTrackShown(endpoint, true);
-			}, i->second.shownTrackingLifetime);
+			}, i->second.lifetime);
 		}
 		addVideoOutput(i->first.id, { track->sink() });
 	} else {
@@ -1109,10 +1123,11 @@ void GroupCall::markTrackShown(const VideoEndpoint &endpoint, bool shown) {
 	const auto changed = shown
 		? _shownVideoTracks.emplace(endpoint).second
 		: _shownVideoTracks.remove(endpoint);
-	if (changed) {
-		_videoStreamShownUpdates.fire_copy({ endpoint, shown });
+	if (!changed) {
+		return;
 	}
-	if (shown && changed && endpoint.type == VideoEndpointType::Screen) {
+	_videoStreamShownUpdates.fire_copy({ endpoint, shown });
+	if (shown && endpoint.type == VideoEndpointType::Screen) {
 		crl::on_main(this, [=] {
 			if (_shownVideoTracks.contains(endpoint)) {
 				pinVideoEndpoint(endpoint);
@@ -2431,21 +2446,37 @@ void GroupCall::updateRequestedVideoChannelsDelayed() {
 	});
 }
 
+void GroupCall::refreshHasNotShownVideo() {
+	if (!_joinState.ssrc || hasNotShownVideo()) {
+		return;
+	}
+	const auto real = lookupReal();
+	Assert(real != nullptr);
+
+	const auto hasVideo = [&](const Data::GroupCallParticipant &data) {
+		return (data.peer != _joinAs)
+			&& (!GetCameraEndpoint(data.videoParams).empty()
+				|| !GetScreenEndpoint(data.videoParams).empty());
+	};
+	_hasNotShownVideo = _joinState.ssrc
+		&& ranges::any_of(real->participants(), hasVideo);
+}
+
 void GroupCall::fillActiveVideoEndpoints() {
 	const auto real = lookupReal();
 	Assert(real != nullptr);
 
-	if (const auto participant = real->participantByPeer(_joinAs)) {
-		_videoIsWorking = participant->videoJoined;
+	const auto me = real->participantByPeer(_joinAs);
+	if (me && me->videoJoined) {
+		_videoIsWorking = true;
+		_hasNotShownVideo = false;
 	} else {
+		refreshHasNotShownVideo();
 		_videoIsWorking = false;
-	}
-	if (!videoIsWorking()) {
 		toggleVideo(false);
 		toggleScreenSharing(std::nullopt);
 	}
 
-	const auto &participants = real->participants();
 	const auto &large = _videoEndpointLarge.current();
 	auto largeFound = false;
 	auto endpoints = _activeVideoTracks | ranges::views::transform([](
@@ -2469,7 +2500,7 @@ void GroupCall::fillActiveVideoEndpoints() {
 	};
 	using Type = VideoEndpointType;
 	if (_videoIsWorking.current()) {
-		for (const auto &participant : participants) {
+		for (const auto &participant : real->participants()) {
 			const auto camera = GetCameraEndpoint(participant.videoParams);
 			if (camera != _cameraEndpoint
 				&& camera != _screenEndpoint
@@ -2485,7 +2516,6 @@ void GroupCall::fillActiveVideoEndpoints() {
 				feedOne({ Type::Screen, participant.peer, screen }, paused);
 			}
 		}
-		const auto pausedState = Webrtc::VideoState::Paused;
 		feedOne(
 			{ Type::Camera, _joinAs, cameraSharingEndpoint() },
 			isCameraPaused());
diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h
index 2d66d25f4..e6bcae38e 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_call.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_call.h
@@ -324,9 +324,11 @@ public:
 
 	struct VideoTrack {
 		std::unique_ptr<Webrtc::VideoTrack> track;
+		rpl::variable<QSize> trackSize;
 		PeerData *peer = nullptr;
-		rpl::lifetime shownTrackingLifetime;
+		rpl::lifetime lifetime;
 		Group::VideoQuality quality = Group::VideoQuality();
+		bool shown = false;
 
 		[[nodiscard]] explicit operator bool() const {
 			return (track != nullptr);
@@ -366,6 +368,12 @@ public:
 	[[nodiscard]] rpl::producer<bool> videoIsWorkingValue() const {
 		return _videoIsWorking.value();
 	}
+	[[nodiscard]] bool hasNotShownVideo() const {
+		return _hasNotShownVideo.current();
+	}
+	[[nodiscard]] rpl::producer<bool> hasNotShownVideoValue() const {
+		return _hasNotShownVideo.value();
+	}
 
 	void setCurrentAudioDevice(bool input, const QString &deviceId);
 	void setCurrentVideoDevice(const QString &deviceId);
@@ -514,6 +522,7 @@ private:
 	void updateRequestedVideoChannels();
 	void updateRequestedVideoChannelsDelayed();
 	void fillActiveVideoEndpoints();
+	void refreshHasNotShownVideo();
 
 	void editParticipant(
 		not_null<PeerData*> participantPeer,
@@ -570,6 +579,7 @@ private:
 	rpl::variable<MuteState> _muted = MuteState::Muted;
 	rpl::variable<bool> _canManage = false;
 	rpl::variable<bool> _videoIsWorking = false;
+	rpl::variable<bool> _hasNotShownVideo = false;
 	bool _initialMuteStateSent = false;
 	bool _acceptFields = false;
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
index f3e4b3e80..c5a2ea3fb 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
@@ -29,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/application.h" // Core::App().domain, .activeWindow.
 #include "main/main_domain.h" // Core::App().domain().activate.
 #include "main/main_session.h"
+#include "main/main_account.h" // account().appConfig().
+#include "main/main_app_config.h" // appConfig().get<double>().
 #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
 #include "window/window_controller.h" // Controller::sessionController.
 #include "window/window_session_controller.h"
@@ -41,9 +43,110 @@ namespace {
 
 constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
 constexpr auto kShadowMaxAlpha = 74;
+constexpr auto kUserpicSizeForBlur = 40;
+constexpr auto kUserpicBlurRadius = 8;
 
 using Row = MembersRow;
 
+void SetupVideoPlaceholder(
+		not_null<Ui::RpWidget*> widget,
+		not_null<PeerData*> chat) {
+	struct State {
+		QImage blurred;
+		QImage rounded;
+		InMemoryKey key = {};
+		std::shared_ptr<Data::CloudImageView> view;
+		qint64 blurredCacheKey = 0;
+	};
+	const auto state = widget->lifetime().make_state<State>();
+	const auto refreshBlurred = [=] {
+		const auto key = chat->userpicUniqueKey(state->view);
+		if (state->key == key && !state->blurred.isNull()) {
+			return;
+		}
+		constexpr auto size = kUserpicSizeForBlur;
+		state->key = key;
+		state->blurred = QImage(
+			QSize(size, size),
+			QImage::Format_ARGB32_Premultiplied);
+		{
+			auto p = Painter(&state->blurred);
+			auto hq = PainterHighQualityEnabler(p);
+			chat->paintUserpicSquare(p, state->view, 0, 0, size);
+		}
+		state->blurred = Images::BlurLargeImage(
+			std::move(state->blurred),
+			kUserpicBlurRadius);
+		widget->update();
+	};
+	const auto refreshRounded = [=](QSize size) {
+		refreshBlurred();
+		const auto key = state->blurred.cacheKey();
+		if (state->rounded.size() == size && state->blurredCacheKey == key) {
+			return;
+		}
+		state->blurredCacheKey = key;
+		state->rounded = Images::prepare(
+			state->blurred,
+			size.width(),
+			size.width(), // Square
+			Images::Option::Smooth,
+			size.width(),
+			size.height());
+		{
+			auto p = QPainter(&state->rounded);
+			p.fillRect(
+				0,
+				0,
+				size.width(),
+				size.height(),
+				QColor(0, 0, 0, Viewport::kShadowMaxAlpha));
+		}
+		state->rounded = Images::prepare(
+			std::move(state->rounded),
+			size.width(),
+			size.height(),
+			(Images::Option::RoundedLarge | Images::Option::RoundedAll),
+			size.width(),
+			size.height());
+	};
+	chat->loadUserpic();
+	refreshBlurred();
+
+	widget->paintRequest(
+	) | rpl::start_with_next([=] {
+		const auto size = QSize(
+			widget->width(),
+			widget->height() - st::groupCallVideoSmallSkip);
+		refreshRounded(size * cIntRetinaFactor());
+
+		auto p = QPainter(widget);
+		const auto inner = QRect(QPoint(), size);
+		p.drawImage(inner, state->rounded);
+		st::groupCallPaused.paint(
+			p,
+			(size.width() - st::groupCallPaused.width()) / 2,
+			st::groupCallVideoPlaceholderIconTop,
+			size.width());
+
+		const auto skip = st::groupCallVideoLargeSkip;
+		const auto limit = chat->session().account().appConfig().get<double>(
+			"groupcall_video_participants_max",
+			30.);
+		p.setPen(st::groupCallVideoTextFg);
+		const auto text = QRect(
+			skip,
+			st::groupCallVideoPlaceholderTextTop,
+			(size.width() - 2 * skip),
+			size.height() - st::groupCallVideoPlaceholderTextTop);
+		p.setFont(st::semiboldFont);
+		p.drawText(
+			text,
+			tr::lng_group_call_limit(tr::now, lt_count, int(limit)),
+			style::al_top);
+	}, widget->lifetime());
+}
+
 } // namespace
 
 class Members::Controller final
@@ -1467,6 +1570,7 @@ Members::Members(
 , _layout(_scroll->setOwnedWidget(
 	object_ptr<Ui::VerticalLayout>(_scroll.data())))
 , _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
+, _videoPlaceholder(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
 , _viewport(
 	std::make_unique<Viewport>(
 		_videoWrap.get(),
@@ -1704,11 +1808,27 @@ void Members::trackViewportGeometry() {
 	_scroll->scrollTopValue(
 	) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
 
-	_viewport->fullHeightValue(
-	) | rpl::start_with_next([=](int height) {
-		_videoWrap->resize(_videoWrap->width(), height);
-		move();
-		resize();
+	rpl::combine(
+		_layout->widthValue(),
+		_call->hasNotShownVideoValue()
+	) | rpl::start_with_next([=](int width, bool has) {
+		const auto height = has ? st::groupCallVideoPlaceholderHeight : 0;
+		_videoPlaceholder->setGeometry(0, 0, width, height);
+	}, _videoPlaceholder->lifetime());
+
+	SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer());
+
+	rpl::combine(
+		_videoPlaceholder->heightValue(),
+		_viewport->fullHeightValue()
+	) | rpl::start_with_next([=](int placeholder, int viewport) {
+		_videoWrap->resize(
+			_videoWrap->width(),
+			std::max(placeholder, viewport));
+		if (viewport > 0) {
+			move();
+			resize();
+		}
 	}, _viewport->lifetime());
 }
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h
index f828d7910..198fdcaad 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_members.h
@@ -101,6 +101,7 @@ private:
 	std::unique_ptr<Controller> _listController;
 	not_null<Ui::VerticalLayout*> _layout;
 	const not_null<Ui::RpWidget*> _videoWrap;
+	const std::unique_ptr<Ui::RpWidget> _videoPlaceholder;
 	std::unique_ptr<Viewport> _viewport;
 	rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
 	RpWidget *_topSkip = nullptr;
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index a43acc24b..03b46c3e8 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -854,6 +854,7 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
 		viewport->add(
 			endpoint,
 			VideoTileTrack{ track.track.get(), row },
+			track.trackSize.value(),
 			std::move(pinned));
 	};
 	for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
index 7abe2cb20..497b7d978 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
@@ -225,10 +225,12 @@ void Viewport::setControlsShown(float64 shown) {
 void Viewport::add(
 		const VideoEndpoint &endpoint,
 		VideoTileTrack track,
+		rpl::producer<QSize> trackSize,
 		rpl::producer<bool> pinned) {
 	_tiles.push_back(std::make_unique<VideoTile>(
 		endpoint,
 		track,
+		std::move(trackSize),
 		std::move(pinned),
 		[=] { widget()->update(); }));
 
@@ -711,11 +713,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
 	const auto layoutNext = [&](not_null<VideoTile*> tile) {
 		const auto size = tile->trackOrUserpicSize();
 		const auto shown = !size.isEmpty() && _large && tile != _large;
-		const auto height = shown
-			? st::groupCallNarrowVideoHeight
-			: 0;
-		setTileGeometry(tile, { 0, y + top, outerWidth, height });
-		top += height ? (height + st::groupCallVideoSmallSkip) : 0;
+		const auto height = st::groupCallNarrowVideoHeight;
+		if (!shown) {
+			tile->hide();
+		} else {
+			setTileGeometry(tile, { 0, y + top, outerWidth, height });
+			top += height + st::groupCallVideoSmallSkip;
+		}
 	};
 	const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
 	const auto reorderNeeded = [&] {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
index b7c8c8080..a26e0a811 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
@@ -39,6 +39,7 @@ enum class VideoQuality;
 struct VideoTileTrack {
 	Webrtc::VideoTrack *track = nullptr;
 	MembersRow *row = nullptr;
+	rpl::variable<QSize> trackSize;
 
 	[[nodiscard]] explicit operator bool() const {
 		return track != nullptr;
@@ -77,6 +78,7 @@ public:
 	void add(
 		const VideoEndpoint &endpoint,
 		VideoTileTrack track,
+		rpl::producer<QSize> trackSize,
 		rpl::producer<bool> pinned);
 	void remove(const VideoEndpoint &endpoint);
 	void showLarge(const VideoEndpoint &endpoint);
@@ -91,6 +93,8 @@ public:
 
 	[[nodiscard]] rpl::lifetime &lifetime();
 
+	static constexpr auto kShadowMaxAlpha = 80;
+
 private:
 	struct Textures;
 	class VideoTile;
@@ -132,8 +136,6 @@ private:
 		}
 	};
 
-	static constexpr auto kShadowMaxAlpha = 80;
-
 	void setup();
 	[[nodiscard]] bool wide() const;
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
index 55044f935..e1aec4b91 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
@@ -320,7 +320,6 @@ Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
 void Viewport::RendererGL::init(
 		not_null<QOpenGLWidget*> widget,
 		QOpenGLFunctions &f) {
-	_factor = widget->devicePixelRatio();
 	_frameBuffer.emplace();
 	_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
 	_frameBuffer->create();
@@ -427,7 +426,11 @@ void Viewport::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {
 void Viewport::RendererGL::paint(
 		not_null<QOpenGLWidget*> widget,
 		QOpenGLFunctions &f) {
-	_factor = widget->devicePixelRatio();
+	const auto factor = widget->devicePixelRatio();
+	if (_factor != factor) {
+		_factor = factor;
+		_buttons.invalidate();
+	}
 	_viewport = widget->size();
 
 	const auto defaultFramebufferObject = widget->defaultFramebufferObject();
@@ -1054,7 +1057,6 @@ void Viewport::RendererGL::ensureButtonsImage() {
 	if (_buttons) {
 		return;
 	}
-	const auto factor = cIntRetinaFactor();
 	const auto pinOnSize = VideoTile::PinInnerSize(true);
 	const auto pinOffSize = VideoTile::PinInnerSize(false);
 	const auto backSize = VideoTile::BackInnerSize();
@@ -1074,18 +1076,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
 			+ backSize.height()
 			+ muteSize.height()
 			+ pausedSize.height()));
-	const auto imageSize = fullSize * factor;
+	const auto imageSize = fullSize * _factor;
 	auto image = _buttons.takeImage();
 	if (image.size() != imageSize) {
 		image = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
 	}
 	image.fill(Qt::transparent);
-	image.setDevicePixelRatio(cRetinaFactor());
+	image.setDevicePixelRatio(_factor);
 	{
 		auto p = Painter(&image);
 		auto hq = PainterHighQualityEnabler(p);
 
-		_pinOn = QRect(QPoint(), pinOnSize * factor);
+		_pinOn = QRect(QPoint(), pinOnSize * _factor);
 		VideoTile::PaintPinButton(
 			p,
 			true,
@@ -1096,7 +1098,9 @@ void Viewport::RendererGL::ensureButtonsImage() {
 			&_pinIcon);
 
 		const auto pinOffTop = pinOnSize.height();
-		_pinOff = QRect(QPoint(0, pinOffTop) * factor, pinOffSize * factor);
+		_pinOff = QRect(
+			QPoint(0, pinOffTop) * _factor,
+			pinOffSize * _factor);
 		VideoTile::PaintPinButton(
 			p,
 			false,
@@ -1107,7 +1111,7 @@ void Viewport::RendererGL::ensureButtonsImage() {
 			&_pinIcon);
 
 		const auto backTop = pinOffTop + pinOffSize.height();
-		_back = QRect(QPoint(0, backTop) * factor, backSize * factor);
+		_back = QRect(QPoint(0, backTop) * _factor, backSize * _factor);
 		VideoTile::PaintBackButton(
 			p,
 			0,
@@ -1116,16 +1120,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
 			&_pinBackground);
 
 		const auto muteTop = backTop + backSize.height();
-		_muteOn = QRect(QPoint(0, muteTop) * factor, muteSize * factor);
+		_muteOn = QRect(QPoint(0, muteTop) * _factor, muteSize * _factor);
 		_muteIcon.paint(p, { 0, muteTop }, 1.);
 
 		_muteOff = QRect(
-			QPoint(muteSize.width(), muteTop) * factor,
-			muteSize * factor);
+			QPoint(muteSize.width(), muteTop) * _factor,
+			muteSize * _factor);
 		_muteIcon.paint(p, { muteSize.width(), muteTop }, 0.);
 
 		const auto pausedTop = muteTop + muteSize.height();
-		_paused = QRect(QPoint(0, pausedTop) * factor, pausedSize * factor);
+		_paused = QRect(
+			QPoint(0, pausedTop) * _factor,
+			pausedSize * _factor);
 		st::groupCallPaused.paint(p, 0, pausedTop, fullSize.width());
 	}
 	_buttons.setImage(std::move(image));
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
index 46e61d79f..ccb5d33bc 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
@@ -25,11 +25,13 @@ constexpr auto kPausedVideoSize = 90;
 Viewport::VideoTile::VideoTile(
 	const VideoEndpoint &endpoint,
 	VideoTileTrack track,
+	rpl::producer<QSize> trackSize,
 	rpl::producer<bool> pinned,
 	Fn<void()> update)
 : _endpoint(endpoint)
 , _update(std::move(update))
-, _track(track) {
+, _track(track)
+, _trackSize(std::move(trackSize)) {
 	Expects(track.track != nullptr);
 	Expects(track.row != nullptr);
 
@@ -254,19 +256,7 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
 	}, _lifetime);
 
 	_track.track->renderNextFrame(
-	) | rpl::start_with_next([=] {
-		const auto size = _track.track->frameSize();
-		if (size.isEmpty()) {
-			_track.track->markFrameShown();
-		} else {
-			_trackSize = size;
-		}
-		_update();
-	}, _lifetime);
-
-	if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
-		_trackSize = size;
-	}
+	) | rpl::start_with_next(_update, _lifetime);
 
 	updateTopControlsSize();
 }
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
index 0b0080f0e..5b05f6797 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
@@ -26,6 +26,7 @@ public:
 	VideoTile(
 		const VideoEndpoint &endpoint,
 		VideoTileTrack track,
+		rpl::producer<QSize> trackSize,
 		rpl::producer<bool> pinned,
 		Fn<void()> update);
 
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index d6cf194dc..7577ede6d 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -304,10 +304,10 @@ void Application::showOpenGLCrashNotification() {
 		Local::writeSettings();
 	};
 	_window->show(Box<ConfirmBox>(
-		"Last time OpenGL crashed on initialization. "
-		"Perhaps it is a problem with your graphics card driver.\n\n"
-		"Right now OpenGL was disabled. You can try to enable it back "
-		"or keep it disabled, if it continues crashing.",
+		"There may be a problem with your graphics drivers and OpenGL. "
+		"Try updating your drivers.\n\n"
+		"OpenGL has been disabled. You can try to enable it again "
+		"or keep it disabled if crashes continue.",
 		"Enable",
 		"Keep Disabled",
 		enable,
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 252690e59..db7e50645 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -3274,7 +3274,6 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
 		}
 		paintRadialLoading(renderer);
 	} else {
-		int a = 0;
 		if (_themePreviewShown) {
 			renderer->paintThemePreview(_themePreviewRect);
 		} else if (documentBubbleShown() && !_docRect.isEmpty()) {
diff --git a/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp
index f027bb9f0..c295df385 100644
--- a/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp
@@ -406,21 +406,22 @@ void Pip::RendererGL::paintTransformedContent(
 	_f->glActiveTexture(rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3);
 	_shadowImage.bind(*_f);
 
+	const auto globalFactor = cIntRetinaFactor();
 	const auto fadeAlpha = st::radialBg->c.alphaF() * geometry.fade;
 	const auto roundRect = transformRect(RoundingRect(geometry));
 	program->setUniformValue("roundRect", Uniform(roundRect));
 	program->setUniformValue("h_texture", GLint(rgbaFrame ? 1 : 3));
 	program->setUniformValue("h_size", QSizeF(_shadowImage.image().size()));
 	program->setUniformValue("h_extend", QVector4D(
-		st::callShadow.extend.left(),
-		st::callShadow.extend.top(),
-		st::callShadow.extend.right(),
-		st::callShadow.extend.bottom()));
+		st::callShadow.extend.left() * globalFactor,
+		st::callShadow.extend.top() * globalFactor,
+		st::callShadow.extend.right() * globalFactor,
+		st::callShadow.extend.bottom() * globalFactor));
 	program->setUniformValue("h_components", QVector4D(
-		float(st::callShadow.topLeft.width()),
-		float(st::callShadow.topLeft.height()),
-		float(st::callShadow.left.width()),
-		float(st::callShadow.top.height())));
+		float(st::callShadow.topLeft.width() * globalFactor),
+		float(st::callShadow.topLeft.height() * globalFactor),
+		float(st::callShadow.left.width() * globalFactor),
+		float(st::callShadow.top.height() * globalFactor)));
 	program->setUniformValue(
 		"roundRadius",
 		GLfloat(st::roundRadiusLarge * _factor));
diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls
index 9adb220f7..d147f286c 160000
--- a/Telegram/ThirdParty/tgcalls
+++ b/Telegram/ThirdParty/tgcalls
@@ -1 +1 @@
-Subproject commit 9adb220f798cadc0848d3e1efbf32fb2a98a3bcb
+Subproject commit d147f286cfe2a23c49c438a63be9aa1c0a04344b