1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "media/view/media_view_pip.h"
9 
10 #include "media/streaming/media_streaming_player.h"
11 #include "media/streaming/media_streaming_document.h"
12 #include "media/streaming/media_streaming_utility.h"
13 #include "media/view/media_view_playback_progress.h"
14 #include "media/view/media_view_pip_opengl.h"
15 #include "media/view/media_view_pip_raster.h"
16 #include "media/audio/media_audio.h"
17 #include "data/data_document.h"
18 #include "data/data_document_media.h"
19 #include "data/data_file_origin.h"
20 #include "data/data_session.h"
21 #include "data/data_media_rotation.h"
22 #include "main/main_account.h"
23 #include "main/main_session.h"
24 #include "core/application.h"
25 #include "base/platform/base_platform_info.h"
26 #include "ui/platform/ui_platform_utility.h"
27 #include "ui/widgets/buttons.h"
28 #include "ui/wrap/fade_wrap.h"
29 #include "ui/widgets/shadow.h"
30 #include "ui/text/format_values.h"
31 #include "ui/gl/gl_surface.h"
32 #include "window/window_controller.h"
33 #include "styles/style_widgets.h"
34 #include "styles/style_window.h"
35 #include "styles/style_media_view.h"
36 
37 #include <QtGui/QWindow>
38 #include <QtGui/QScreen>
39 #include <QtWidgets/QApplication>
40 
41 namespace Media {
42 namespace View {
43 namespace {
44 
45 constexpr auto kPipLoaderPriority = 2;
46 constexpr auto kMsInSecond = 1000;
47 
IsWindowControlsOnLeft()48 [[nodiscard]] bool IsWindowControlsOnLeft() {
49 	return Platform::IsMac();
50 }
51 
ScreenFromPosition(QPoint point)52 [[nodiscard]] QRect ScreenFromPosition(QPoint point) {
53 	const auto screen = QGuiApplication::screenAt(point);
54 	const auto use = screen ? screen : QGuiApplication::primaryScreen();
55 	return use
56 		? use->availableGeometry()
57 		: QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight);
58 }
59 
ClampToEdges(QRect screen,QRect inner)60 [[nodiscard]] QPoint ClampToEdges(QRect screen, QRect inner) {
61 	const auto skip = st::pipBorderSkip;
62 	const auto area = st::pipBorderSnapArea;
63 	const auto sleft = screen.x() + skip;
64 	const auto stop = screen.y() + skip;
65 	const auto sright = screen.x() + screen.width() - skip;
66 	const auto sbottom = screen.y() + screen.height() - skip;
67 	const auto ileft = inner.x();
68 	const auto itop = inner.y();
69 	const auto iright = inner.x() + inner.width();
70 	const auto ibottom = inner.y() + inner.height();
71 	auto shiftx = 0;
72 	auto shifty = 0;
73 	if (iright + shiftx >= sright - area && iright + shiftx < sright + area) {
74 		shiftx += (sright - iright);
75 	}
76 	if (ileft + shiftx >= sleft - area && ileft + shiftx < sleft + area) {
77 		shiftx += (sleft - ileft);
78 	}
79 	if (ibottom + shifty >= sbottom - area && ibottom + shifty < sbottom + area) {
80 		shifty += (sbottom - ibottom);
81 	}
82 	if (itop + shifty >= stop - area && itop + shifty < stop + area) {
83 		shifty += (stop - itop);
84 	}
85 	return inner.topLeft() + QPoint(shiftx, shifty);
86 }
87 
Transformed(QRect original,QSize minimalSize,QSize maximalSize,QPoint delta,RectPart by)88 [[nodiscard]] QRect Transformed(
89 		QRect original,
90 		QSize minimalSize,
91 		QSize maximalSize,
92 		QPoint delta,
93 		RectPart by) {
94 	const auto width = original.width();
95 	const auto height = original.height();
96 	const auto x1 = width - minimalSize.width();
97 	const auto x2 = maximalSize.width() - width;
98 	const auto y1 = height - minimalSize.height();
99 	const auto y2 = maximalSize.height() - height;
100 	switch (by) {
101 	case RectPart::Center: return original.translated(delta);
102 	case RectPart::TopLeft:
103 		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
104 		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
105 		return original;
106 	case RectPart::TopRight:
107 		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
108 		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
109 		return original;
110 	case RectPart::BottomRight:
111 		original.setHeight(
112 			original.height() + std::clamp(delta.y(), -y1, y2));
113 		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
114 		return original;
115 	case RectPart::BottomLeft:
116 		original.setHeight(
117 			original.height() + std::clamp(delta.y(), -y1, y2));
118 		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
119 		return original;
120 	case RectPart::Left:
121 		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
122 		return original;
123 	case RectPart::Top:
124 		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
125 		return original;
126 	case RectPart::Right:
127 		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
128 		return original;
129 	case RectPart::Bottom:
130 		original.setHeight(
131 			original.height() + std::clamp(delta.y(), -y1, y2));
132 		return original;
133 	}
134 	return original;
135 	Unexpected("RectPart in PiP Transformed.");
136 }
137 
Constrained(QRect original,QSize minimalSize,QSize maximalSize,QSize ratio,RectPart by,RectParts attached)138 [[nodiscard]] QRect Constrained(
139 		QRect original,
140 		QSize minimalSize,
141 		QSize maximalSize,
142 		QSize ratio,
143 		RectPart by,
144 		RectParts attached) {
145 	if (by == RectPart::Center) {
146 		return original;
147 	} else if (!original.width() && !original.height()) {
148 		return QRect(original.topLeft(), ratio);
149 	}
150 	const auto widthLarger = (original.width() * ratio.height())
151 		> (original.height() * ratio.width());
152 	const auto desiredSize = ratio.scaled(
153 		original.size(),
154 		(((RectParts(by) & RectPart::AllCorners)
155 			|| ((by == RectPart::Top || by == RectPart::Bottom)
156 				&& widthLarger)
157 			|| ((by == RectPart::Left || by == RectPart::Right)
158 				&& !widthLarger))
159 			? Qt::KeepAspectRatio
160 			: Qt::KeepAspectRatioByExpanding));
161 	const auto newSize = QSize(
162 		std::clamp(
163 			desiredSize.width(),
164 			minimalSize.width(),
165 			maximalSize.width()),
166 		std::clamp(
167 			desiredSize.height(),
168 			minimalSize.height(),
169 			maximalSize.height()));
170 	switch (by) {
171 	case RectPart::TopLeft:
172 		return QRect(
173 			original.topLeft() + QPoint(
174 				original.width() - newSize.width(),
175 				original.height() - newSize.height()),
176 			newSize);
177 	case RectPart::TopRight:
178 		return QRect(
179 			original.topLeft() + QPoint(
180 				0,
181 				original.height() - newSize.height()),
182 			newSize);
183 	case RectPart::BottomRight:
184 		return QRect(original.topLeft(), newSize);
185 	case RectPart::BottomLeft:
186 		return QRect(
187 			original.topLeft() + QPoint(
188 				original.width() - newSize.width(),
189 				0),
190 			newSize);
191 	case RectPart::Left:
192 		return QRect(
193 			original.topLeft() + QPoint(
194 				(original.width() - newSize.width()),
195 				((attached & RectPart::Top)
196 					? 0
197 					: (attached & RectPart::Bottom)
198 					? (original.height() - newSize.height())
199 					: (original.height() - newSize.height()) / 2)),
200 			newSize);
201 	case RectPart::Top:
202 		return QRect(
203 			original.topLeft() + QPoint(
204 				((attached & RectPart::Left)
205 					? 0
206 					: (attached & RectPart::Right)
207 					? (original.width() - newSize.width())
208 					: (original.width() - newSize.width()) / 2),
209 				0),
210 			newSize);
211 	case RectPart::Right:
212 		return QRect(
213 			original.topLeft() + QPoint(
214 				0,
215 				((attached & RectPart::Top)
216 					? 0
217 					: (attached & RectPart::Bottom)
218 					? (original.height() - newSize.height())
219 					: (original.height() - newSize.height()) / 2)),
220 			newSize);
221 	case RectPart::Bottom:
222 		return QRect(
223 			original.topLeft() + QPoint(
224 				((attached & RectPart::Left)
225 					? 0
226 					: (attached & RectPart::Right)
227 					? (original.width() - newSize.width())
228 					: (original.width() - newSize.width()) / 2),
229 				(original.height() - newSize.height())),
230 			newSize);
231 	}
232 	Unexpected("RectPart in PiP Constrained.");
233 }
234 
Serialize(const PipPanel::Position & position)235 [[nodiscard]] QByteArray Serialize(const PipPanel::Position &position) {
236 	auto result = QByteArray();
237 	auto stream = QDataStream(&result, QIODevice::WriteOnly);
238 	stream.setVersion(QDataStream::Qt_5_3);
239 	stream
240 		<< qint32(position.attached.value())
241 		<< qint32(position.snapped.value())
242 		<< position.screen
243 		<< position.geometry;
244 	stream.device()->close();
245 
246 	return result;
247 }
248 
Deserialize(const QByteArray & data)249 [[nodiscard]] PipPanel::Position Deserialize(const QByteArray &data) {
250 	auto stream = QDataStream(data);
251 	auto result = PipPanel::Position();
252 	auto attached = qint32(0);
253 	auto snapped = qint32(0);
254 	stream >> attached >> snapped >> result.screen >> result.geometry;
255 	if (stream.status() != QDataStream::Ok) {
256 		return {};
257 	}
258 	result.attached = RectParts::from_raw(attached);
259 	result.snapped = RectParts::from_raw(snapped);
260 	return result;
261 }
262 
RectPartToQtEdges(RectPart rectPart)263 Qt::Edges RectPartToQtEdges(RectPart rectPart) {
264 	switch (rectPart) {
265 	case RectPart::TopLeft:
266 		return Qt::TopEdge | Qt::LeftEdge;
267 	case RectPart::TopRight:
268 		return Qt::TopEdge | Qt::RightEdge;
269 	case RectPart::BottomRight:
270 		return Qt::BottomEdge | Qt::RightEdge;
271 	case RectPart::BottomLeft:
272 		return Qt::BottomEdge | Qt::LeftEdge;
273 	case RectPart::Left:
274 		return Qt::LeftEdge;
275 	case RectPart::Top:
276 		return Qt::TopEdge;
277 	case RectPart::Right:
278 		return Qt::RightEdge;
279 	case RectPart::Bottom:
280 		return Qt::BottomEdge;
281 	}
282 
283 	return Qt::Edges();
284 }
285 
286 } // namespace
287 
RotatedRect(QRect rect,int rotation)288 QRect RotatedRect(QRect rect, int rotation) {
289 	switch (rotation) {
290 	case 0: return rect;
291 	case 90: return QRect(
292 		rect.y(),
293 		-rect.x() - rect.width(),
294 		rect.height(),
295 		rect.width());
296 	case 180: return QRect(
297 		-rect.x() - rect.width(),
298 		-rect.y() - rect.height(),
299 		rect.width(),
300 		rect.height());
301 	case 270: return QRect(
302 		-rect.y() - rect.height(),
303 		rect.x(),
304 		rect.height(),
305 		rect.width());
306 	}
307 	Unexpected("Rotation in RotatedRect.");
308 }
309 
UsePainterRotation(int rotation)310 bool UsePainterRotation(int rotation) {
311 	return !(rotation % 180);
312 }
313 
FlipSizeByRotation(QSize size,int rotation)314 QSize FlipSizeByRotation(QSize size, int rotation) {
315 	return (((rotation / 90) % 2) == 1)
316 		? QSize(size.height(), size.width())
317 		: size;
318 }
319 
RotateFrameImage(QImage image,int rotation)320 QImage RotateFrameImage(QImage image, int rotation) {
321 	auto transform = QTransform();
322 	transform.rotate(rotation);
323 	return image.transformed(transform);
324 }
325 
PipPanel(QWidget * parent,Fn<Ui::GL::ChosenRenderer (Ui::GL::Capabilities)> renderer)326 PipPanel::PipPanel(
327 	QWidget *parent,
328 	Fn<Ui::GL::ChosenRenderer(Ui::GL::Capabilities)> renderer)
329 : _content(Ui::GL::CreateSurface(std::move(renderer)))
330 , _parent(parent) {
331 }
332 
init()333 void PipPanel::init() {
334 	widget()->setWindowFlags(Qt::Tool
335 		| Qt::WindowStaysOnTopHint
336 		| Qt::FramelessWindowHint
337 		| Qt::WindowDoesNotAcceptFocus);
338 	widget()->setAttribute(Qt::WA_ShowWithoutActivating);
339 	widget()->setAttribute(Qt::WA_MacAlwaysShowToolWindow);
340 	widget()->setAttribute(Qt::WA_NoSystemBackground);
341 	widget()->setAttribute(Qt::WA_TranslucentBackground);
342 	Ui::Platform::IgnoreAllActivation(widget());
343 	Ui::Platform::InitOnTopPanel(widget());
344 	widget()->setMouseTracking(true);
345 	widget()->resize(0, 0);
346 	widget()->hide();
347 	widget()->createWinId();
348 
349 	rp()->shownValue(
350 	) | rpl::filter([=](bool shown) {
351 		return shown;
352 	}) | rpl::start_with_next([=] {
353 		// Workaround Qt's forced transient parent.
354 		Ui::Platform::ClearTransientParent(widget());
355 	}, rp()->lifetime());
356 }
357 
widget() const358 not_null<QWidget*> PipPanel::widget() const {
359 	return _content->rpWidget();
360 }
361 
rp() const362 not_null<Ui::RpWidgetWrap*> PipPanel::rp() const {
363 	return _content.get();
364 }
365 
setAspectRatio(QSize ratio)366 void PipPanel::setAspectRatio(QSize ratio) {
367 	if (_ratio == ratio) {
368 		return;
369 	}
370 	_ratio = ratio;
371 	if (_ratio.isEmpty()) {
372 		_ratio = QSize(1, 1);
373 	}
374 	if (!widget()->size().isEmpty()) {
375 		setPosition(countPosition());
376 	}
377 }
378 
setPosition(Position position)379 void PipPanel::setPosition(Position position) {
380 	if (!position.screen.isEmpty()) {
381 		for (const auto screen : QApplication::screens()) {
382 			if (screen->geometry() == position.screen) {
383 				setPositionOnScreen(position, screen->availableGeometry());
384 				return;
385 			}
386 		}
387 	}
388 	setPositionDefault();
389 }
390 
inner() const391 QRect PipPanel::inner() const {
392 	return widget()->rect().marginsRemoved(_padding);
393 }
394 
attached() const395 RectParts PipPanel::attached() const {
396 	return _attached;
397 }
398 
useTransparency() const399 bool PipPanel::useTransparency() const {
400 	return _useTransparency;
401 }
402 
setDragDisabled(bool disabled)403 void PipPanel::setDragDisabled(bool disabled) {
404 	_dragDisabled = disabled;
405 	if (_dragState) {
406 		_dragState = std::nullopt;
407 	}
408 }
409 
dragging() const410 bool PipPanel::dragging() const {
411 	return _dragState.has_value();
412 }
413 
saveGeometryRequests() const414 rpl::producer<> PipPanel::saveGeometryRequests() const {
415 	return _saveGeometryRequests.events();
416 }
417 
myScreen() const418 QScreen *PipPanel::myScreen() const {
419 	if (const auto window = widget()->windowHandle()) {
420 		return window->screen();
421 	}
422 	return nullptr;
423 }
424 
countPosition() const425 PipPanel::Position PipPanel::countPosition() const {
426 	const auto screen = myScreen();
427 	if (!screen) {
428 		return Position();
429 	}
430 	auto result = Position();
431 	result.screen = screen->geometry();
432 	result.geometry = widget()->geometry().marginsRemoved(_padding);
433 	const auto available = screen->availableGeometry();
434 	const auto skip = st::pipBorderSkip;
435 	const auto left = result.geometry.x();
436 	const auto right = left + result.geometry.width();
437 	const auto top = result.geometry.y();
438 	const auto bottom = top + result.geometry.height();
439 	if ((!_dragState || *_dragState != RectPart::Center)
440 		&& !Platform::IsWayland()) {
441 		if (left == available.x()) {
442 			result.attached |= RectPart::Left;
443 		} else if (right == available.x() + available.width()) {
444 			result.attached |= RectPart::Right;
445 		} else if (left == available.x() + skip) {
446 			result.snapped |= RectPart::Left;
447 		} else if (right == available.x() + available.width() - skip) {
448 			result.snapped |= RectPart::Right;
449 		}
450 		if (top == available.y()) {
451 			result.attached |= RectPart::Top;
452 		} else if (bottom == available.y() + available.height()) {
453 			result.attached |= RectPart::Bottom;
454 		} else if (top == available.y() + skip) {
455 			result.snapped |= RectPart::Top;
456 		} else if (bottom == available.y() + available.height() - skip) {
457 			result.snapped |= RectPart::Bottom;
458 		}
459 	}
460 	return result;
461 }
462 
setPositionDefault()463 void PipPanel::setPositionDefault() {
464 	const auto widgetScreen = [&](auto &&widget) -> QScreen* {
465 		if (auto handle = widget ? widget->windowHandle() : nullptr) {
466 			return handle->screen();
467 		}
468 		return nullptr;
469 	};
470 	const auto parentScreen = widgetScreen(_parent);
471 	const auto myScreen = widgetScreen(widget());
472 	if (parentScreen && myScreen && myScreen != parentScreen) {
473 		widget()->windowHandle()->setScreen(parentScreen);
474 	}
475 	const auto screen = parentScreen
476 		? parentScreen
477 		: QGuiApplication::primaryScreen();
478 	auto position = Position();
479 	position.snapped = RectPart::Top | RectPart::Left;
480 	position.screen = screen->geometry();
481 	position.geometry = QRect(0, 0, st::pipDefaultSize, st::pipDefaultSize);
482 	setPositionOnScreen(position, screen->availableGeometry());
483 }
484 
setPositionOnScreen(Position position,QRect available)485 void PipPanel::setPositionOnScreen(Position position, QRect available) {
486 	const auto screen = available;
487 	const auto requestedSize = position.geometry.size();
488 	const auto max = std::max(requestedSize.width(), requestedSize.height());
489 
490 	// Apply aspect ratio.
491 	const auto scaled = (_ratio.width() > _ratio.height())
492 		? QSize(max, max * _ratio.height() / _ratio.width())
493 		: QSize(max * _ratio.width() / _ratio.height(), max);
494 
495 	// At least one side should not be greater than half of screen size.
496 	const auto byWidth = (scaled.width() * screen.height())
497 		> (scaled.height() * screen.width());
498 	const auto fit = QSize(screen.width() / 2, screen.height() / 2);
499 	const auto normalized = (byWidth && scaled.width() > fit.width())
500 		? QSize(fit.width(), fit.width() * scaled.height() / scaled.width())
501 		: (!byWidth && scaled.height() > fit.height())
502 		? QSize(
503 			fit.height() * scaled.width() / scaled.height(),
504 			fit.height())
505 		: scaled;
506 
507 	// Apply minimal size.
508 	const auto min = st::pipMinimalSize;
509 	const auto minimalSize = (_ratio.width() > _ratio.height())
510 		? QSize(min * _ratio.width() / _ratio.height(), min)
511 		: QSize(min, min * _ratio.height() / _ratio.width());
512 	const auto size = QSize(
513 		std::max(normalized.width(), minimalSize.width()),
514 		std::max(normalized.height(), minimalSize.height()));
515 
516 	// Apply maximal size.
517 	const auto maximalSize = (_ratio.width() > _ratio.height())
518 		? QSize(fit.width(), fit.width() * _ratio.height() / _ratio.width())
519 		: QSize(fit.height() * _ratio.width() / _ratio.height(), fit.height());
520 
521 	// Apply left-right screen borders.
522 	const auto skip = st::pipBorderSkip;
523 	const auto inner = screen.marginsRemoved({ skip, skip, skip, skip });
524 	auto geometry = QRect(position.geometry.topLeft(), size);
525 	if ((position.attached & RectPart::Left)
526 		|| (geometry.x() < screen.x())) {
527 		geometry.moveLeft(screen.x());
528 	} else if ((position.attached & RectPart::Right)
529 		|| (geometry.x() + geometry.width() > screen.x() + screen.width())) {
530 		geometry.moveLeft(screen.x() + screen.width() - geometry.width());
531 	} else if (position.snapped & RectPart::Left) {
532 		geometry.moveLeft(inner.x());
533 	} else if (position.snapped & RectPart::Right) {
534 		geometry.moveLeft(inner.x() + inner.width() - geometry.width());
535 	}
536 
537 	// Apply top-bottom screen borders.
538 	if ((position.attached & RectPart::Top) || (geometry.y() < screen.y())) {
539 		geometry.moveTop(screen.y());
540 	} else if ((position.attached & RectPart::Bottom)
541 		|| (geometry.y() + geometry.height()
542 			> screen.y() + screen.height())) {
543 		geometry.moveTop(
544 			screen.y() + screen.height() - geometry.height());
545 	} else if (position.snapped & RectPart::Top) {
546 		geometry.moveTop(inner.y());
547 	} else if (position.snapped & RectPart::Bottom) {
548 		geometry.moveTop(inner.y() + inner.height() - geometry.height());
549 	}
550 
551 	geometry += _padding;
552 
553 	setGeometry(geometry);
554 	widget()->setMinimumSize(minimalSize);
555 	widget()->setMaximumSize(
556 		std::max(minimalSize.width(), maximalSize.width()),
557 		std::max(minimalSize.height(), maximalSize.height()));
558 	updateDecorations();
559 }
560 
update()561 void PipPanel::update() {
562 	widget()->update();
563 }
564 
setGeometry(QRect geometry)565 void PipPanel::setGeometry(QRect geometry) {
566 	widget()->setGeometry(geometry);
567 }
568 
handleMousePress(QPoint position,Qt::MouseButton button)569 void PipPanel::handleMousePress(QPoint position, Qt::MouseButton button) {
570 	if (button != Qt::LeftButton) {
571 		return;
572 	}
573 	updateOverState(position);
574 	_pressState = _overState;
575 	_pressPoint = QCursor::pos();
576 }
577 
handleMouseRelease(QPoint position,Qt::MouseButton button)578 void PipPanel::handleMouseRelease(QPoint position, Qt::MouseButton button) {
579 	if (button != Qt::LeftButton || !base::take(_pressState)) {
580 		return;
581 	} else if (base::take(_dragState)) {
582 		finishDrag(QCursor::pos());
583 		updateOverState(position);
584 	}
585 }
586 
updateOverState(QPoint point)587 void PipPanel::updateOverState(QPoint point) {
588 	const auto size = st::pipResizeArea;
589 	const auto ignore = _attached | _snapped;
590 	const auto count = [&](RectPart side, int padding) {
591 		return (ignore & side) ? 0 : padding ? padding : size;
592 	};
593 	const auto left = count(RectPart::Left, _padding.left());
594 	const auto top = count(RectPart::Top, _padding.top());
595 	const auto right = count(RectPart::Right, _padding.right());
596 	const auto bottom = count(RectPart::Bottom, _padding.bottom());
597 	const auto width = widget()->width();
598 	const auto height = widget()->height();
599 	const auto overState = [&] {
600 		if (point.x() < left) {
601 			if (point.y() < top) {
602 				return RectPart::TopLeft;
603 			} else if (point.y() >= height - bottom) {
604 				return RectPart::BottomLeft;
605 			} else {
606 				return RectPart::Left;
607 			}
608 		} else if (point.x() >= width - right) {
609 			if (point.y() < top) {
610 				return RectPart::TopRight;
611 			} else if (point.y() >= height - bottom) {
612 				return RectPart::BottomRight;
613 			} else {
614 				return RectPart::Right;
615 			}
616 		} else if (point.y() < top) {
617 			return RectPart::Top;
618 		} else if (point.y() >= height - bottom) {
619 			return RectPart::Bottom;
620 		} else {
621 			return RectPart::Center;
622 		}
623 	}();
624 	if (_overState != overState) {
625 		_overState = overState;
626 		widget()->setCursor([&] {
627 			switch (_overState) {
628 			case RectPart::Center:
629 				return style::cur_pointer;
630 			case RectPart::TopLeft:
631 			case RectPart::BottomRight:
632 				return style::cur_sizefdiag;
633 			case RectPart::TopRight:
634 			case RectPart::BottomLeft:
635 				return style::cur_sizebdiag;
636 			case RectPart::Left:
637 			case RectPart::Right:
638 				return style::cur_sizehor;
639 			case RectPart::Top:
640 			case RectPart::Bottom:
641 				return style::cur_sizever;
642 			}
643 			Unexpected("State in PipPanel::updateOverState.");
644 		}());
645 	}
646 }
647 
handleMouseMove(QPoint position)648 void PipPanel::handleMouseMove(QPoint position) {
649 	if (!_pressState) {
650 		updateOverState(position);
651 		return;
652 	}
653 	const auto point = QCursor::pos();
654 	const auto distance = QApplication::startDragDistance();
655 	if (!_dragState
656 		&& (point - _pressPoint).manhattanLength() > distance
657 		&& !_dragDisabled) {
658 		_dragState = _pressState;
659 		updateDecorations();
660 		_dragStartGeometry = widget()->geometry().marginsRemoved(_padding);
661 	}
662 	if (_dragState) {
663 		if (Platform::IsWayland()) {
664 			startSystemDrag();
665 		} else {
666 			processDrag(point);
667 		}
668 	}
669 }
670 
startSystemDrag()671 void PipPanel::startSystemDrag() {
672 	Expects(_dragState.has_value());
673 
674 	const auto stateEdges = RectPartToQtEdges(*_dragState);
675 	if (stateEdges) {
676 		widget()->windowHandle()->startSystemResize(stateEdges);
677 	} else {
678 		widget()->windowHandle()->startSystemMove();
679 	}
680 }
681 
processDrag(QPoint point)682 void PipPanel::processDrag(QPoint point) {
683 	Expects(_dragState.has_value());
684 
685 	const auto dragPart = *_dragState;
686 	const auto screen = (dragPart == RectPart::Center)
687 		? ScreenFromPosition(point)
688 		: myScreen()
689 		? myScreen()->availableGeometry()
690 		: QRect();
691 	if (screen.isEmpty()) {
692 		return;
693 	}
694 	const auto minimalSize = _ratio.scaled(
695 		st::pipMinimalSize,
696 		st::pipMinimalSize,
697 		Qt::KeepAspectRatioByExpanding);
698 	const auto maximalSize = _ratio.scaled(
699 		screen.width() / 2,
700 		screen.height() / 2,
701 		Qt::KeepAspectRatio);
702 	const auto geometry = Transformed(
703 		_dragStartGeometry,
704 		minimalSize,
705 		maximalSize,
706 		point - _pressPoint,
707 		dragPart);
708 	const auto valid = Constrained(
709 		geometry,
710 		minimalSize,
711 		maximalSize,
712 		_ratio,
713 		dragPart,
714 		_attached);
715 	const auto clamped = (dragPart == RectPart::Center)
716 		? ClampToEdges(screen, valid)
717 		: valid.topLeft();
718 	if (clamped != valid.topLeft()) {
719 		moveAnimated(clamped);
720 	} else {
721 		const auto newGeometry = valid.marginsAdded(_padding);
722 		_positionAnimation.stop();
723 		setGeometry(newGeometry);
724 	}
725 }
726 
finishDrag(QPoint point)727 void PipPanel::finishDrag(QPoint point) {
728 	const auto screen = ScreenFromPosition(point);
729 	const auto inner = widget()->geometry().marginsRemoved(_padding);
730 	const auto position = widget()->pos();
731 	const auto clamped = [&] {
732 		auto result = position;
733 		if (Platform::IsWayland()) {
734 			return result;
735 		}
736 		if (result.x() > screen.x() + screen.width() - inner.width()) {
737 			result.setX(screen.x() + screen.width() - inner.width());
738 		}
739 		if (result.x() < screen.x()) {
740 			result.setX(screen.x());
741 		}
742 		if (result.y() > screen.y() + screen.height() - inner.height()) {
743 			result.setY(screen.y() + screen.height() - inner.height());
744 		}
745 		if (result.y() < screen.y()) {
746 			result.setY(screen.y());
747 		}
748 		return result;
749 	}();
750 	if (position != clamped) {
751 		moveAnimated(clamped);
752 	} else {
753 		_positionAnimation.stop();
754 		updateDecorations();
755 	}
756 }
757 
updatePositionAnimated()758 void PipPanel::updatePositionAnimated() {
759 	const auto progress = _positionAnimation.value(1.);
760 	if (!_positionAnimation.animating()) {
761 		widget()->move(_positionAnimationTo
762 			- QPoint(_padding.left(), _padding.top()));
763 		if (!_dragState) {
764 			updateDecorations();
765 		}
766 		return;
767 	}
768 	const auto from = QPointF(_positionAnimationFrom);
769 	const auto to = QPointF(_positionAnimationTo);
770 	widget()->move((from + (to - from) * progress).toPoint()
771 		- QPoint(_padding.left(), _padding.top()));
772 }
773 
moveAnimated(QPoint to)774 void PipPanel::moveAnimated(QPoint to) {
775 	if (_positionAnimation.animating() && _positionAnimationTo == to) {
776 		return;
777 	}
778 	_positionAnimationTo = to;
779 	_positionAnimationFrom = widget()->pos()
780 		+ QPoint(_padding.left(), _padding.top());
781 	_positionAnimation.stop();
782 	_positionAnimation.start(
783 		[=] { updatePositionAnimated(); },
784 		0.,
785 		1.,
786 		st::slideWrapDuration,
787 		anim::easeOutCirc);
788 }
789 
updateDecorations()790 void PipPanel::updateDecorations() {
791 	const auto guard = gsl::finally([&] {
792 		if (!_dragState) {
793 			_saveGeometryRequests.fire({});
794 		}
795 	});
796 	const auto position = countPosition();
797 	const auto center = position.geometry.center();
798 	const auto use = Ui::Platform::TranslucentWindowsSupported(center);
799 	const auto full = use ? st::callShadow.extend : style::margins();
800 	const auto padding = style::margins(
801 		(position.attached & RectPart::Left) ? 0 : full.left(),
802 		(position.attached & RectPart::Top) ? 0 : full.top(),
803 		(position.attached & RectPart::Right) ? 0 : full.right(),
804 		(position.attached & RectPart::Bottom) ? 0 : full.bottom());
805 	_snapped = position.snapped;
806 	if (_padding == padding && _attached == position.attached) {
807 		return;
808 	}
809 	const auto newGeometry = position.geometry.marginsAdded(padding);
810 	_attached = position.attached;
811 	_padding = padding;
812 	_useTransparency = use;
813 	widget()->setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
814 	setGeometry(newGeometry);
815 	update();
816 }
817 
Pip(not_null<Delegate * > delegate,not_null<DocumentData * > data,FullMsgId contextId,std::shared_ptr<Streaming::Document> shared,FnMut<void ()> closeAndContinue,FnMut<void ()> destroy)818 Pip::Pip(
819 	not_null<Delegate*> delegate,
820 	not_null<DocumentData*> data,
821 	FullMsgId contextId,
822 	std::shared_ptr<Streaming::Document> shared,
823 	FnMut<void()> closeAndContinue,
824 	FnMut<void()> destroy)
825 : _delegate(delegate)
826 , _data(data)
827 , _contextId(contextId)
828 , _instance(std::move(shared), [=] { waitingAnimationCallback(); })
829 , _panel(
830 	_delegate->pipParentWidget(),
__anone86b8e890c02(Ui::GL::Capabilities capabilities) 831 	[=](Ui::GL::Capabilities capabilities) {
832 		return chooseRenderer(capabilities);
833 	})
834 , _playbackProgress(std::make_unique<PlaybackProgress>())
835 , _rotation(data->owner().mediaRotation().get(data))
836 , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.)
837 	? Core::App().settings().videoVolume()
838 	: Core::Settings::kDefaultVolume)
839 , _closeAndContinue(std::move(closeAndContinue))
840 , _destroy(std::move(destroy)) {
841 	setupPanel();
842 	setupButtons();
843 	setupStreaming();
844 
845 	_data->session().account().sessionChanges(
__anone86b8e890d02null846 	) | rpl::start_with_next([=] {
847 		_destroy();
848 	}, _panel.rp()->lifetime());
849 }
850 
851 Pip::~Pip() = default;
852 
setupPanel()853 void Pip::setupPanel() {
854 	_panel.init();
855 	const auto size = [&] {
856 		if (!_instance.info().video.size.isEmpty()) {
857 			return _instance.info().video.size;
858 		}
859 		const auto media = _data->activeMediaView();
860 		if (media) {
861 			media->goodThumbnailWanted();
862 		}
863 		const auto good = media ? media->goodThumbnail() : nullptr;
864 		const auto original = good ? good->size() : _data->dimensions;
865 		return original.isEmpty() ? QSize(1, 1) : original;
866 	}();
867 	_panel.setAspectRatio(FlipSizeByRotation(size, _rotation));
868 	_panel.setPosition(Deserialize(_delegate->pipLoadGeometry()));
869 	_panel.widget()->show();
870 
871 	_panel.saveGeometryRequests(
872 	) | rpl::start_with_next([=] {
873 		saveGeometry();
874 	}, _panel.rp()->lifetime());
875 
876 	_panel.rp()->events(
877 	) | rpl::start_with_next([=](not_null<QEvent*> e) {
878 		const auto mousePosition = [&] {
879 			return static_cast<QMouseEvent*>(e.get())->pos();
880 		};
881 		const auto mouseButton = [&] {
882 			return static_cast<QMouseEvent*>(e.get())->button();
883 		};
884 		switch (e->type()) {
885 		case QEvent::Close: handleClose(); break;
886 		case QEvent::Leave: handleLeave(); break;
887 		case QEvent::MouseMove:
888 			handleMouseMove(mousePosition());
889 			break;
890 		case QEvent::MouseButtonPress:
891 			handleMousePress(mousePosition(), mouseButton());
892 			break;
893 		case QEvent::MouseButtonRelease:
894 			handleMouseRelease(mousePosition(), mouseButton());
895 			break;
896 		case QEvent::MouseButtonDblClick:
897 			handleDoubleClick(mouseButton());
898 			break;
899 		}
900 	}, _panel.rp()->lifetime());
901 }
902 
handleClose()903 void Pip::handleClose() {
904 	crl::on_main(_panel.widget(), [=] {
905 		_destroy();
906 	});
907 }
908 
handleLeave()909 void Pip::handleLeave() {
910 	setOverState(OverState::None);
911 }
912 
handleMouseMove(QPoint position)913 void Pip::handleMouseMove(QPoint position) {
914 	const auto weak = Ui::MakeWeak(_panel.widget());
915 	const auto guard = gsl::finally([&] {
916 		if (weak) {
917 			_panel.handleMouseMove(position);
918 		}
919 	});
920 	setOverState(computeState(position));
921 	seekUpdate(position);
922 	volumeControllerUpdate(position);
923 }
924 
setOverState(OverState state)925 void Pip::setOverState(OverState state) {
926 	if (_over == state) {
927 		return;
928 	}
929 	const auto wasShown = ResolveShownOver(_over);
930 	_over = state;
931 	const auto nowAreShown = (ResolveShownOver(_over) != OverState::None);
932 	if ((wasShown != OverState::None) != nowAreShown) {
933 		_controlsShown.start(
934 			[=] { _panel.update(); },
935 			nowAreShown ? 0. : 1.,
936 			nowAreShown ? 1. : 0.,
937 			st::fadeWrapDuration,
938 			anim::linear);
939 	}
940 	if (!_pressed) {
941 		updateActiveState(wasShown);
942 	}
943 	_panel.update();
944 }
945 
setPressedState(std::optional<OverState> state)946 void Pip::setPressedState(std::optional<OverState> state) {
947 	if (_pressed == state) {
948 		return;
949 	}
950 	const auto wasShown = shownActiveState();
951 	_pressed = state;
952 	updateActiveState(wasShown);
953 }
954 
shownActiveState() const955 Pip::OverState Pip::shownActiveState() const {
956 	return ResolveShownOver(_pressed.value_or(_over));
957 }
958 
activeValue(const Button & button) const959 float64 Pip::activeValue(const Button &button) const {
960 	const auto shownState = ResolveShownOver(button.state);
961 	return button.active.value((shownActiveState() == shownState) ? 1. : 0.);
962 }
963 
updateActiveState(OverState wasShown)964 void Pip::updateActiveState(OverState wasShown) {
965 	const auto check = [&](Button &button) {
966 		const auto shownState = ResolveShownOver(button.state);
967 		const auto nowIsShown = (shownActiveState() == shownState);
968 		if ((wasShown == shownState) != nowIsShown) {
969 			button.active.start(
970 				[=, &button] { _panel.widget()->update(button.icon); },
971 				nowIsShown ? 0. : 1.,
972 				nowIsShown ? 1. : 0.,
973 				st::fadeWrapDuration,
974 				anim::linear);
975 		}
976 	};
977 	check(_close);
978 	check(_enlarge);
979 	check(_play);
980 	check(_playback);
981 	check(_volumeToggle);
982 	check(_volumeController);
983 }
984 
ResolveShownOver(OverState state)985 Pip::OverState Pip::ResolveShownOver(OverState state) {
986 	return (state == OverState::VolumeController)
987 		? OverState::VolumeToggle
988 		: state;
989 }
990 
handleMousePress(QPoint position,Qt::MouseButton button)991 void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
992 	const auto weak = Ui::MakeWeak(_panel.widget());
993 	const auto guard = gsl::finally([&] {
994 		if (weak) {
995 			_panel.handleMousePress(position, button);
996 		}
997 	});
998 	if (button != Qt::LeftButton) {
999 		return;
1000 	}
1001 	_pressed = _over;
1002 	if (_over == OverState::Playback || _over == OverState::VolumeController) {
1003 		_panel.setDragDisabled(true);
1004 	}
1005 	seekUpdate(position);
1006 	volumeControllerUpdate(position);
1007 }
1008 
handleMouseRelease(QPoint position,Qt::MouseButton button)1009 void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
1010 	Expects(1 && _delegate->pipPlaybackSpeed() >= 0.5
1011 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1012 
1013 	const auto weak = Ui::MakeWeak(_panel.widget());
1014 	const auto guard = gsl::finally([&] {
1015 		if (weak) {
1016 			_panel.handleMouseRelease(position, button);
1017 		}
1018 	});
1019 	if (button != Qt::LeftButton) {
1020 		return;
1021 	}
1022 	seekUpdate(position);
1023 
1024 	Assert(2 && _delegate->pipPlaybackSpeed() >= 0.5
1025 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1026 
1027 	volumeControllerUpdate(position);
1028 
1029 	Assert(3 && _delegate->pipPlaybackSpeed() >= 0.5
1030 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1031 
1032 	const auto pressed = base::take(_pressed);
1033 	if (pressed && *pressed == OverState::Playback) {
1034 		_panel.setDragDisabled(false);
1035 
1036 		Assert(4 && _delegate->pipPlaybackSpeed() >= 0.5
1037 			&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1038 
1039 		seekFinish(_playbackProgress->value());
1040 	} else if (pressed && *pressed == OverState::VolumeController) {
1041 		_panel.setDragDisabled(false);
1042 		_panel.update();
1043 	} else if (_panel.dragging() || !pressed || *pressed != _over) {
1044 		_lastHandledPress = std::nullopt;
1045 	} else {
1046 		_lastHandledPress = _over;
1047 		switch (_over) {
1048 		case OverState::Close: _panel.widget()->close(); break;
1049 		case OverState::Enlarge: _closeAndContinue(); break;
1050 		case OverState::VolumeToggle: volumeToggled(); break;
1051 		case OverState::Other: playbackPauseResume(); break;
1052 		}
1053 	}
1054 }
1055 
handleDoubleClick(Qt::MouseButton button)1056 void Pip::handleDoubleClick(Qt::MouseButton button) {
1057 	if (_over != OverState::Other
1058 		|| !_lastHandledPress
1059 		|| *_lastHandledPress != _over) {
1060 		return;
1061 	}
1062 	playbackPauseResume(); // Un-click the first click.
1063 	_closeAndContinue();
1064 }
1065 
seekUpdate(QPoint position)1066 void Pip::seekUpdate(QPoint position) {
1067 	if (!_pressed || *_pressed != OverState::Playback) {
1068 		return;
1069 	}
1070 	const auto unbound = (position.x() - _playback.icon.x())
1071 		/ float64(_playback.icon.width());
1072 	const auto progress = std::clamp(unbound, 0., 1.);
1073 	seekProgress(progress);
1074 }
1075 
seekProgress(float64 value)1076 void Pip::seekProgress(float64 value) {
1077 	if (!_lastDurationMs) {
1078 		return;
1079 	}
1080 
1081 	_playbackProgress->setValue(value, false);
1082 
1083 	const auto positionMs = std::clamp(
1084 		static_cast<crl::time>(value * _lastDurationMs),
1085 		crl::time(0),
1086 		_lastDurationMs);
1087 	if (_seekPositionMs != positionMs) {
1088 		_seekPositionMs = positionMs;
1089 		if (!_instance.player().paused()
1090 			&& !_instance.player().finished()) {
1091 			_pausedBySeek = true;
1092 			playbackPauseResume();
1093 		}
1094 		updatePlaybackTexts(_seekPositionMs, _lastDurationMs, kMsInSecond);
1095 	}
1096 }
1097 
seekFinish(float64 value)1098 void Pip::seekFinish(float64 value) {
1099 	Expects(5 && _delegate->pipPlaybackSpeed() >= 0.5
1100 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1101 
1102 	if (!_lastDurationMs) {
1103 		return;
1104 	}
1105 
1106 	const auto positionMs = std::clamp(
1107 		static_cast<crl::time>(value * _lastDurationMs),
1108 		crl::time(0),
1109 		_lastDurationMs);
1110 	_seekPositionMs = -1;
1111 	_startPaused = !_pausedBySeek && !_instance.player().finished();
1112 	restartAtSeekPosition(positionMs);
1113 }
1114 
volumeChanged(float64 volume)1115 void Pip::volumeChanged(float64 volume) {
1116 	if (volume > 0.) {
1117 		_lastPositiveVolume = volume;
1118 	}
1119 	Player::mixer()->setVideoVolume(volume);
1120 	Core::App().settings().setVideoVolume(volume);
1121 	Core::App().saveSettingsDelayed();
1122 }
1123 
volumeToggled()1124 void Pip::volumeToggled() {
1125 	const auto volume = Core::App().settings().videoVolume();
1126 	volumeChanged(volume ? 0. : _lastPositiveVolume);
1127 	_panel.update();
1128 }
1129 
volumeControllerUpdate(QPoint position)1130 void Pip::volumeControllerUpdate(QPoint position) {
1131 	if (!_pressed || *_pressed != OverState::VolumeController) {
1132 		return;
1133 	}
1134 	const auto unbound = (position.x() - _volumeController.icon.x())
1135 		/ float64(_volumeController.icon.width());
1136 	const auto value = std::clamp(unbound, 0., 1.);
1137 	volumeChanged(value);
1138 	_panel.update();
1139 }
1140 
setupButtons()1141 void Pip::setupButtons() {
1142 	_close.state = OverState::Close;
1143 	_enlarge.state = OverState::Enlarge;
1144 	_playback.state = OverState::Playback;
1145 	_volumeToggle.state = OverState::VolumeToggle;
1146 	_volumeController.state = OverState::VolumeController;
1147 	_play.state = OverState::Other;
1148 	_panel.rp()->sizeValue(
1149 	) | rpl::map([=] {
1150 		return _panel.inner();
1151 	}) | rpl::start_with_next([=](QRect rect) {
1152 		const auto skip = st::pipControlSkip;
1153 		_close.area = QRect(
1154 			rect.x(),
1155 			rect.y(),
1156 			st::pipCloseIcon.width() + 2 * skip,
1157 			st::pipCloseIcon.height() + 2 * skip);
1158 		_enlarge.area = QRect(
1159 			_close.area.x() + _close.area.width(),
1160 			rect.y(),
1161 			st::pipEnlargeIcon.width() + 2 * skip,
1162 			st::pipEnlargeIcon.height() + 2 * skip);
1163 
1164 		const auto volumeSkip = st::pipPlaybackSkip;
1165 		const auto volumeHeight = 2 * volumeSkip + st::pipPlaybackWide;
1166 		const auto volumeToggleWidth = st::pipVolumeIcon0.width()
1167 			+ 2 * skip;
1168 		const auto volumeToggleHeight = st::pipVolumeIcon0.height()
1169 			+ 2 * skip;
1170 		const auto volumeWidth = (((st::mediaviewVolumeWidth + 2 * skip)
1171 			+ _close.area.width()
1172 			+ _enlarge.area.width()
1173 			+ volumeToggleWidth) < rect.width())
1174 				? st::mediaviewVolumeWidth
1175 				: 0;
1176 		_volumeController.area = QRect(
1177 			rect.x() + rect.width() - volumeWidth - 2 * volumeSkip,
1178 			rect.y() + (volumeToggleHeight - volumeHeight) / 2,
1179 			volumeWidth,
1180 			volumeHeight);
1181 		_volumeToggle.area = QRect(
1182 			_volumeController.area.x()
1183 				- st::pipVolumeIcon0.width()
1184 				- skip,
1185 			rect.y(),
1186 			volumeToggleWidth,
1187 			volumeToggleHeight);
1188 		if (!IsWindowControlsOnLeft()) {
1189 			_close.area.moveLeft(rect.x()
1190 				+ rect.width()
1191 				- (_close.area.x() - rect.x())
1192 				- _close.area.width());
1193 			_enlarge.area.moveLeft(rect.x()
1194 				+ rect.width()
1195 				- (_enlarge.area.x() - rect.x())
1196 				- _enlarge.area.width());
1197 			_volumeToggle.area.moveLeft(rect.x());
1198 			_volumeController.area.moveLeft(_volumeToggle.area.x()
1199 				+ _volumeToggle.area.width());
1200 		}
1201 		_close.icon = _close.area.marginsRemoved({ skip, skip, skip, skip });
1202 		_enlarge.icon = _enlarge.area.marginsRemoved(
1203 			{ skip, skip, skip, skip });
1204 		_volumeToggle.icon = _volumeToggle.area.marginsRemoved(
1205 			{ skip, skip, skip, skip });
1206 		_play.icon = QRect(
1207 			rect.x() + (rect.width() - st::pipPlayIcon.width()) / 2,
1208 			rect.y() + (rect.height() - st::pipPlayIcon.height()) / 2,
1209 			st::pipPlayIcon.width(),
1210 			st::pipPlayIcon.height());
1211 		const auto volumeArea = _volumeController.area;
1212 		_volumeController.icon = (volumeArea.width() > 2 * volumeSkip
1213 			&& volumeArea.height() > 2 * volumeSkip)
1214 			? volumeArea.marginsRemoved(
1215 				{ volumeSkip, volumeSkip, volumeSkip, volumeSkip })
1216 			: QRect();
1217 		const auto playbackSkip = st::pipPlaybackSkip;
1218 		const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWide;
1219 		_playback.area = QRect(
1220 			rect.x(),
1221 			rect.y() + rect.height() - playbackHeight,
1222 			rect.width(),
1223 			playbackHeight);
1224 		_playback.icon = _playback.area.marginsRemoved(
1225 			{ playbackSkip, playbackSkip, playbackSkip, playbackSkip });
1226 	}, _panel.rp()->lifetime());
1227 
1228 	_playbackProgress->setValueChangedCallback([=](
1229 			float64 value,
1230 			float64 receivedTill) {
1231 		_panel.widget()->update(_playback.area);
1232 	});
1233 }
1234 
saveGeometry()1235 void Pip::saveGeometry() {
1236 	_delegate->pipSaveGeometry(Serialize(_panel.countPosition()));
1237 }
1238 
updatePlayPauseResumeState(const Player::TrackState & state)1239 void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
1240 	auto showPause = Player::ShowPauseIcon(state.state);
1241 	if (showPause != _showPause) {
1242 		_showPause = showPause;
1243 		_panel.update();
1244 	}
1245 }
1246 
setupStreaming()1247 void Pip::setupStreaming() {
1248 	_instance.setPriority(kPipLoaderPriority);
1249 	_instance.lockPlayer();
1250 
1251 	_instance.player().updates(
1252 	) | rpl::start_with_next_error([=](Streaming::Update &&update) {
1253 		handleStreamingUpdate(std::move(update));
1254 	}, [=](Streaming::Error &&error) {
1255 		handleStreamingError(std::move(error));
1256 	}, _instance.lifetime());
1257 	updatePlaybackState();
1258 }
1259 
chooseRenderer(Ui::GL::Capabilities capabilities)1260 Ui::GL::ChosenRenderer Pip::chooseRenderer(
1261 		Ui::GL::Capabilities capabilities) {
1262 	const auto use = Platform::IsMac()
1263 		? true
1264 		: capabilities.transparency;
1265 	LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use)));
1266 	if (use) {
1267 		_opengl = true;
1268 		return {
1269 			.renderer = std::make_unique<RendererGL>(this),
1270 			.backend = Ui::GL::Backend::OpenGL,
1271 		};
1272 	}
1273 	return {
1274 		.renderer = std::make_unique<RendererSW>(this),
1275 		.backend = Ui::GL::Backend::Raster,
1276 	};
1277 }
1278 
paint(not_null<Renderer * > renderer) const1279 void Pip::paint(not_null<Renderer*> renderer) const {
1280 	const auto controlsShown = _controlsShown.value(
1281 		(_over != OverState::None) ? 1. : 0.);
1282 	auto geometry = ContentGeometry{
1283 		.inner = _panel.inner(),
1284 		.attached = (_panel.useTransparency()
1285 			? _panel.attached()
1286 			: RectPart::AllSides),
1287 		.fade = controlsShown,
1288 		.outer = _panel.widget()->size(),
1289 		.rotation = _rotation,
1290 		.videoRotation = _instance.info().video.rotation,
1291 		.useTransparency = _panel.useTransparency(),
1292 	};
1293 	if (canUseVideoFrame()) {
1294 		renderer->paintTransformedVideoFrame(geometry);
1295 		_instance.markFrameShown();
1296 	} else {
1297 		const auto content = staticContent();
1298 		if (_preparedCoverState == ThumbState::Cover) {
1299 			geometry.rotation += base::take(geometry.videoRotation);
1300 		}
1301 		renderer->paintTransformedStaticContent(staticContent(), geometry);
1302 	}
1303 	if (_instance.waitingShown()) {
1304 		renderer->paintRadialLoading(countRadialRect(), controlsShown);
1305 	}
1306 	if (controlsShown > 0) {
1307 		paintButtons(renderer, controlsShown);
1308 		paintPlayback(renderer, controlsShown);
1309 		paintVolumeController(renderer, controlsShown);
1310 	}
1311 }
1312 
paintButtons(not_null<Renderer * > renderer,float64 shown) const1313 void Pip::paintButtons(not_null<Renderer*> renderer, float64 shown) const {
1314 	const auto outer = _panel.widget()->width();
1315 	const auto drawOne = [&](
1316 			const Button &button,
1317 			const style::icon &icon,
1318 			const style::icon &iconOver) {
1319 		renderer->paintButton(
1320 			button,
1321 			outer,
1322 			shown,
1323 			activeValue(button),
1324 			icon,
1325 			iconOver);
1326 	};
1327 
1328 	renderer->paintButtonsStart();
1329 	drawOne(
1330 		_play,
1331 		_showPause ? st::pipPauseIcon : st::pipPlayIcon,
1332 		_showPause ? st::pipPauseIconOver : st::pipPlayIconOver);
1333 	drawOne(_close, st::pipCloseIcon, st::pipCloseIconOver);
1334 	drawOne(_enlarge, st::pipEnlargeIcon, st::pipEnlargeIconOver);
1335 	const auto volume = Core::App().settings().videoVolume();
1336 	if (volume <= 0.) {
1337 		drawOne(
1338 			_volumeToggle,
1339 			st::pipVolumeIcon0,
1340 			st::pipVolumeIcon0Over);
1341 	} else if (volume < 1 / 2.) {
1342 		drawOne(
1343 			_volumeToggle,
1344 			st::pipVolumeIcon1,
1345 			st::pipVolumeIcon1Over);
1346 	} else {
1347 		drawOne(
1348 			_volumeToggle,
1349 			st::pipVolumeIcon2,
1350 			st::pipVolumeIcon2Over);
1351 	}
1352 }
1353 
paintPlayback(not_null<Renderer * > renderer,float64 shown) const1354 void Pip::paintPlayback(not_null<Renderer*> renderer, float64 shown) const {
1355 	const auto outer = QRect(
1356 		_playback.icon.x(),
1357 		_playback.icon.y() - st::pipPlaybackFont->height,
1358 		_playback.icon.width(),
1359 		st::pipPlaybackFont->height + _playback.icon.height());
1360 	renderer->paintPlayback(outer, shown);
1361 }
1362 
paintPlaybackContent(QPainter & p,QRect outer,float64 shown) const1363 void Pip::paintPlaybackContent(
1364 		QPainter &p,
1365 		QRect outer,
1366 		float64 shown) const {
1367 	p.setOpacity(shown);
1368 	paintPlaybackProgress(p, outer);
1369 	paintPlaybackTexts(p, outer);
1370 }
1371 
paintPlaybackProgress(QPainter & p,QRect outer) const1372 void Pip::paintPlaybackProgress(QPainter &p, QRect outer) const {
1373 	const auto radius = _playback.icon.height() / 2;
1374 	const auto progress = _playbackProgress->value();
1375 	const auto active = activeValue(_playback);
1376 	const auto height = anim::interpolate(
1377 		st::pipPlaybackWidth,
1378 		_playback.icon.height(),
1379 		active);
1380 	const auto rect = QRect(
1381 		outer.x(),
1382 		(outer.y()
1383 			+ st::pipPlaybackFont->height
1384 			+ _playback.icon.height()
1385 			- height),
1386 		outer.width(),
1387 		height);
1388 
1389 	paintProgressBar(p, rect, progress, radius, active);
1390 }
1391 
paintProgressBar(QPainter & p,const QRect & rect,float64 progress,int radius,float64 active) const1392 void Pip::paintProgressBar(
1393 		QPainter &p,
1394 		const QRect &rect,
1395 		float64 progress,
1396 		int radius,
1397 		float64 active) const {
1398 	const auto done = int(base::SafeRound(rect.width() * progress));
1399 	PainterHighQualityEnabler hq(p);
1400 	p.setPen(Qt::NoPen);
1401 	if (done > 0) {
1402 		p.setBrush(anim::brush(
1403 			st::mediaviewPipControlsFg,
1404 			st::mediaviewPipPlaybackActive,
1405 			active));
1406 		p.setClipRect(rect.x(), rect.y(), done, rect.height());
1407 		p.drawRoundedRect(
1408 			rect.x(),
1409 			rect.y(),
1410 			std::min(done + radius, rect.width()),
1411 			rect.height(),
1412 			radius,
1413 			radius);
1414 	}
1415 	if (done < rect.width()) {
1416 		const auto from = std::max(rect.x() + done - radius, rect.x());
1417 		p.setBrush(st::mediaviewPipPlaybackInactive);
1418 		p.setClipRect(
1419 			rect.x() + done,
1420 			rect.y(),
1421 			rect.width() - done,
1422 			rect.height());
1423 		p.drawRoundedRect(
1424 			from,
1425 			rect.y(),
1426 			rect.x() + rect.width() - from,
1427 			rect.height(),
1428 			radius,
1429 			radius);
1430 	}
1431 	p.setClipping(false);
1432 }
1433 
paintPlaybackTexts(QPainter & p,QRect outer) const1434 void Pip::paintPlaybackTexts(QPainter &p, QRect outer) const {
1435 	const auto left = outer.x()
1436 		- _playback.icon.x()
1437 		+ _playback.area.x()
1438 		+ st::pipPlaybackTextSkip;
1439 	const auto right = outer.x()
1440 		- _playback.icon.x()
1441 		+ _playback.area.x()
1442 		+ _playback.area.width()
1443 		- st::pipPlaybackTextSkip;
1444 	const auto top = outer.y() + st::pipPlaybackFont->ascent;
1445 
1446 	p.setFont(st::pipPlaybackFont);
1447 	p.setPen(st::mediaviewPipControlsFgOver);
1448 	p.drawText(left, top, _timeAlready);
1449 	p.drawText(right - _timeLeftWidth, top, _timeLeft);
1450 }
1451 
paintVolumeController(not_null<Renderer * > renderer,float64 shown) const1452 void Pip::paintVolumeController(
1453 		not_null<Renderer*> renderer,
1454 		float64 shown) const {
1455 	if (_volumeController.icon.isEmpty()) {
1456 		return;
1457 	}
1458 	renderer->paintVolumeController(_volumeController.icon, shown);
1459 }
1460 
paintVolumeControllerContent(QPainter & p,QRect outer,float64 shown) const1461 void Pip::paintVolumeControllerContent(
1462 		QPainter &p,
1463 		QRect outer,
1464 		float64 shown) const {
1465 	p.setOpacity(shown);
1466 
1467 	const auto radius = _volumeController.icon.height() / 2;
1468 	const auto volume = Core::App().settings().videoVolume();
1469 	const auto active = activeValue(_volumeController);
1470 	const auto height = anim::interpolate(
1471 		st::pipPlaybackWidth,
1472 		_volumeController.icon.height(),
1473 		active);
1474 	const auto rect = QRect(
1475 		outer.x(),
1476 		outer.y() + radius - height / 2,
1477 		outer.width(),
1478 		height);
1479 
1480 	paintProgressBar(p, rect, volume, radius, active);
1481 }
1482 
handleStreamingUpdate(Streaming::Update && update)1483 void Pip::handleStreamingUpdate(Streaming::Update &&update) {
1484 	using namespace Streaming;
1485 
1486 	v::match(update.data, [&](Information &update) {
1487 		_panel.setAspectRatio(
1488 			FlipSizeByRotation(update.video.size, _rotation));
1489 	}, [&](const PreloadedVideo &update) {
1490 		updatePlaybackState();
1491 	}, [&](const UpdateVideo &update) {
1492 		_panel.update();
1493 		Core::App().updateNonIdle();
1494 		updatePlaybackState();
1495 	}, [&](const PreloadedAudio &update) {
1496 		updatePlaybackState();
1497 	}, [&](const UpdateAudio &update) {
1498 		updatePlaybackState();
1499 	}, [&](WaitingForData) {
1500 	}, [&](MutedByOther) {
1501 	}, [&](Finished) {
1502 		updatePlaybackState();
1503 	});
1504 }
1505 
updatePlaybackState()1506 void Pip::updatePlaybackState() {
1507 	const auto state = _instance.player().prepareLegacyState();
1508 	updatePlayPauseResumeState(state);
1509 	if (state.position == kTimeUnknown
1510 		|| state.length == kTimeUnknown
1511 		|| _pausedBySeek) {
1512 		return;
1513 	}
1514 	_playbackProgress->updateState(state);
1515 
1516 	qint64 position = 0;
1517 	if (Player::IsStoppedAtEnd(state.state)) {
1518 		position = state.length;
1519 	} else if (!Player::IsStoppedOrStopping(state.state)) {
1520 		position = state.position;
1521 	} else {
1522 		position = 0;
1523 	}
1524 	const auto playFrequency = state.frequency;
1525 	_lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
1526 
1527 	if (_seekPositionMs < 0) {
1528 		updatePlaybackTexts(position, state.length, playFrequency);
1529 	}
1530 }
1531 
updatePlaybackTexts(int64 position,int64 length,int64 frequency)1532 void Pip::updatePlaybackTexts(
1533 		int64 position,
1534 		int64 length,
1535 		int64 frequency) {
1536 	const auto playAlready = position / frequency;
1537 	const auto playLeft = (length / frequency) - playAlready;
1538 	const auto already = Ui::FormatDurationText(playAlready);
1539 	const auto minus = QChar(8722);
1540 	const auto left = minus + Ui::FormatDurationText(playLeft);
1541 	if (_timeAlready == already && _timeLeft == left) {
1542 		return;
1543 	}
1544 	_timeAlready = already;
1545 	_timeLeft = left;
1546 	_timeLeftWidth = st::pipPlaybackFont->width(_timeLeft);
1547 	_panel.widget()->update(QRect(
1548 		_playback.area.x(),
1549 		_playback.icon.y() - st::pipPlaybackFont->height,
1550 		_playback.area.width(),
1551 		st::pipPlaybackFont->height));
1552 }
1553 
handleStreamingError(Streaming::Error && error)1554 void Pip::handleStreamingError(Streaming::Error &&error) {
1555 	_panel.widget()->close();
1556 }
1557 
playbackPauseResume()1558 void Pip::playbackPauseResume() {
1559 	if (_instance.player().failed()) {
1560 		_panel.widget()->close();
1561 	} else if (_instance.player().finished()
1562 		|| !_instance.player().active()) {
1563 		_startPaused = false;
1564 		restartAtSeekPosition(0);
1565 	} else if (_instance.player().paused()) {
1566 		_instance.resume();
1567 		updatePlaybackState();
1568 	} else {
1569 		_instance.pause();
1570 		updatePlaybackState();
1571 	}
1572 }
1573 
restartAtSeekPosition(crl::time position)1574 void Pip::restartAtSeekPosition(crl::time position) {
1575 	Expects(6 && _delegate->pipPlaybackSpeed() >= 0.5
1576 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1577 
1578 	if (!_instance.info().video.cover.isNull()) {
1579 		_preparedCoverStorage = QImage();
1580 		_preparedCoverState = ThumbState::Empty;
1581 		_instance.saveFrameToCover();
1582 	}
1583 
1584 	Assert(7 && _delegate->pipPlaybackSpeed() >= 0.5
1585 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1586 
1587 	auto options = Streaming::PlaybackOptions();
1588 	options.position = position;
1589 	options.audioId = _instance.player().prepareLegacyState().id;
1590 
1591 	Assert(8 && _delegate->pipPlaybackSpeed() >= 0.5
1592 		&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
1593 
1594 	options.speed = _delegate->pipPlaybackSpeed();
1595 
1596 	Assert(9 && options.speed >= 0.5
1597 		&& options.speed <= 2.); // Debugging strange crash.
1598 
1599 	_instance.play(options);
1600 	if (_startPaused) {
1601 		_instance.pause();
1602 	}
1603 	_pausedBySeek = false;
1604 	updatePlaybackState();
1605 }
1606 
canUseVideoFrame() const1607 bool Pip::canUseVideoFrame() const {
1608 	return _instance.player().ready()
1609 		&& !_instance.info().video.cover.isNull();
1610 }
1611 
videoFrame(const FrameRequest & request) const1612 QImage Pip::videoFrame(const FrameRequest &request) const {
1613 	Expects(canUseVideoFrame());
1614 
1615 	return _instance.frame(request);
1616 }
1617 
videoFrameWithInfo() const1618 Streaming::FrameWithInfo Pip::videoFrameWithInfo() const {
1619 	Expects(canUseVideoFrame());
1620 
1621 	return _instance.frameWithInfo();
1622 }
1623 
staticContent() const1624 QImage Pip::staticContent() const {
1625 	const auto &cover = _instance.info().video.cover;
1626 	const auto media = _data->activeMediaView();
1627 	const auto use = media
1628 		? media
1629 		: _data->inlineThumbnailBytes().isEmpty()
1630 		? nullptr
1631 		: _data->createMediaView();
1632 	if (use) {
1633 		use->goodThumbnailWanted();
1634 	}
1635 	const auto good = use ? use->goodThumbnail() : nullptr;
1636 	const auto thumb = use ? use->thumbnail() : nullptr;
1637 	const auto blurred = use ? use->thumbnailInline() : nullptr;
1638 
1639 	const auto state = !cover.isNull()
1640 		? ThumbState::Cover
1641 		: good
1642 		? ThumbState::Good
1643 		: thumb
1644 		? ThumbState::Thumb
1645 		: blurred
1646 		? ThumbState::Inline
1647 		: ThumbState::Empty;
1648 	if (!_preparedCoverStorage.isNull() && _preparedCoverState >= state) {
1649 		return _preparedCoverStorage;
1650 	}
1651 	_preparedCoverState = state;
1652 	if (state == ThumbState::Cover) {
1653 		_preparedCoverStorage = _instance.info().video.cover;
1654 	} else {
1655 		_preparedCoverStorage = (good
1656 			? good
1657 			: thumb
1658 			? thumb
1659 			: blurred
1660 			? blurred
1661 			: Image::BlankMedia().get())->original();
1662 		if (!good) {
1663 			_preparedCoverStorage = Images::prepareBlur(
1664 				std::move(_preparedCoverStorage));
1665 		}
1666 	}
1667 	return _preparedCoverStorage;
1668 }
1669 
paintRadialLoadingContent(QPainter & p,const QRect & inner,QColor fg) const1670 void Pip::paintRadialLoadingContent(
1671 		QPainter &p,
1672 		const QRect &inner,
1673 		QColor fg) const {
1674 	const auto arc = inner.marginsRemoved(QMargins(
1675 		st::radialLine,
1676 		st::radialLine,
1677 		st::radialLine,
1678 		st::radialLine));
1679 	p.setOpacity(_instance.waitingOpacity());
1680 	p.setPen(Qt::NoPen);
1681 	p.setBrush(st::radialBg);
1682 	{
1683 		PainterHighQualityEnabler hq(p);
1684 		p.drawEllipse(inner);
1685 	}
1686 	p.setOpacity(1.);
1687 	Ui::InfiniteRadialAnimation::Draw(
1688 		p,
1689 		_instance.waitingState(),
1690 		arc.topLeft(),
1691 		arc.size(),
1692 		_panel.widget()->width(),
1693 		fg,
1694 		st::radialLine);
1695 }
1696 
countRadialRect() const1697 QRect Pip::countRadialRect() const {
1698 	const auto outer = _panel.inner();
1699 	return {
1700 		outer.x() + (outer.width() - st::radialSize.width()) / 2,
1701 		outer.y() + (outer.height() - st::radialSize.height()) / 2,
1702 		st::radialSize.width(),
1703 		st::radialSize.height()
1704 	};
1705 }
1706 
computeState(QPoint position) const1707 Pip::OverState Pip::computeState(QPoint position) const {
1708 	if (!_panel.inner().contains(position)) {
1709 		return OverState::None;
1710 	} else if (_close.area.contains(position)) {
1711 		return OverState::Close;
1712 	} else if (_enlarge.area.contains(position)) {
1713 		return OverState::Enlarge;
1714 	} else if (_playback.area.contains(position)) {
1715 		return OverState::Playback;
1716 	} else if (_volumeToggle.area.contains(position)) {
1717 		return OverState::VolumeToggle;
1718 	} else if (_volumeController.area.contains(position)) {
1719 		return OverState::VolumeController;
1720 	} else {
1721 		return OverState::Other;
1722 	}
1723 }
1724 
waitingAnimationCallback()1725 void Pip::waitingAnimationCallback() {
1726 	_panel.widget()->update(countRadialRect());
1727 }
1728 
1729 } // namespace View
1730 } // namespace Media
1731