2020-01-03 11:18:38 +00:00
|
|
|
/*
|
|
|
|
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_pip.h"
|
|
|
|
|
|
|
|
#include "media/streaming/media_streaming_player.h"
|
|
|
|
#include "media/streaming/media_streaming_document.h"
|
2020-01-06 17:54:11 +00:00
|
|
|
#include "media/streaming/media_streaming_utility.h"
|
2020-01-28 11:48:29 +00:00
|
|
|
#include "media/audio/media_audio.h"
|
2020-01-28 12:41:08 +00:00
|
|
|
#include "data/data_document.h"
|
2020-01-03 11:18:38 +00:00
|
|
|
#include "core/application.h"
|
2020-01-07 12:16:28 +00:00
|
|
|
#include "ui/platform/ui_platform_utility.h"
|
2020-01-28 11:48:29 +00:00
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/wrap/fade_wrap.h"
|
2020-01-29 13:16:22 +00:00
|
|
|
#include "ui/widgets/shadow.h"
|
2020-01-03 11:18:38 +00:00
|
|
|
#include "window/window_controller.h"
|
|
|
|
#include "styles/style_window.h"
|
2020-01-03 13:22:59 +00:00
|
|
|
#include "styles/style_mediaview.h"
|
2020-01-28 11:48:29 +00:00
|
|
|
#include "styles/style_layers.h" // st::boxTitleClose
|
2020-01-29 13:16:22 +00:00
|
|
|
#include "styles/style_calls.h" // st::callShadow
|
2020-01-03 11:18:38 +00:00
|
|
|
|
|
|
|
#include <QtGui/QWindow>
|
|
|
|
#include <QtGui/QScreen>
|
|
|
|
#include <QtWidgets/QApplication>
|
|
|
|
|
|
|
|
namespace Media {
|
|
|
|
namespace View {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr auto kPipLoaderPriority = 2;
|
2020-01-29 08:58:07 +00:00
|
|
|
constexpr auto kSaveGeometryTimeout = crl::time(1000);
|
2020-01-03 11:18:38 +00:00
|
|
|
|
2020-01-03 13:22:59 +00:00
|
|
|
[[nodiscard]] QRect ScreenFromPosition(QPoint point) {
|
|
|
|
const auto screen = QGuiApplication::screenAt(point);
|
|
|
|
const auto use = screen ? screen : QGuiApplication::primaryScreen();
|
|
|
|
return use
|
|
|
|
? use->availableGeometry()
|
|
|
|
: QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight);
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
[[nodiscard]] QPoint ClampToEdges(QRect screen, QRect inner) {
|
|
|
|
const auto skip = st::pipBorderSkip;
|
|
|
|
const auto area = st::pipBorderSnapArea;
|
|
|
|
const auto sleft = screen.x() + skip;
|
|
|
|
const auto stop = screen.y() + skip;
|
|
|
|
const auto sright = screen.x() + screen.width() - skip;
|
|
|
|
const auto sbottom = screen.y() + screen.height() - skip;
|
|
|
|
const auto ileft = inner.x();
|
|
|
|
const auto itop = inner.y();
|
|
|
|
const auto iright = inner.x() + inner.width();
|
|
|
|
const auto ibottom = inner.y() + inner.height();
|
|
|
|
auto shiftx = 0;
|
|
|
|
auto shifty = 0;
|
|
|
|
if (iright + shiftx >= sright - area && iright + shiftx < sright + area) {
|
|
|
|
shiftx += (sright - iright);
|
|
|
|
}
|
|
|
|
if (ileft + shiftx >= sleft - area && ileft + shiftx < sleft + area) {
|
|
|
|
shiftx += (sleft - ileft);
|
|
|
|
}
|
|
|
|
if (ibottom + shifty >= sbottom - area && ibottom + shifty < sbottom + area) {
|
|
|
|
shifty += (sbottom - ibottom);
|
|
|
|
}
|
|
|
|
if (itop + shifty >= stop - area && itop + shifty < stop + area) {
|
|
|
|
shifty += (stop - itop);
|
|
|
|
}
|
|
|
|
return inner.topLeft() + QPoint(shiftx, shifty);
|
|
|
|
}
|
|
|
|
|
2020-01-07 11:41:42 +00:00
|
|
|
[[nodiscard]] QRect Transformed(
|
|
|
|
QRect original,
|
|
|
|
QSize minimalSize,
|
|
|
|
QSize maximalSize,
|
|
|
|
QPoint delta,
|
|
|
|
RectPart by) {
|
2020-01-06 19:50:03 +00:00
|
|
|
const auto width = original.width();
|
|
|
|
const auto height = original.height();
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto x1 = width - minimalSize.width();
|
|
|
|
const auto x2 = maximalSize.width() - width;
|
|
|
|
const auto y1 = height - minimalSize.height();
|
|
|
|
const auto y2 = maximalSize.height() - height;
|
2020-01-06 19:50:03 +00:00
|
|
|
switch (by) {
|
|
|
|
case RectPart::Center: return original.translated(delta);
|
|
|
|
case RectPart::TopLeft:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
|
|
|
|
original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::TopRight:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
|
|
|
|
original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::BottomRight:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setHeight(
|
|
|
|
original.height() + std::clamp(delta.y(), -y1, y2));
|
|
|
|
original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::BottomLeft:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setHeight(
|
|
|
|
original.height() + std::clamp(delta.y(), -y1, y2));
|
|
|
|
original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::Left:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::Top:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::Right:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
case RectPart::Bottom:
|
2020-01-07 11:41:42 +00:00
|
|
|
original.setHeight(
|
|
|
|
original.height() + std::clamp(delta.y(), -y1, y2));
|
2020-01-06 19:50:03 +00:00
|
|
|
return original;
|
|
|
|
}
|
|
|
|
return original;
|
|
|
|
Unexpected("RectPart in PiP Transformed.");
|
|
|
|
}
|
|
|
|
|
2020-01-07 11:41:42 +00:00
|
|
|
[[nodiscard]] QRect Constrained(
|
|
|
|
QRect original,
|
|
|
|
QSize minimalSize,
|
|
|
|
QSize maximalSize,
|
|
|
|
QSize ratio,
|
2020-01-29 13:16:22 +00:00
|
|
|
RectPart by,
|
|
|
|
RectParts attached) {
|
2020-01-06 19:50:03 +00:00
|
|
|
if (by == RectPart::Center) {
|
|
|
|
return original;
|
|
|
|
} else if (!original.width() && !original.height()) {
|
|
|
|
return QRect(original.topLeft(), ratio);
|
|
|
|
}
|
|
|
|
const auto widthLarger = (original.width() * ratio.height())
|
|
|
|
> (original.height() * ratio.width());
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto desiredSize = ratio.scaled(
|
2020-01-06 19:50:03 +00:00
|
|
|
original.size(),
|
|
|
|
(((RectParts(by) & RectPart::AllCorners)
|
|
|
|
|| ((by == RectPart::Top || by == RectPart::Bottom)
|
|
|
|
&& widthLarger)
|
|
|
|
|| ((by == RectPart::Left || by == RectPart::Right)
|
|
|
|
&& !widthLarger))
|
|
|
|
? Qt::KeepAspectRatio
|
|
|
|
: Qt::KeepAspectRatioByExpanding));
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto newSize = QSize(
|
|
|
|
std::clamp(
|
|
|
|
desiredSize.width(),
|
|
|
|
minimalSize.width(),
|
|
|
|
maximalSize.width()),
|
|
|
|
std::clamp(
|
|
|
|
desiredSize.height(),
|
|
|
|
minimalSize.height(),
|
|
|
|
maximalSize.height()));
|
2020-01-06 19:50:03 +00:00
|
|
|
switch (by) {
|
|
|
|
case RectPart::TopLeft:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
|
|
|
original.width() - newSize.width(),
|
|
|
|
original.height() - newSize.height()),
|
|
|
|
newSize);
|
|
|
|
case RectPart::TopRight:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
|
|
|
0,
|
|
|
|
original.height() - newSize.height()),
|
|
|
|
newSize);
|
|
|
|
case RectPart::BottomRight:
|
|
|
|
return QRect(original.topLeft(), newSize);
|
|
|
|
case RectPart::BottomLeft:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
|
|
|
original.width() - newSize.width(),
|
|
|
|
0),
|
|
|
|
newSize);
|
|
|
|
case RectPart::Left:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
|
|
|
(original.width() - newSize.width()),
|
2020-01-29 13:16:22 +00:00
|
|
|
((attached & RectPart::Top)
|
|
|
|
? 0
|
|
|
|
: (attached & RectPart::Bottom)
|
|
|
|
? (original.height() - newSize.height())
|
|
|
|
: (original.height() - newSize.height()) / 2)),
|
2020-01-06 19:50:03 +00:00
|
|
|
newSize);
|
|
|
|
case RectPart::Top:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
2020-01-29 13:16:22 +00:00
|
|
|
((attached & RectPart::Left)
|
|
|
|
? 0
|
|
|
|
: (attached & RectPart::Right)
|
|
|
|
? (original.width() - newSize.width())
|
|
|
|
: (original.width() - newSize.width()) / 2),
|
2020-01-06 19:50:03 +00:00
|
|
|
0),
|
|
|
|
newSize);
|
|
|
|
case RectPart::Right:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
|
|
|
0,
|
2020-01-29 13:16:22 +00:00
|
|
|
((attached & RectPart::Top)
|
|
|
|
? 0
|
|
|
|
: (attached & RectPart::Bottom)
|
|
|
|
? (original.height() - newSize.height())
|
|
|
|
: (original.height() - newSize.height()) / 2)),
|
2020-01-06 19:50:03 +00:00
|
|
|
newSize);
|
|
|
|
case RectPart::Bottom:
|
|
|
|
return QRect(
|
|
|
|
original.topLeft() + QPoint(
|
2020-01-29 13:16:22 +00:00
|
|
|
((attached & RectPart::Left)
|
|
|
|
? 0
|
|
|
|
: (attached & RectPart::Right)
|
|
|
|
? (original.width() - newSize.width())
|
|
|
|
: (original.width() - newSize.width()) / 2),
|
2020-01-06 19:50:03 +00:00
|
|
|
(original.height() - newSize.height())),
|
|
|
|
newSize);
|
|
|
|
}
|
|
|
|
Unexpected("RectPart in PiP Constrained.");
|
|
|
|
}
|
|
|
|
|
2020-01-29 08:58:07 +00:00
|
|
|
[[nodiscard]] QByteArray Serialize(const PipPanel::Position &position) {
|
|
|
|
auto result = QByteArray();
|
|
|
|
auto stream = QDataStream(&result, QIODevice::WriteOnly);
|
|
|
|
stream.setVersion(QDataStream::Qt_5_3);
|
|
|
|
stream
|
|
|
|
<< qint32(position.attached.value())
|
|
|
|
<< qint32(position.snapped.value())
|
|
|
|
<< position.screen
|
|
|
|
<< position.geometry;
|
|
|
|
stream.device()->close();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] PipPanel::Position Deserialize(const QByteArray &data) {
|
|
|
|
auto stream = QDataStream(data);
|
|
|
|
auto result = PipPanel::Position();
|
|
|
|
auto attached = qint32(0);
|
|
|
|
auto snapped = qint32(0);
|
|
|
|
stream >> attached >> snapped >> result.screen >> result.geometry;
|
|
|
|
if (stream.status() != QDataStream::Ok) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
result.attached = RectParts::from_raw(attached);
|
|
|
|
result.snapped = RectParts::from_raw(snapped);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-01-03 11:18:38 +00:00
|
|
|
} // namespace
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
PipPanel::PipPanel(
|
|
|
|
QWidget *parent,
|
2020-01-06 19:50:03 +00:00
|
|
|
Fn<void(QPainter&, FrameRequest)> paint)
|
2020-01-06 17:54:11 +00:00
|
|
|
: _parent(parent)
|
|
|
|
, _paint(std::move(paint)) {
|
|
|
|
setWindowFlags(Qt::Tool
|
|
|
|
| Qt::WindowStaysOnTopHint
|
2020-01-07 12:16:28 +00:00
|
|
|
| Qt::FramelessWindowHint
|
|
|
|
| Qt::WindowDoesNotAcceptFocus);
|
2020-01-06 17:54:11 +00:00
|
|
|
setAttribute(Qt::WA_ShowWithoutActivating);
|
2020-01-28 17:10:27 +00:00
|
|
|
setAttribute(Qt::WA_MacAlwaysShowToolWindow);
|
2020-01-29 13:16:22 +00:00
|
|
|
setAttribute(Qt::WA_NoSystemBackground);
|
|
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
2020-01-28 17:10:27 +00:00
|
|
|
Ui::Platform::InitOnTopPanel(this);
|
2020-01-06 19:50:03 +00:00
|
|
|
setMouseTracking(true);
|
2020-01-06 17:54:11 +00:00
|
|
|
resize(0, 0);
|
2020-01-07 12:16:28 +00:00
|
|
|
show();
|
2020-01-29 13:16:22 +00:00
|
|
|
//Ui::Platform::IgnoreAllActivation(this);
|
2020-01-06 17:54:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PipPanel::setAspectRatio(QSize ratio) {
|
|
|
|
if (_ratio == ratio) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_ratio = ratio;
|
|
|
|
if (_ratio.isEmpty()) {
|
|
|
|
_ratio = QSize(1, 1);
|
|
|
|
}
|
|
|
|
if (!size().isEmpty()) {
|
|
|
|
setPosition(countPosition());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PipPanel::setPosition(Position position) {
|
|
|
|
if (!position.screen.isEmpty()) {
|
|
|
|
for (const auto screen : QApplication::screens()) {
|
|
|
|
if (screen->geometry() == position.screen) {
|
|
|
|
setPositionOnScreen(position, screen->availableGeometry());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setPositionDefault();
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
rpl::producer<> PipPanel::saveGeometryRequests() const {
|
|
|
|
return _saveGeometryRequests.events();
|
|
|
|
}
|
|
|
|
|
2020-01-06 19:50:03 +00:00
|
|
|
QScreen *PipPanel::myScreen() const {
|
|
|
|
return windowHandle() ? windowHandle()->screen() : nullptr;
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
PipPanel::Position PipPanel::countPosition() const {
|
2020-01-06 19:50:03 +00:00
|
|
|
const auto screen = myScreen();
|
2020-01-06 17:54:11 +00:00
|
|
|
if (!screen) {
|
|
|
|
return Position();
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
2020-01-06 17:54:11 +00:00
|
|
|
auto result = Position();
|
|
|
|
result.screen = screen->geometry();
|
2020-01-29 13:16:22 +00:00
|
|
|
result.geometry = geometry().marginsRemoved(_padding);
|
2020-01-06 17:54:11 +00:00
|
|
|
const auto available = screen->availableGeometry();
|
|
|
|
const auto skip = st::pipBorderSkip;
|
|
|
|
const auto left = result.geometry.x();
|
|
|
|
const auto right = left + result.geometry.width();
|
|
|
|
const auto top = result.geometry.y();
|
|
|
|
const auto bottom = top + result.geometry.height();
|
2020-01-29 13:16:22 +00:00
|
|
|
if (!_dragState || *_dragState != RectPart::Center) {
|
|
|
|
if (left == available.x()) {
|
|
|
|
result.attached |= RectPart::Left;
|
|
|
|
} else if (right == available.x() + available.width()) {
|
|
|
|
result.attached |= RectPart::Right;
|
|
|
|
} else if (left == available.x() + skip) {
|
|
|
|
result.snapped |= RectPart::Left;
|
|
|
|
} else if (right == available.x() + available.width() - skip) {
|
|
|
|
result.snapped |= RectPart::Right;
|
|
|
|
}
|
|
|
|
if (top == available.y()) {
|
|
|
|
result.attached |= RectPart::Top;
|
|
|
|
} else if (bottom == available.y() + available.height()) {
|
|
|
|
result.attached |= RectPart::Bottom;
|
|
|
|
} else if (top == available.y() + skip) {
|
|
|
|
result.snapped |= RectPart::Top;
|
|
|
|
} else if (bottom == available.y() + available.height() - skip) {
|
|
|
|
result.snapped |= RectPart::Bottom;
|
|
|
|
}
|
2020-01-06 17:54:11 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-01-03 11:18:38 +00:00
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::setPositionDefault() {
|
2020-01-03 11:18:38 +00:00
|
|
|
const auto widgetScreen = [&](auto &&widget) -> QScreen* {
|
|
|
|
if (auto handle = widget ? widget->windowHandle() : nullptr) {
|
|
|
|
return handle->screen();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
};
|
2020-01-06 17:54:11 +00:00
|
|
|
const auto parentScreen = widgetScreen(_parent);
|
2020-01-03 11:18:38 +00:00
|
|
|
const auto myScreen = widgetScreen(this);
|
2020-01-06 17:54:11 +00:00
|
|
|
if (parentScreen && myScreen && myScreen != parentScreen) {
|
|
|
|
windowHandle()->setScreen(parentScreen);
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
2020-01-06 17:54:11 +00:00
|
|
|
const auto screen = parentScreen
|
|
|
|
? parentScreen
|
2020-01-03 11:18:38 +00:00
|
|
|
: QGuiApplication::primaryScreen();
|
2020-01-06 17:54:11 +00:00
|
|
|
auto position = Position();
|
|
|
|
position.snapped = RectPart::Top | RectPart::Left;
|
|
|
|
position.screen = screen->geometry();
|
|
|
|
position.geometry = QRect(0, 0, st::pipDefaultSize, st::pipDefaultSize);
|
|
|
|
setPositionOnScreen(position, screen->availableGeometry());
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::setPositionOnScreen(Position position, QRect available) {
|
|
|
|
const auto screen = available;
|
|
|
|
const auto requestedSize = position.geometry.size();
|
|
|
|
const auto max = std::max(requestedSize.width(), requestedSize.height());
|
|
|
|
|
|
|
|
// Apply aspect ratio.
|
|
|
|
const auto scaled = (_ratio.width() > _ratio.height())
|
|
|
|
? QSize(max, max * _ratio.height() / _ratio.width())
|
|
|
|
: QSize(max * _ratio.width() / _ratio.height(), max);
|
|
|
|
|
|
|
|
// At least one side should not be greater than half of screen size.
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto byWidth = (scaled.width() * screen.height())
|
2020-01-06 17:54:11 +00:00
|
|
|
> (scaled.height() * screen.width());
|
|
|
|
const auto fit = QSize(screen.width() / 2, screen.height() / 2);
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto normalized = (byWidth && scaled.width() > fit.width())
|
|
|
|
? QSize(fit.width(), fit.width() * scaled.height() / scaled.width())
|
|
|
|
: (!byWidth && scaled.height() > fit.height())
|
2020-01-06 17:54:11 +00:00
|
|
|
? QSize(
|
|
|
|
fit.height() * scaled.width() / scaled.height(),
|
|
|
|
fit.height())
|
|
|
|
: scaled;
|
|
|
|
|
|
|
|
// Apply minimal size.
|
2020-01-06 19:50:03 +00:00
|
|
|
const auto min = st::pipMinimalSize;
|
|
|
|
const auto minimalSize = (_ratio.width() > _ratio.height())
|
|
|
|
? QSize(min * _ratio.width() / _ratio.height(), min)
|
|
|
|
: QSize(min, min * _ratio.height() / _ratio.width());
|
2020-01-06 17:54:11 +00:00
|
|
|
const auto size = QSize(
|
2020-01-06 19:50:03 +00:00
|
|
|
std::max(normalized.width(), minimalSize.width()),
|
|
|
|
std::max(normalized.height(), minimalSize.height()));
|
2020-01-06 17:54:11 +00:00
|
|
|
|
|
|
|
// Apply left-right screen borders.
|
|
|
|
const auto skip = st::pipBorderSkip;
|
|
|
|
const auto inner = screen.marginsRemoved({ skip, skip, skip, skip });
|
|
|
|
auto geometry = QRect(position.geometry.topLeft(), size);
|
|
|
|
if ((position.attached & RectPart::Left)
|
|
|
|
|| (geometry.x() < screen.x())) {
|
|
|
|
geometry.moveLeft(screen.x());
|
|
|
|
} else if ((position.attached & RectPart::Right)
|
|
|
|
|| (geometry.x() + geometry.width() > screen.x() + screen.width())) {
|
|
|
|
geometry.moveLeft(screen.x() + screen.width() - geometry.width());
|
|
|
|
} else if (position.snapped & RectPart::Left) {
|
|
|
|
geometry.moveLeft(inner.x());
|
|
|
|
} else if (position.snapped & RectPart::Right) {
|
|
|
|
geometry.moveLeft(inner.x() + inner.width() - geometry.width());
|
|
|
|
}
|
2020-01-03 11:18:38 +00:00
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
// Apply top-bottom screen borders.
|
|
|
|
if ((position.attached & RectPart::Top) || (geometry.y() < screen.y())) {
|
|
|
|
geometry.moveTop(screen.y());
|
|
|
|
} else if ((position.attached & RectPart::Bottom)
|
|
|
|
|| (geometry.y() + geometry.height()
|
|
|
|
> screen.y() + screen.height())) {
|
|
|
|
geometry.moveTop(
|
|
|
|
screen.y() + screen.height() - geometry.height());
|
|
|
|
} else if (position.snapped & RectPart::Top) {
|
|
|
|
geometry.moveTop(inner.y());
|
|
|
|
} else if (position.snapped & RectPart::Bottom) {
|
|
|
|
geometry.moveTop(inner.y() + inner.height() - geometry.height());
|
|
|
|
}
|
2020-01-03 11:18:38 +00:00
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
setGeometry(geometry.marginsAdded(_padding));
|
|
|
|
updateDecorations();
|
2020-01-06 17:54:11 +00:00
|
|
|
update();
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::paintEvent(QPaintEvent *e) {
|
2020-01-03 11:18:38 +00:00
|
|
|
QPainter p(this);
|
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
if (_useTransparency) {
|
|
|
|
Ui::Platform::StartTranslucentPaint(p, e);
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
auto request = FrameRequest();
|
2020-01-29 13:16:22 +00:00
|
|
|
const auto inner = rect().marginsRemoved(_padding);
|
|
|
|
request.resize = request.outer = inner.size();
|
2020-01-06 17:54:11 +00:00
|
|
|
request.corners = RectPart(0)
|
|
|
|
| ((_attached & (RectPart::Left | RectPart::Top))
|
|
|
|
? RectPart(0)
|
|
|
|
: RectPart::TopLeft)
|
|
|
|
| ((_attached & (RectPart::Top | RectPart::Right))
|
|
|
|
? RectPart(0)
|
|
|
|
: RectPart::TopRight)
|
|
|
|
| ((_attached & (RectPart::Right | RectPart::Bottom))
|
|
|
|
? RectPart(0)
|
|
|
|
: RectPart::BottomRight)
|
|
|
|
| ((_attached & (RectPart::Bottom | RectPart::Left))
|
|
|
|
? RectPart(0)
|
|
|
|
: RectPart::BottomLeft);
|
2020-01-29 13:16:22 +00:00
|
|
|
request.radius = ImageRoundRadius::Large;
|
|
|
|
if (_useTransparency) {
|
|
|
|
const auto sides = RectPart::AllSides & ~_attached;
|
|
|
|
Ui::Shadow::paint(p, inner, width(), st::callShadow);
|
|
|
|
}
|
|
|
|
p.translate(_padding.left(), _padding.top());
|
2020-01-06 17:54:11 +00:00
|
|
|
_paint(p, request);
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::mousePressEvent(QMouseEvent *e) {
|
2020-01-03 11:18:38 +00:00
|
|
|
if (e->button() != Qt::LeftButton) {
|
|
|
|
return;
|
|
|
|
}
|
2020-01-06 19:50:03 +00:00
|
|
|
_pressState = _overState;
|
2020-01-03 11:18:38 +00:00
|
|
|
_pressPoint = e->globalPos();
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::mouseReleaseEvent(QMouseEvent *e) {
|
2020-01-06 19:50:03 +00:00
|
|
|
if (e->button() != Qt::LeftButton || !base::take(_pressState)) {
|
2020-01-03 11:18:38 +00:00
|
|
|
return;
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (!base::take(_dragState)) {
|
2020-01-06 17:54:11 +00:00
|
|
|
//playbackPauseResume();
|
2020-01-03 13:22:59 +00:00
|
|
|
} else {
|
|
|
|
finishDrag(e->globalPos());
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 19:50:03 +00:00
|
|
|
void PipPanel::updateOverState(QPoint point) {
|
|
|
|
const auto size = st::pipResizeArea;
|
2020-01-29 13:16:22 +00:00
|
|
|
const auto ignore = _attached | _snapped;
|
|
|
|
const auto count = [&](RectPart side, int padding) {
|
|
|
|
return (ignore & side) ? 0 : padding ? padding : size;
|
|
|
|
};
|
|
|
|
const auto left = count(RectPart::Left, _padding.left());
|
|
|
|
const auto top = count(RectPart::Top, _padding.top());
|
|
|
|
const auto right = count(RectPart::Right, _padding.right());
|
|
|
|
const auto bottom = count(RectPart::Bottom, _padding.bottom());
|
2020-01-06 19:50:03 +00:00
|
|
|
const auto overState = [&] {
|
2020-01-29 13:16:22 +00:00
|
|
|
if (point.x() < left) {
|
|
|
|
if (point.y() < top) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::TopLeft;
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (point.y() >= height() - bottom) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::BottomLeft;
|
|
|
|
} else {
|
|
|
|
return RectPart::Left;
|
|
|
|
}
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (point.x() >= width() - right) {
|
|
|
|
if (point.y() < top) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::TopRight;
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (point.y() >= height() - bottom) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::BottomRight;
|
|
|
|
} else {
|
|
|
|
return RectPart::Right;
|
|
|
|
}
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (point.y() < top) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::Top;
|
2020-01-29 13:16:22 +00:00
|
|
|
} else if (point.y() >= height() - bottom) {
|
2020-01-06 19:50:03 +00:00
|
|
|
return RectPart::Bottom;
|
|
|
|
} else {
|
|
|
|
return RectPart::Center;
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
if (_overState != overState) {
|
|
|
|
_overState = overState;
|
|
|
|
setCursor([&] {
|
|
|
|
switch (_overState) {
|
|
|
|
case RectPart::Center:
|
|
|
|
return style::cur_default;
|
|
|
|
case RectPart::TopLeft:
|
|
|
|
case RectPart::BottomRight:
|
|
|
|
return style::cur_sizefdiag;
|
|
|
|
case RectPart::TopRight:
|
|
|
|
case RectPart::BottomLeft:
|
|
|
|
return style::cur_sizebdiag;
|
|
|
|
case RectPart::Left:
|
|
|
|
case RectPart::Right:
|
|
|
|
return style::cur_sizehor;
|
|
|
|
case RectPart::Top:
|
|
|
|
case RectPart::Bottom:
|
|
|
|
return style::cur_sizever;
|
|
|
|
}
|
|
|
|
Unexpected("State in PipPanel::updateOverState.");
|
|
|
|
}());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::mouseMoveEvent(QMouseEvent *e) {
|
2020-01-06 19:50:03 +00:00
|
|
|
if (!_pressState) {
|
|
|
|
updateOverState(e->pos());
|
2020-01-03 11:18:38 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto point = e->globalPos();
|
|
|
|
const auto distance = QApplication::startDragDistance();
|
2020-01-29 13:16:22 +00:00
|
|
|
if (!_dragState
|
2020-01-06 19:50:03 +00:00
|
|
|
&& (point - _pressPoint).manhattanLength() > distance) {
|
2020-01-29 13:16:22 +00:00
|
|
|
_dragState = _pressState;
|
|
|
|
updateDecorations();
|
|
|
|
_dragStartGeometry = geometry().marginsRemoved(_padding);
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
2020-01-29 13:16:22 +00:00
|
|
|
if (_dragState) {
|
|
|
|
processDrag(point);
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
void PipPanel::processDrag(QPoint point) {
|
|
|
|
Expects(_dragState.has_value());
|
2020-01-06 19:50:03 +00:00
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
const auto dragPart = *_dragState;
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto screen = (dragPart == RectPart::Center)
|
2020-01-06 19:50:03 +00:00
|
|
|
? ScreenFromPosition(point)
|
|
|
|
: myScreen()
|
|
|
|
? myScreen()->availableGeometry()
|
|
|
|
: QRect();
|
|
|
|
if (screen.isEmpty()) {
|
|
|
|
return;
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto minimalSize = _ratio.scaled(
|
|
|
|
st::pipMinimalSize,
|
|
|
|
st::pipMinimalSize,
|
|
|
|
Qt::KeepAspectRatioByExpanding);
|
|
|
|
const auto maximalSize = _ratio.scaled(
|
|
|
|
screen.width() / 2,
|
|
|
|
screen.height() / 2,
|
|
|
|
Qt::KeepAspectRatio);
|
2020-01-06 19:50:03 +00:00
|
|
|
const auto geometry = Transformed(
|
2020-01-29 13:16:22 +00:00
|
|
|
_dragStartGeometry,
|
2020-01-07 11:41:42 +00:00
|
|
|
minimalSize,
|
|
|
|
maximalSize,
|
2020-01-06 19:50:03 +00:00
|
|
|
point - _pressPoint,
|
2020-01-07 11:41:42 +00:00
|
|
|
dragPart);
|
|
|
|
const auto valid = Constrained(
|
|
|
|
geometry,
|
|
|
|
minimalSize,
|
|
|
|
maximalSize,
|
|
|
|
_ratio,
|
2020-01-29 13:16:22 +00:00
|
|
|
dragPart,
|
|
|
|
_attached);
|
2020-01-07 11:41:42 +00:00
|
|
|
const auto clamped = (dragPart == RectPart::Center)
|
|
|
|
? ClampToEdges(screen, valid)
|
|
|
|
: valid.topLeft();
|
|
|
|
if (clamped != valid.topLeft()) {
|
|
|
|
moveAnimated(clamped);
|
|
|
|
} else {
|
2020-01-06 19:50:03 +00:00
|
|
|
_positionAnimation.stop();
|
2020-01-29 13:16:22 +00:00
|
|
|
setGeometry(valid.marginsAdded(_padding));
|
2020-01-07 11:41:42 +00:00
|
|
|
}
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::finishDrag(QPoint point) {
|
2020-01-03 13:22:59 +00:00
|
|
|
const auto screen = ScreenFromPosition(point);
|
2020-01-29 13:16:22 +00:00
|
|
|
const auto inner = geometry().marginsRemoved(_padding);
|
2020-01-03 13:22:59 +00:00
|
|
|
const auto position = pos();
|
|
|
|
const auto clamped = [&] {
|
|
|
|
auto result = position;
|
2020-01-29 13:16:22 +00:00
|
|
|
if (result.x() > screen.x() + screen.width() - inner.width()) {
|
|
|
|
result.setX(screen.x() + screen.width() - inner.width());
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
|
|
|
if (result.x() < screen.x()) {
|
|
|
|
result.setX(screen.x());
|
|
|
|
}
|
2020-01-29 13:16:22 +00:00
|
|
|
if (result.y() > screen.y() + screen.height() - inner.height()) {
|
|
|
|
result.setY(screen.y() + screen.height() - inner.height());
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
|
|
|
if (result.y() < screen.y()) {
|
|
|
|
result.setY(screen.y());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}();
|
|
|
|
if (position != clamped) {
|
|
|
|
moveAnimated(clamped);
|
|
|
|
} else {
|
|
|
|
_positionAnimation.stop();
|
2020-01-29 13:16:22 +00:00
|
|
|
updateDecorations();
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::updatePositionAnimated() {
|
2020-01-03 13:22:59 +00:00
|
|
|
const auto progress = _positionAnimation.value(1.);
|
|
|
|
if (!_positionAnimation.animating()) {
|
2020-01-29 13:16:22 +00:00
|
|
|
move(_positionAnimationTo - QPoint(_padding.left(), _padding.top()));
|
|
|
|
if (!_dragState) {
|
|
|
|
updateDecorations();
|
|
|
|
}
|
2020-01-03 13:22:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto from = QPointF(_positionAnimationFrom);
|
|
|
|
const auto to = QPointF(_positionAnimationTo);
|
2020-01-29 13:16:22 +00:00
|
|
|
move((from + (to - from) * progress).toPoint()
|
|
|
|
- QPoint(_padding.left(), _padding.top()));
|
2020-01-03 13:22:59 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void PipPanel::moveAnimated(QPoint to) {
|
2020-01-03 13:22:59 +00:00
|
|
|
if (_positionAnimation.animating() && _positionAnimationTo == to) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_positionAnimationTo = to;
|
2020-01-29 13:16:22 +00:00
|
|
|
_positionAnimationFrom = pos() + QPoint(_padding.left(), _padding.top());
|
2020-01-03 13:22:59 +00:00
|
|
|
_positionAnimation.stop();
|
|
|
|
_positionAnimation.start(
|
|
|
|
[=] { updatePositionAnimated(); },
|
|
|
|
0.,
|
|
|
|
1.,
|
|
|
|
st::slideWrapDuration,
|
|
|
|
anim::easeOutCirc);
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
void PipPanel::updateDecorations() {
|
|
|
|
const auto position = countPosition();
|
|
|
|
const auto center = position.geometry.center();
|
|
|
|
const auto use = Ui::Platform::TranslucentWindowsSupported(center);
|
|
|
|
const auto full = use ? st::callShadow.extend : style::margins();
|
|
|
|
const auto padding = style::margins(
|
|
|
|
(position.attached & RectPart::Left) ? 0 : full.left(),
|
|
|
|
(position.attached & RectPart::Top) ? 0 : full.top(),
|
|
|
|
(position.attached & RectPart::Right) ? 0 : full.right(),
|
|
|
|
(position.attached & RectPart::Bottom) ? 0 : full.bottom());
|
|
|
|
_snapped = position.snapped;
|
|
|
|
if (_padding == padding || _attached == position.attached) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto newGeometry = position.geometry.marginsAdded(padding);
|
|
|
|
_attached = position.attached;
|
|
|
|
_padding = padding;
|
|
|
|
_useTransparency = use;
|
|
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
|
|
|
|
setGeometry(newGeometry);
|
|
|
|
update();
|
|
|
|
if (!_dragState) {
|
|
|
|
_saveGeometryRequests.fire({});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
Pip::Pip(
|
2020-01-29 08:58:07 +00:00
|
|
|
not_null<Delegate*> delegate,
|
2020-01-06 17:54:11 +00:00
|
|
|
std::shared_ptr<Streaming::Document> document,
|
|
|
|
FnMut<void()> closeAndContinue,
|
|
|
|
FnMut<void()> destroy)
|
2020-01-29 08:58:07 +00:00
|
|
|
: _delegate(delegate)
|
|
|
|
, _instance(document, [=] { waitingAnimationCallback(); })
|
|
|
|
, _panel(
|
|
|
|
_delegate->pipParentWidget(),
|
|
|
|
[=](QPainter &p, const FrameRequest &request) { paint(p, request); })
|
2020-01-28 11:48:29 +00:00
|
|
|
, _playPauseResume(
|
|
|
|
std::in_place,
|
|
|
|
&_panel,
|
|
|
|
object_ptr<Ui::IconButton>(&_panel, st::mediaviewPlayButton))
|
|
|
|
, _pictureInPicture(
|
|
|
|
std::in_place,
|
|
|
|
&_panel,
|
|
|
|
object_ptr<Ui::IconButton>(&_panel, st::mediaviewFullScreenButton))
|
|
|
|
, _close(
|
|
|
|
std::in_place,
|
|
|
|
&_panel,
|
|
|
|
object_ptr<Ui::IconButton>(&_panel, st::boxTitleClose))
|
2020-01-06 17:54:11 +00:00
|
|
|
, _closeAndContinue(std::move(closeAndContinue))
|
|
|
|
, _destroy(std::move(destroy)) {
|
|
|
|
setupPanel();
|
2020-01-29 08:58:07 +00:00
|
|
|
setupButtons();
|
2020-01-06 17:54:11 +00:00
|
|
|
setupStreaming();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::setupPanel() {
|
|
|
|
const auto size = style::ConvertScale(_instance.info().video.size);
|
|
|
|
if (size.isEmpty()) {
|
|
|
|
_panel.setAspectRatio(QSize(1, 1));
|
|
|
|
} else {
|
|
|
|
_panel.setAspectRatio(size);
|
|
|
|
}
|
2020-01-29 08:58:07 +00:00
|
|
|
_panel.setPosition(Deserialize(_delegate->pipLoadGeometry()));
|
2020-01-06 17:54:11 +00:00
|
|
|
_panel.show();
|
2020-01-28 11:48:29 +00:00
|
|
|
|
2020-01-29 13:16:22 +00:00
|
|
|
_panel.saveGeometryRequests(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
saveGeometry();
|
2020-01-29 08:58:07 +00:00
|
|
|
}, _panel.lifetime());
|
|
|
|
|
|
|
|
_panel.events(
|
|
|
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
|
|
|
return e->type() == QEvent::Close;
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
_destroy();
|
|
|
|
}, _panel.lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::setupButtons() {
|
2020-01-28 11:48:29 +00:00
|
|
|
_panel.sizeValue(
|
|
|
|
) | rpl::start_with_next([=](QSize size) {
|
|
|
|
_close->moveToLeft(0, 0, size.width());
|
|
|
|
const auto skip = st::mediaviewFullScreenLeft;
|
|
|
|
const auto sum = _playPauseResume->width() + skip + _pictureInPicture->width();
|
|
|
|
const auto left = (size.width() - sum) / 2;
|
|
|
|
const auto top = size.height() - _playPauseResume->height() - skip;
|
|
|
|
_playPauseResume->moveToLeft(left, top);
|
|
|
|
_pictureInPicture->moveToRight(left, top);
|
|
|
|
}, _panel.lifetime());
|
|
|
|
|
2020-01-29 08:58:07 +00:00
|
|
|
_close->entity()->addClickHandler([=] {
|
|
|
|
_panel.close();
|
|
|
|
});
|
|
|
|
_pictureInPicture->entity()->addClickHandler([=] {
|
|
|
|
_closeAndContinue();
|
|
|
|
});
|
|
|
|
_playPauseResume->entity()->addClickHandler([=] {
|
|
|
|
playbackPauseResume();
|
|
|
|
});
|
|
|
|
_close->show(anim::type::instant);
|
|
|
|
_pictureInPicture->show(anim::type::instant);
|
|
|
|
_playPauseResume->show(anim::type::instant);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::saveGeometry() {
|
|
|
|
_delegate->pipSaveGeometry(Serialize(_panel.countPosition()));
|
2020-01-28 11:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
|
|
|
|
auto showPause = Player::ShowPauseIcon(state.state);
|
|
|
|
if (showPause != _showPause) {
|
|
|
|
_showPause = showPause;
|
|
|
|
_playPauseResume->entity()->setIconOverride(
|
|
|
|
_showPause ? &st::mediaviewPauseIcon : nullptr,
|
|
|
|
_showPause ? &st::mediaviewPauseIconOver : nullptr);
|
|
|
|
}
|
2020-01-06 17:54:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::setupStreaming() {
|
|
|
|
_instance.setPriority(kPipLoaderPriority);
|
|
|
|
_instance.lockPlayer();
|
|
|
|
|
|
|
|
_instance.player().updates(
|
|
|
|
) | rpl::start_with_next_error([=](Streaming::Update &&update) {
|
|
|
|
handleStreamingUpdate(std::move(update));
|
|
|
|
}, [=](Streaming::Error &&error) {
|
|
|
|
handleStreamingError(std::move(error));
|
|
|
|
}, _instance.lifetime());
|
|
|
|
}
|
|
|
|
|
2020-01-06 19:50:03 +00:00
|
|
|
void Pip::paint(QPainter &p, FrameRequest request) {
|
2020-01-06 17:54:11 +00:00
|
|
|
const auto image = videoFrameForDirectPaint(request);
|
|
|
|
p.drawImage(0, 0, image);
|
|
|
|
if (_instance.player().ready()) {
|
|
|
|
_instance.markFrameShown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::handleStreamingUpdate(Streaming::Update &&update) {
|
|
|
|
using namespace Streaming;
|
|
|
|
|
|
|
|
update.data.match([&](Information &update) {
|
|
|
|
_panel.setAspectRatio(update.video.size);
|
|
|
|
}, [&](const PreloadedVideo &update) {
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
}, [&](const UpdateVideo &update) {
|
|
|
|
_panel.update();
|
|
|
|
Core::App().updateNonIdle();
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
}, [&](const PreloadedAudio &update) {
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
}, [&](const UpdateAudio &update) {
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
}, [&](WaitingForData) {
|
|
|
|
}, [&](MutedByOther) {
|
|
|
|
}, [&](Finished) {
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-28 11:48:29 +00:00
|
|
|
void Pip::updatePlaybackState() {
|
|
|
|
const auto state = _instance.player().prepareLegacyState();
|
|
|
|
if (state.position != kTimeUnknown && state.length != kTimeUnknown) {
|
|
|
|
updatePlayPauseResumeState(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
void Pip::handleStreamingError(Streaming::Error &&error) {
|
|
|
|
_panel.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::playbackPauseResume() {
|
2020-01-28 11:48:29 +00:00
|
|
|
if (_instance.player().failed()) {
|
|
|
|
_panel.close();
|
|
|
|
} else if (_instance.player().finished()
|
|
|
|
|| !_instance.player().active()) {
|
|
|
|
restartAtSeekPosition(0);
|
2020-01-06 17:54:11 +00:00
|
|
|
} else if (_instance.player().paused()) {
|
|
|
|
_instance.resume();
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
} else {
|
|
|
|
_instance.pause();
|
2020-01-28 11:48:29 +00:00
|
|
|
updatePlaybackState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::restartAtSeekPosition(crl::time position) {
|
|
|
|
if (!_instance.info().video.cover.isNull()) {
|
|
|
|
_instance.saveFrameToCover();
|
2020-01-06 17:54:11 +00:00
|
|
|
}
|
2020-01-28 11:48:29 +00:00
|
|
|
auto options = Streaming::PlaybackOptions();
|
|
|
|
options.position = position;
|
|
|
|
options.audioId = _instance.player().prepareLegacyState().id;
|
2020-01-29 08:58:07 +00:00
|
|
|
options.speed = _delegate->pipPlaybackSpeed();
|
2020-01-28 11:48:29 +00:00
|
|
|
_instance.play(options);
|
|
|
|
updatePlaybackState();
|
2020-01-06 17:54:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QImage Pip::videoFrame(const FrameRequest &request) const {
|
|
|
|
if (_instance.player().ready()) {
|
|
|
|
return _instance.frame(request);
|
|
|
|
} else if (_preparedCoverStorage.isNull()
|
|
|
|
|| _preparedCoverRequest != request) {
|
|
|
|
_preparedCoverRequest = request;
|
|
|
|
_preparedCoverStorage = Streaming::PrepareByRequest(
|
|
|
|
_instance.info().video.cover,
|
|
|
|
_instance.info().video.rotation,
|
|
|
|
request,
|
|
|
|
std::move(_preparedCoverStorage));
|
|
|
|
}
|
|
|
|
return _preparedCoverStorage;
|
2020-01-03 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-06 17:54:11 +00:00
|
|
|
QImage Pip::videoFrameForDirectPaint(const FrameRequest &request) const {
|
|
|
|
const auto result = videoFrame(request);
|
2020-01-03 11:18:38 +00:00
|
|
|
|
|
|
|
#ifdef USE_OPENGL_OVERLAY_WIDGET
|
|
|
|
const auto bytesPerLine = result.bytesPerLine();
|
|
|
|
if (bytesPerLine == result.width() * 4) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// On macOS 10.8+ we use QOpenGLWidget as OverlayWidget base class.
|
|
|
|
// The OpenGL painter can't paint textures where byte data is with strides.
|
|
|
|
// So in that case we prepare a compact copy of the frame to render.
|
|
|
|
//
|
|
|
|
// See Qt commit ed557c037847e343caa010562952b398f806adcd
|
|
|
|
//
|
2020-01-06 17:54:11 +00:00
|
|
|
auto &cache = _frameForDirectPaint;
|
2020-01-03 11:18:38 +00:00
|
|
|
if (cache.size() != result.size()) {
|
|
|
|
cache = QImage(result.size(), result.format());
|
|
|
|
}
|
|
|
|
const auto height = result.height();
|
|
|
|
const auto line = cache.bytesPerLine();
|
|
|
|
Assert(line == result.width() * 4);
|
|
|
|
Assert(line < bytesPerLine);
|
|
|
|
|
|
|
|
auto from = result.bits();
|
|
|
|
auto to = cache.bits();
|
|
|
|
for (auto y = 0; y != height; ++y) {
|
|
|
|
memcpy(to, from, line);
|
|
|
|
to += line;
|
|
|
|
from += bytesPerLine;
|
|
|
|
}
|
|
|
|
return cache;
|
|
|
|
#endif // USE_OPENGL_OVERLAY_WIDGET
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Pip::waitingAnimationCallback() {
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace View
|
|
|
|
} // namespace Media
|