| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870 |
- /*
- 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"
- #include "media/streaming/media_streaming_utility.h"
- #include "media/view/media_view_playback_progress.h"
- #include "media/view/media_view_pip_opengl.h"
- #include "media/view/media_view_pip_raster.h"
- #include "media/audio/media_audio.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_file_origin.h"
- #include "data/data_session.h"
- #include "data/data_media_rotation.h"
- #include "main/main_account.h"
- #include "main/main_session.h"
- #include "core/application.h"
- #include "base/platform/base_platform_info.h"
- #include "base/power_save_blocker.h"
- #include "ui/platform/ui_platform_utility.h"
- #include "ui/platform/ui_platform_window_title.h"
- #include "ui/widgets/buttons.h"
- #include "ui/wrap/fade_wrap.h"
- #include "ui/widgets/shadow.h"
- #include "ui/text/format_values.h"
- #include "ui/gl/gl_surface.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "window/window_controller.h"
- #include "styles/style_widgets.h"
- #include "styles/style_window.h"
- #include "styles/style_media_view.h"
- #include <QtGui/QWindow>
- #include <QtGui/QScreen>
- #include <QtWidgets/QApplication>
- namespace Media {
- namespace View {
- namespace {
- constexpr auto kPipLoaderPriority = 2;
- constexpr auto kMsInSecond = 1000;
- [[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);
- }
- [[nodiscard]] QSize MaxAllowedSizeForScreen(QSize screenSize) {
- // Each side should be less than screen side - 3 * st::pipBorderSkip,
- // That way it won't try to snap to both opposite sides of the screen.
- const auto skip = 3 * st::pipBorderSkip;
- return { screenSize.width() - skip, screenSize.height() - skip };
- }
- [[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);
- }
- [[nodiscard]] QRect Transformed(
- QRect original,
- QSize minimalSize,
- QSize maximalSize,
- QPoint delta,
- RectPart by) {
- const auto width = original.width();
- const auto height = original.height();
- const auto x1 = width - minimalSize.width();
- const auto x2 = maximalSize.width() - width;
- const auto y1 = height - minimalSize.height();
- const auto y2 = maximalSize.height() - height;
- switch (by) {
- case RectPart::Center: return original.translated(delta);
- case RectPart::TopLeft:
- original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
- original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
- return original;
- case RectPart::TopRight:
- original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
- original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
- return original;
- case RectPart::BottomRight:
- original.setHeight(
- original.height() + std::clamp(delta.y(), -y1, y2));
- original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
- return original;
- case RectPart::BottomLeft:
- original.setHeight(
- original.height() + std::clamp(delta.y(), -y1, y2));
- original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
- return original;
- case RectPart::Left:
- original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
- return original;
- case RectPart::Top:
- original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
- return original;
- case RectPart::Right:
- original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
- return original;
- case RectPart::Bottom:
- original.setHeight(
- original.height() + std::clamp(delta.y(), -y1, y2));
- return original;
- }
- return original;
- Unexpected("RectPart in PiP Transformed.");
- }
- [[nodiscard]] QRect Constrained(
- QRect original,
- QSize minimalSize,
- QSize maximalSize,
- QSize ratio,
- RectPart by,
- RectParts attached) {
- 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());
- const auto desiredSize = ratio.scaled(
- original.size(),
- (((RectParts(by) & RectPart::AllCorners)
- || ((by == RectPart::Top || by == RectPart::Bottom)
- && widthLarger)
- || ((by == RectPart::Left || by == RectPart::Right)
- && !widthLarger))
- ? Qt::KeepAspectRatio
- : Qt::KeepAspectRatioByExpanding));
- const auto newSize = QSize(
- std::clamp(
- desiredSize.width(),
- minimalSize.width(),
- maximalSize.width()),
- std::clamp(
- desiredSize.height(),
- minimalSize.height(),
- maximalSize.height()));
- 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()),
- ((attached & RectPart::Top)
- ? 0
- : (attached & RectPart::Bottom)
- ? (original.height() - newSize.height())
- : (original.height() - newSize.height()) / 2)),
- newSize);
- case RectPart::Top:
- return QRect(
- original.topLeft() + QPoint(
- ((attached & RectPart::Left)
- ? 0
- : (attached & RectPart::Right)
- ? (original.width() - newSize.width())
- : (original.width() - newSize.width()) / 2),
- 0),
- newSize);
- case RectPart::Right:
- return QRect(
- original.topLeft() + QPoint(
- 0,
- ((attached & RectPart::Top)
- ? 0
- : (attached & RectPart::Bottom)
- ? (original.height() - newSize.height())
- : (original.height() - newSize.height()) / 2)),
- newSize);
- case RectPart::Bottom:
- return QRect(
- original.topLeft() + QPoint(
- ((attached & RectPart::Left)
- ? 0
- : (attached & RectPart::Right)
- ? (original.width() - newSize.width())
- : (original.width() - newSize.width()) / 2),
- (original.height() - newSize.height())),
- newSize);
- }
- Unexpected("RectPart in PiP Constrained.");
- }
- [[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;
- }
- Qt::Edges RectPartToQtEdges(RectPart rectPart) {
- switch (rectPart) {
- case RectPart::TopLeft:
- return Qt::TopEdge | Qt::LeftEdge;
- case RectPart::TopRight:
- return Qt::TopEdge | Qt::RightEdge;
- case RectPart::BottomRight:
- return Qt::BottomEdge | Qt::RightEdge;
- case RectPart::BottomLeft:
- return Qt::BottomEdge | Qt::LeftEdge;
- case RectPart::Left:
- return Qt::LeftEdge;
- case RectPart::Top:
- return Qt::TopEdge;
- case RectPart::Right:
- return Qt::RightEdge;
- case RectPart::Bottom:
- return Qt::BottomEdge;
- }
- return Qt::Edges();
- }
- } // namespace
- QRect RotatedRect(QRect rect, int rotation) {
- switch (rotation) {
- case 0: return rect;
- case 90: return QRect(
- rect.y(),
- -rect.x() - rect.width(),
- rect.height(),
- rect.width());
- case 180: return QRect(
- -rect.x() - rect.width(),
- -rect.y() - rect.height(),
- rect.width(),
- rect.height());
- case 270: return QRect(
- -rect.y() - rect.height(),
- rect.x(),
- rect.height(),
- rect.width());
- }
- Unexpected("Rotation in RotatedRect.");
- }
- bool UsePainterRotation(int rotation) {
- return !(rotation % 180);
- }
- QSize FlipSizeByRotation(QSize size, int rotation) {
- return (((rotation / 90) % 2) == 1)
- ? QSize(size.height(), size.width())
- : size;
- }
- QImage RotateFrameImage(QImage image, int rotation) {
- auto transform = QTransform();
- transform.rotate(rotation);
- return image.transformed(transform);
- }
- PipPanel::PipPanel(
- QWidget *parent,
- Fn<Ui::GL::ChosenRenderer(Ui::GL::Capabilities)> renderer)
- : _content(Ui::GL::CreateSurface(std::move(renderer)))
- , _parent(parent) {
- }
- void PipPanel::init() {
- widget()->setWindowFlags(Qt::Tool
- | Qt::WindowStaysOnTopHint
- | Qt::FramelessWindowHint
- | Qt::WindowDoesNotAcceptFocus);
- widget()->setAttribute(Qt::WA_ShowWithoutActivating);
- widget()->setAttribute(Qt::WA_MacAlwaysShowToolWindow);
- widget()->setAttribute(Qt::WA_NoSystemBackground);
- widget()->setAttribute(Qt::WA_TranslucentBackground);
- Ui::Platform::IgnoreAllActivation(widget());
- Ui::Platform::InitOnTopPanel(widget());
- widget()->setMouseTracking(true);
- widget()->resize(0, 0);
- widget()->hide();
- rpl::merge(
- rp()->shownValue() | rpl::to_empty,
- rp()->paintRequest() | rpl::to_empty
- ) | rpl::map([=] {
- return widget()->windowHandle()
- && widget()->windowHandle()->isExposed();
- }) | rpl::distinct_until_changed(
- ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
- // Workaround Qt's forced transient parent.
- Ui::Platform::ClearTransientParent(widget());
- Ui::Platform::SetWindowMargins(widget(), _padding);
- }, rp()->lifetime());
- rp()->screenValue(
- ) | rpl::skip(1) | rpl::start_with_next([=](not_null<QScreen*> screen) {
- handleScreenChanged(screen);
- }, rp()->lifetime());
- if (Platform::IsWayland()) {
- rp()->sizeValue(
- ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
- handleWaylandResize(size);
- }, rp()->lifetime());
- }
- }
- not_null<QWidget*> PipPanel::widget() const {
- return _content->rpWidget();
- }
- not_null<Ui::RpWidgetWrap*> PipPanel::rp() const {
- return _content.get();
- }
- void PipPanel::setAspectRatio(QSize ratio) {
- if (_ratio == ratio) {
- return;
- }
- _ratio = ratio;
- if (_ratio.isEmpty()) {
- _ratio = QSize(1, 1);
- }
- Ui::Platform::DisableSystemWindowResize(widget(), _ratio);
- if (!widget()->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();
- }
- QRect PipPanel::inner() const {
- return widget()->rect().marginsRemoved(_padding);
- }
- RectParts PipPanel::attached() const {
- return _attached;
- }
- bool PipPanel::useTransparency() const {
- return _useTransparency;
- }
- void PipPanel::setDragDisabled(bool disabled) {
- _dragDisabled = disabled;
- if (_dragState) {
- _dragState = std::nullopt;
- }
- }
- bool PipPanel::dragging() const {
- return _dragState.has_value();
- }
- rpl::producer<> PipPanel::saveGeometryRequests() const {
- return _saveGeometryRequests.events();
- }
- QScreen *PipPanel::myScreen() const {
- return widget()->screen();
- }
- PipPanel::Position PipPanel::countPosition() const {
- const auto screen = myScreen();
- if (!screen) {
- return Position();
- }
- auto result = Position();
- result.screen = screen->geometry();
- result.geometry = widget()->geometry().marginsRemoved(_padding);
- 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();
- if ((!_dragState || *_dragState != RectPart::Center)
- && !Platform::IsWayland()) {
- 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;
- }
- }
- return result;
- }
- void PipPanel::setPositionDefault() {
- const auto parentScreen = _parent ? _parent->screen() : nullptr;
- const auto myScreen = widget()->screen();
- if (parentScreen && myScreen != parentScreen) {
- #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- widget()->setScreen(parentScreen);
- #else // Qt >= 6.0.0
- widget()->windowHandle()->setScreen(parentScreen);
- #endif // Qt < 6.0.0
- }
- auto position = Position();
- position.snapped = RectPart::Top | RectPart::Left;
- position.screen = parentScreen->geometry();
- position.geometry = QRect(0, 0, st::pipDefaultSize, st::pipDefaultSize);
- setPositionOnScreen(position, parentScreen->availableGeometry());
- }
- 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);
- // Apply maximum size.
- const auto fit = MaxAllowedSizeForScreen(screen.size());
- const auto byWidth = (scaled.width() * fit.height())
- > (scaled.height() * fit.width());
- const auto normalized = (byWidth && scaled.width() > fit.width())
- ? QSize(fit.width(), fit.width() * scaled.height() / scaled.width())
- : (!byWidth && scaled.height() > fit.height())
- ? QSize(
- fit.height() * scaled.width() / scaled.height(),
- fit.height())
- : scaled;
- // Apply minimal size.
- 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());
- const auto size = QSize(
- std::max(normalized.width(), minimalSize.width()),
- std::max(normalized.height(), minimalSize.height()));
- // Apply maximal size.
- const auto maximalSize = byWidth
- ? QSize(fit.width(), fit.width() * _ratio.height() / _ratio.width())
- : QSize(fit.height() * _ratio.width() / _ratio.height(), fit.height());
- // 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());
- }
- // 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());
- }
- geometry += _padding;
- setGeometry(geometry);
- widget()->setMinimumSize(minimalSize);
- widget()->setMaximumSize(
- std::max(minimalSize.width(), maximalSize.width()),
- std::max(minimalSize.height(), maximalSize.height()));
- updateDecorations();
- }
- void PipPanel::update() {
- widget()->update();
- }
- void PipPanel::setGeometry(QRect geometry) {
- widget()->setGeometry(geometry);
- }
- void PipPanel::handleWaylandResize(QSize size) {
- if (_inHandleWaylandResize) {
- return;
- }
- _inHandleWaylandResize = true;
- // Apply aspect ratio.
- const auto max = std::max(size.width(), size.height());
- const auto scaled = (_ratio.width() > _ratio.height())
- ? QSize(max, max * _ratio.height() / _ratio.width())
- : QSize(max * _ratio.width() / _ratio.height(), max);
- // Buffer can't be bigger than the configured
- // (suggested by compositor) size.
- const auto byWidth = (scaled.width() * size.height())
- > (scaled.height() * size.width());
- const auto normalized = (byWidth && scaled.width() > size.width())
- ? QSize(size.width(), size.width() * scaled.height() / scaled.width())
- : (!byWidth && scaled.height() > size.height())
- ? QSize(
- size.height() * scaled.width() / scaled.height(),
- size.height())
- : scaled;
- widget()->resize(normalized);
- QResizeEvent e(normalized, size);
- QCoreApplication::sendEvent(widget()->windowHandle(), &e);
- _inHandleWaylandResize = false;
- }
- void PipPanel::handleScreenChanged(not_null<QScreen*> screen) {
- const auto screenGeometry = screen->availableGeometry();
- const auto minimalSize = _ratio.scaled(
- st::pipMinimalSize,
- st::pipMinimalSize,
- Qt::KeepAspectRatioByExpanding);
- const auto maximalSize = _ratio.scaled(
- MaxAllowedSizeForScreen(screenGeometry.size()),
- Qt::KeepAspectRatio);
- widget()->setMinimumSize(minimalSize);
- widget()->setMaximumSize(
- std::max(minimalSize.width(), maximalSize.width()),
- std::max(minimalSize.height(), maximalSize.height()));
- }
- void PipPanel::handleMousePress(QPoint position, Qt::MouseButton button) {
- if (button != Qt::LeftButton) {
- return;
- }
- updateOverState(position);
- _pressState = _overState;
- _pressPoint = QCursor::pos();
- }
- void PipPanel::handleMouseRelease(QPoint position, Qt::MouseButton button) {
- if (button != Qt::LeftButton || !base::take(_pressState)) {
- return;
- } else if (base::take(_dragState)) {
- finishDrag(QCursor::pos());
- updateOverState(position);
- }
- }
- void PipPanel::updateOverState(QPoint point) {
- const auto size = st::pipResizeArea;
- 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());
- const auto width = widget()->width();
- const auto height = widget()->height();
- const auto overState = [&] {
- if (point.x() < left) {
- if (point.y() < top) {
- return RectPart::TopLeft;
- } else if (point.y() >= height - bottom) {
- return RectPart::BottomLeft;
- } else {
- return RectPart::Left;
- }
- } else if (point.x() >= width - right) {
- if (point.y() < top) {
- return RectPart::TopRight;
- } else if (point.y() >= height - bottom) {
- return RectPart::BottomRight;
- } else {
- return RectPart::Right;
- }
- } else if (point.y() < top) {
- return RectPart::Top;
- } else if (point.y() >= height - bottom) {
- return RectPart::Bottom;
- } else {
- return RectPart::Center;
- }
- }();
- if (_overState != overState) {
- _overState = overState;
- widget()->setCursor([&] {
- switch (_overState) {
- case RectPart::Center:
- return style::cur_pointer;
- 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.");
- }());
- }
- }
- void PipPanel::handleMouseMove(QPoint position) {
- if (!_pressState) {
- updateOverState(position);
- return;
- }
- const auto point = QCursor::pos();
- const auto distance = QApplication::startDragDistance();
- if (!_dragState
- && (point - _pressPoint).manhattanLength() > distance
- && !_dragDisabled) {
- _dragState = _pressState;
- updateDecorations();
- _dragStartGeometry = widget()->geometry().marginsRemoved(_padding);
- }
- if (_dragState) {
- if (Platform::IsWayland()) {
- startSystemDrag();
- } else {
- processDrag(point);
- }
- }
- }
- void PipPanel::startSystemDrag() {
- Expects(_dragState.has_value());
- const auto stateEdges = RectPartToQtEdges(*_dragState);
- if (stateEdges) {
- widget()->windowHandle()->startSystemResize(stateEdges);
- } else {
- widget()->windowHandle()->startSystemMove();
- }
- Ui::SendSynteticMouseEvent(
- widget().get(),
- QEvent::MouseButtonRelease,
- Qt::LeftButton);
- }
- void PipPanel::processDrag(QPoint point) {
- Expects(_dragState.has_value());
- const auto dragPart = *_dragState;
- const auto screen = (dragPart == RectPart::Center)
- ? ScreenFromPosition(point)
- : myScreen()
- ? myScreen()->availableGeometry()
- : QRect();
- if (screen.isEmpty()) {
- return;
- }
- const auto minimalSize = _ratio.scaled(
- st::pipMinimalSize,
- st::pipMinimalSize,
- Qt::KeepAspectRatioByExpanding);
- const auto maximalSize = _ratio.scaled(
- MaxAllowedSizeForScreen(screen.size()),
- Qt::KeepAspectRatio);
- const auto geometry = Transformed(
- _dragStartGeometry,
- minimalSize,
- maximalSize,
- point - _pressPoint,
- dragPart);
- const auto valid = Constrained(
- geometry,
- minimalSize,
- maximalSize,
- _ratio,
- dragPart,
- _attached);
- const auto clamped = (dragPart == RectPart::Center)
- ? ClampToEdges(screen, valid)
- : valid.topLeft();
- widget()->setMinimumSize(minimalSize);
- widget()->setMaximumSize(
- std::max(minimalSize.width(), maximalSize.width()),
- std::max(minimalSize.height(), maximalSize.height()));
- if (clamped != valid.topLeft()) {
- moveAnimated(clamped);
- } else {
- const auto newGeometry = valid.marginsAdded(_padding);
- _positionAnimation.stop();
- setGeometry(newGeometry);
- }
- }
- void PipPanel::finishDrag(QPoint point) {
- const auto screen = ScreenFromPosition(point);
- const auto inner = widget()->geometry().marginsRemoved(_padding);
- const auto position = widget()->pos();
- const auto clamped = [&] {
- auto result = position;
- if (Platform::IsWayland()) {
- return result;
- }
- if (result.x() > screen.x() + screen.width() - inner.width()) {
- result.setX(screen.x() + screen.width() - inner.width());
- }
- if (result.x() < screen.x()) {
- result.setX(screen.x());
- }
- if (result.y() > screen.y() + screen.height() - inner.height()) {
- result.setY(screen.y() + screen.height() - inner.height());
- }
- if (result.y() < screen.y()) {
- result.setY(screen.y());
- }
- return result;
- }();
- if (position != clamped) {
- moveAnimated(clamped);
- } else {
- _positionAnimation.stop();
- updateDecorations();
- }
- }
- void PipPanel::updatePositionAnimated() {
- const auto progress = _positionAnimation.value(1.);
- if (!_positionAnimation.animating()) {
- widget()->move(_positionAnimationTo
- - QPoint(_padding.left(), _padding.top()));
- if (!_dragState) {
- updateDecorations();
- }
- return;
- }
- const auto from = QPointF(_positionAnimationFrom);
- const auto to = QPointF(_positionAnimationTo);
- widget()->move((from + (to - from) * progress).toPoint()
- - QPoint(_padding.left(), _padding.top()));
- }
- void PipPanel::moveAnimated(QPoint to) {
- if (_positionAnimation.animating() && _positionAnimationTo == to) {
- return;
- }
- _positionAnimationTo = to;
- _positionAnimationFrom = widget()->pos()
- + QPoint(_padding.left(), _padding.top());
- _positionAnimation.stop();
- _positionAnimation.start(
- [=] { updatePositionAnimated(); },
- 0.,
- 1.,
- st::slideWrapDuration,
- anim::easeOutCirc);
- }
- void PipPanel::updateDecorations() {
- const auto guard = gsl::finally([&] {
- if (!_dragState) {
- _saveGeometryRequests.fire({});
- }
- });
- const auto position = countPosition();
- const auto use = Ui::Platform::TranslucentWindowsSupported();
- 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;
- widget()->setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
- if (widget()->windowHandle()) {
- Ui::Platform::SetWindowMargins(widget(), _padding);
- }
- setGeometry(newGeometry);
- update();
- }
- Pip::Pip(
- not_null<Delegate*> delegate,
- not_null<DocumentData*> data,
- Data::FileOrigin origin,
- not_null<DocumentData*> chosenQuality,
- HistoryItem *context,
- VideoQuality quality,
- std::shared_ptr<Streaming::Document> shared,
- FnMut<void()> closeAndContinue,
- FnMut<void()> destroy)
- : _delegate(delegate)
- , _data(data)
- , _origin(origin)
- , _chosenQuality(chosenQuality)
- , _context(context)
- , _quality(quality)
- , _instance(
- std::in_place,
- std::move(shared),
- [=] { waitingAnimationCallback(); })
- , _panel(
- _delegate->pipParentWidget(),
- [=](Ui::GL::Capabilities capabilities) {
- return chooseRenderer(capabilities);
- })
- , _playbackProgress(std::make_unique<PlaybackProgress>())
- , _dataMedia(_data->createMediaView())
- , _rotation(data->owner().mediaRotation().get(data))
- , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.)
- ? Core::App().settings().videoVolume()
- : Core::Settings::kDefaultVolume)
- , _closeAndContinue(std::move(closeAndContinue))
- , _destroy(std::move(destroy)) {
- setupPanel();
- setupButtons();
- setupStreaming();
- _data->session().account().sessionChanges(
- ) | rpl::start_with_next([=] {
- _destroy();
- }, _panel.rp()->lifetime());
- if (_context) {
- _data->owner().itemRemoved(
- ) | rpl::start_with_next([=](not_null<const HistoryItem*> data) {
- if (_context != data) {
- _context = nullptr;
- }
- }, _panel.rp()->lifetime());
- }
- }
- Pip::~Pip() = default;
- std::shared_ptr<Streaming::Document> Pip::shared() const {
- return _instance->shared();
- }
- void Pip::setupPanel() {
- _panel.init();
- const auto size = [&] {
- if (!_instance->info().video.size.isEmpty()) {
- return _instance->info().video.size;
- }
- const auto media = _data->activeMediaView();
- if (media) {
- media->goodThumbnailWanted();
- }
- const auto good = media ? media->goodThumbnail() : nullptr;
- const auto original = good ? good->size() : _data->dimensions;
- return original.isEmpty() ? QSize(1, 1) : original;
- }();
- _panel.setAspectRatio(FlipSizeByRotation(size, _rotation));
- _panel.setPosition(Deserialize(_delegate->pipLoadGeometry()));
- _panel.widget()->show();
- _panel.saveGeometryRequests(
- ) | rpl::start_with_next([=] {
- saveGeometry();
- }, _panel.rp()->lifetime());
- _panel.rp()->events(
- ) | rpl::start_with_next([=](not_null<QEvent*> e) {
- const auto mousePosition = [&] {
- return static_cast<QMouseEvent*>(e.get())->pos();
- };
- const auto mouseButton = [&] {
- return static_cast<QMouseEvent*>(e.get())->button();
- };
- switch (e->type()) {
- case QEvent::Close: handleClose(); break;
- case QEvent::Leave: handleLeave(); break;
- case QEvent::MouseMove:
- handleMouseMove(mousePosition());
- break;
- case QEvent::MouseButtonPress:
- handleMousePress(mousePosition(), mouseButton());
- break;
- case QEvent::MouseButtonRelease:
- handleMouseRelease(mousePosition(), mouseButton());
- break;
- case QEvent::MouseButtonDblClick:
- handleDoubleClick(mouseButton());
- break;
- }
- }, _panel.rp()->lifetime());
- }
- void Pip::handleClose() {
- crl::on_main(_panel.widget(), [=] {
- _destroy();
- });
- }
- void Pip::handleLeave() {
- setOverState(OverState::None);
- }
- void Pip::handleMouseMove(QPoint position) {
- const auto weak = Ui::MakeWeak(_panel.widget());
- const auto guard = gsl::finally([&] {
- if (weak) {
- _panel.handleMouseMove(position);
- }
- });
- setOverState(computeState(position));
- seekUpdate(position);
- volumeControllerUpdate(position);
- }
- void Pip::setOverState(OverState state) {
- if (_over == state) {
- return;
- }
- const auto wasShown = ResolveShownOver(_over);
- _over = state;
- const auto nowAreShown = (ResolveShownOver(_over) != OverState::None);
- if ((wasShown != OverState::None) != nowAreShown) {
- _controlsShown.start(
- [=] { _panel.update(); },
- nowAreShown ? 0. : 1.,
- nowAreShown ? 1. : 0.,
- st::fadeWrapDuration,
- anim::linear);
- }
- if (!_pressed) {
- updateActiveState(wasShown);
- }
- _panel.update();
- }
- void Pip::setPressedState(std::optional<OverState> state) {
- if (_pressed == state) {
- return;
- }
- const auto wasShown = shownActiveState();
- _pressed = state;
- updateActiveState(wasShown);
- }
- Pip::OverState Pip::shownActiveState() const {
- return ResolveShownOver(_pressed.value_or(_over));
- }
- float64 Pip::activeValue(const Button &button) const {
- const auto shownState = ResolveShownOver(button.state);
- return button.active.value((shownActiveState() == shownState) ? 1. : 0.);
- }
- void Pip::updateActiveState(OverState wasShown) {
- const auto check = [&](Button &button) {
- const auto shownState = ResolveShownOver(button.state);
- const auto nowIsShown = (shownActiveState() == shownState);
- if ((wasShown == shownState) != nowIsShown) {
- button.active.start(
- [=, &button] { _panel.widget()->update(button.icon); },
- nowIsShown ? 0. : 1.,
- nowIsShown ? 1. : 0.,
- st::fadeWrapDuration,
- anim::linear);
- }
- };
- check(_close);
- check(_enlarge);
- check(_play);
- check(_playback);
- check(_volumeToggle);
- check(_volumeController);
- }
- Pip::OverState Pip::ResolveShownOver(OverState state) {
- return (state == OverState::VolumeController)
- ? OverState::VolumeToggle
- : state;
- }
- void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
- const auto weak = Ui::MakeWeak(_panel.widget());
- const auto guard = gsl::finally([&] {
- if (weak) {
- _panel.handleMousePress(position, button);
- }
- });
- if (button != Qt::LeftButton) {
- return;
- }
- _pressed = _over;
- if (_over == OverState::Playback || _over == OverState::VolumeController) {
- _panel.setDragDisabled(true);
- }
- seekUpdate(position);
- volumeControllerUpdate(position);
- }
- void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
- const auto weak = Ui::MakeWeak(_panel.widget());
- const auto guard = gsl::finally([&] {
- if (weak) {
- _panel.handleMouseRelease(position, button);
- }
- });
- if (button != Qt::LeftButton) {
- return;
- }
- seekUpdate(position);
- volumeControllerUpdate(position);
- const auto pressed = base::take(_pressed);
- if (pressed && *pressed == OverState::Playback) {
- _panel.setDragDisabled(false);
- seekFinish(_playbackProgress->value());
- } else if (pressed && *pressed == OverState::VolumeController) {
- _panel.setDragDisabled(false);
- _panel.update();
- } else if (_panel.dragging() || !pressed || *pressed != _over) {
- _lastHandledPress = std::nullopt;
- } else {
- _lastHandledPress = _over;
- switch (_over) {
- case OverState::Close: _panel.widget()->close(); break;
- case OverState::Enlarge: _closeAndContinue(); break;
- case OverState::VolumeToggle: volumeToggled(); break;
- case OverState::Other: playbackPauseResume(); break;
- }
- }
- }
- void Pip::handleDoubleClick(Qt::MouseButton button) {
- if (_over != OverState::Other
- || !_lastHandledPress
- || *_lastHandledPress != _over) {
- return;
- }
- playbackPauseResume(); // Un-click the first click.
- _closeAndContinue();
- }
- void Pip::seekUpdate(QPoint position) {
- if (!_pressed || *_pressed != OverState::Playback) {
- return;
- }
- const auto unbound = (position.x() - _playback.icon.x())
- / float64(_playback.icon.width());
- const auto progress = std::clamp(unbound, 0., 1.);
- seekProgress(progress);
- }
- void Pip::seekProgress(float64 value) {
- if (!_lastDurationMs) {
- return;
- }
- _playbackProgress->setValue(value, false);
- const auto positionMs = std::clamp(
- static_cast<crl::time>(value * _lastDurationMs),
- crl::time(0),
- _lastDurationMs);
- if (_seekPositionMs != positionMs) {
- _seekPositionMs = positionMs;
- if (!_instance->player().paused()
- && !_instance->player().finished()) {
- _pausedBySeek = true;
- playbackPauseResume();
- }
- updatePlaybackTexts(_seekPositionMs, _lastDurationMs, kMsInSecond);
- }
- }
- void Pip::seekFinish(float64 value) {
- if (!_lastDurationMs) {
- return;
- }
- const auto positionMs = std::clamp(
- static_cast<crl::time>(value * _lastDurationMs),
- crl::time(0),
- _lastDurationMs);
- _seekPositionMs = -1;
- _startPaused = !_pausedBySeek && !_instance->player().finished();
- restartAtSeekPosition(positionMs);
- }
- void Pip::volumeChanged(float64 volume) {
- if (volume > 0.) {
- _lastPositiveVolume = volume;
- }
- Player::mixer()->setVideoVolume(volume);
- Core::App().settings().setVideoVolume(volume);
- Core::App().saveSettingsDelayed();
- }
- void Pip::volumeToggled() {
- const auto volume = Core::App().settings().videoVolume();
- volumeChanged(volume ? 0. : _lastPositiveVolume);
- _panel.update();
- }
- void Pip::volumeControllerUpdate(QPoint position) {
- if (!_pressed || *_pressed != OverState::VolumeController) {
- return;
- }
- const auto unbound = (position.x() - _volumeController.icon.x())
- / float64(_volumeController.icon.width());
- const auto value = std::clamp(unbound, 0., 1.);
- volumeChanged(value);
- _panel.update();
- }
- void Pip::setupButtons() {
- _close.state = OverState::Close;
- _enlarge.state = OverState::Enlarge;
- _playback.state = OverState::Playback;
- _volumeToggle.state = OverState::VolumeToggle;
- _volumeController.state = OverState::VolumeController;
- _play.state = OverState::Other;
- _panel.rp()->sizeValue(
- ) | rpl::map([=] {
- return _panel.inner();
- }) | rpl::start_with_next([=](QRect rect) {
- const auto skip = st::pipControlSkip;
- _close.area = QRect(
- rect.x(),
- rect.y(),
- st::pipCloseIcon.width() + 2 * skip,
- st::pipCloseIcon.height() + 2 * skip);
- _enlarge.area = QRect(
- _close.area.x() + _close.area.width(),
- rect.y(),
- st::pipEnlargeIcon.width() + 2 * skip,
- st::pipEnlargeIcon.height() + 2 * skip);
- const auto volumeSkip = st::pipPlaybackSkip;
- const auto volumeHeight = 2 * volumeSkip + st::pipPlaybackWide;
- const auto volumeToggleWidth = st::pipVolumeIcon0.width()
- + 2 * skip;
- const auto volumeToggleHeight = st::pipVolumeIcon0.height()
- + 2 * skip;
- const auto volumeWidth = (((st::mediaviewVolumeWidth + 2 * skip)
- + _close.area.width()
- + _enlarge.area.width()
- + volumeToggleWidth) < rect.width())
- ? st::mediaviewVolumeWidth
- : 0;
- _volumeController.area = QRect(
- rect.x() + rect.width() - volumeWidth - 2 * volumeSkip,
- rect.y() + (volumeToggleHeight - volumeHeight) / 2,
- volumeWidth,
- volumeHeight);
- _volumeToggle.area = QRect(
- _volumeController.area.x()
- - st::pipVolumeIcon0.width()
- - skip,
- rect.y(),
- volumeToggleWidth,
- volumeToggleHeight);
- using Ui::Platform::TitleControlsLayout;
- if (!TitleControlsLayout::Instance()->current().onLeft()) {
- _close.area.moveLeft(rect.x()
- + rect.width()
- - (_close.area.x() - rect.x())
- - _close.area.width());
- _enlarge.area.moveLeft(rect.x()
- + rect.width()
- - (_enlarge.area.x() - rect.x())
- - _enlarge.area.width());
- _volumeToggle.area.moveLeft(rect.x());
- _volumeController.area.moveLeft(_volumeToggle.area.x()
- + _volumeToggle.area.width());
- }
- _close.icon = _close.area.marginsRemoved({ skip, skip, skip, skip });
- _enlarge.icon = _enlarge.area.marginsRemoved(
- { skip, skip, skip, skip });
- _volumeToggle.icon = _volumeToggle.area.marginsRemoved(
- { skip, skip, skip, skip });
- _play.icon = QRect(
- rect.x() + (rect.width() - st::pipPlayIcon.width()) / 2,
- rect.y() + (rect.height() - st::pipPlayIcon.height()) / 2,
- st::pipPlayIcon.width(),
- st::pipPlayIcon.height());
- const auto volumeArea = _volumeController.area;
- _volumeController.icon = (volumeArea.width() > 2 * volumeSkip
- && volumeArea.height() > 2 * volumeSkip)
- ? volumeArea.marginsRemoved(
- { volumeSkip, volumeSkip, volumeSkip, volumeSkip })
- : QRect();
- const auto playbackSkip = st::pipPlaybackSkip;
- const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWide;
- _playback.area = QRect(
- rect.x(),
- rect.y() + rect.height() - playbackHeight,
- rect.width(),
- playbackHeight);
- _playback.icon = _playback.area.marginsRemoved(
- { playbackSkip, playbackSkip, playbackSkip, playbackSkip });
- }, _panel.rp()->lifetime());
- _playbackProgress->setValueChangedCallback([=](
- float64 value,
- float64 receivedTill) {
- _panel.widget()->update(_playback.area);
- });
- }
- void Pip::saveGeometry() {
- _delegate->pipSaveGeometry(Serialize(_panel.countPosition()));
- }
- void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
- auto showPause = Player::ShowPauseIcon(state.state);
- if (showPause != _showPause) {
- _showPause = showPause;
- _panel.update();
- }
- }
- void Pip::setupStreaming() {
- _instance->setPriority(kPipLoaderPriority);
- _instance->lockPlayer();
- _instance->switchQualityRequests(
- ) | rpl::filter([=](int quality) {
- return !_quality.manual && _quality.height != quality;
- }) | rpl::start_with_next([=](int quality) {
- applyVideoQuality({
- .manual = 0,
- .height = uint32(quality),
- });
- }, _instance->lifetime());
- _instance->player().updates(
- ) | rpl::start_with_next_error([=](Streaming::Update &&update) {
- handleStreamingUpdate(std::move(update));
- }, [=](Streaming::Error &&error) {
- handleStreamingError(std::move(error));
- }, _instance->lifetime());
- updatePlaybackState();
- }
- void Pip::applyVideoQuality(VideoQuality value) {
- if (_quality == value
- || !_dataMedia->canBePlayed(_context)) {
- return;
- }
- const auto resolved = _data->chooseQuality(_context, value);
- if (_chosenQuality == resolved) {
- return;
- }
- auto instance = Streaming::Instance(
- resolved,
- _data,
- _context,
- _origin,
- [=] { waitingAnimationCallback(); });
- if (!instance.valid()) {
- return;
- }
- if (_instance->ready()) {
- _qualityChangeFrame = currentVideoFrameImage();
- }
- if (!_instance->player().active()
- || _instance->player().finished()) {
- _qualityChangeFinished = true;
- }
- _startPaused = _qualityChangeFinished || _instance->player().paused();
- _quality = value;
- Core::App().settings().setVideoQuality(value);
- Core::App().saveSettingsDelayed();
- _chosenQuality = resolved;
- _instance.emplace(std::move(instance));
- setupStreaming();
- restartAtSeekPosition(_lastUpdatePosition);
- }
- QImage Pip::currentVideoFrameImage() const {
- return _instance->player().ready()
- ? _instance->player().currentFrameImage()
- : _instance->info().video.cover;
- }
- Ui::GL::ChosenRenderer Pip::chooseRenderer(
- Ui::GL::Capabilities capabilities) {
- const auto use = Platform::IsMac()
- ? true
- : capabilities.transparency;
- LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use)));
- if (use) {
- _opengl = true;
- return {
- .renderer = std::make_unique<RendererGL>(this),
- .backend = Ui::GL::Backend::OpenGL,
- };
- }
- return {
- .renderer = std::make_unique<RendererSW>(this),
- .backend = Ui::GL::Backend::Raster,
- };
- }
- void Pip::paint(not_null<Renderer*> renderer) const {
- const auto controlsShown = _controlsShown.value(
- (_over != OverState::None) ? 1. : 0.);
- auto geometry = ContentGeometry{
- .inner = _panel.inner(),
- .attached = (_panel.useTransparency()
- ? _panel.attached()
- : RectPart::AllSides),
- .fade = controlsShown,
- .outer = _panel.widget()->size(),
- .rotation = _rotation,
- .videoRotation = _instance->info().video.rotation,
- .useTransparency = _panel.useTransparency(),
- };
- if (canUseVideoFrame()) {
- renderer->paintTransformedVideoFrame(geometry);
- _instance->markFrameShown();
- } else {
- const auto content = staticContent();
- if (_preparedCoverState == ThumbState::Cover) {
- geometry.rotation += base::take(geometry.videoRotation);
- }
- renderer->paintTransformedStaticContent(content, geometry);
- }
- if (_instance->waitingShown()) {
- renderer->paintRadialLoading(countRadialRect(), controlsShown);
- }
- if (controlsShown > 0) {
- paintButtons(renderer, controlsShown);
- paintPlayback(renderer, controlsShown);
- paintVolumeController(renderer, controlsShown);
- }
- }
- void Pip::paintButtons(not_null<Renderer*> renderer, float64 shown) const {
- const auto outer = _panel.widget()->width();
- const auto drawOne = [&](
- const Button &button,
- const style::icon &icon,
- const style::icon &iconOver) {
- renderer->paintButton(
- button,
- outer,
- shown,
- activeValue(button),
- icon,
- iconOver);
- };
- renderer->paintButtonsStart();
- drawOne(
- _play,
- _showPause ? st::pipPauseIcon : st::pipPlayIcon,
- _showPause ? st::pipPauseIconOver : st::pipPlayIconOver);
- drawOne(_close, st::pipCloseIcon, st::pipCloseIconOver);
- drawOne(_enlarge, st::pipEnlargeIcon, st::pipEnlargeIconOver);
- const auto volume = Core::App().settings().videoVolume();
- if (volume <= 0.) {
- drawOne(
- _volumeToggle,
- st::pipVolumeIcon0,
- st::pipVolumeIcon0Over);
- } else if (volume < 1 / 2.) {
- drawOne(
- _volumeToggle,
- st::pipVolumeIcon1,
- st::pipVolumeIcon1Over);
- } else {
- drawOne(
- _volumeToggle,
- st::pipVolumeIcon2,
- st::pipVolumeIcon2Over);
- }
- }
- void Pip::paintPlayback(not_null<Renderer*> renderer, float64 shown) const {
- const auto outer = QRect(
- _playback.icon.x(),
- _playback.icon.y() - st::pipPlaybackFont->height,
- _playback.icon.width(),
- st::pipPlaybackFont->height + _playback.icon.height());
- renderer->paintPlayback(outer, shown);
- }
- void Pip::paintPlaybackContent(
- QPainter &p,
- QRect outer,
- float64 shown) const {
- p.setOpacity(shown);
- paintPlaybackProgress(p, outer);
- paintPlaybackTexts(p, outer);
- }
- void Pip::paintPlaybackProgress(QPainter &p, QRect outer) const {
- const auto radius = _playback.icon.height() / 2;
- const auto progress = _playbackProgress->value();
- const auto active = activeValue(_playback);
- const auto height = anim::interpolate(
- st::pipPlaybackWidth,
- _playback.icon.height(),
- active);
- const auto rect = QRect(
- outer.x(),
- (outer.y()
- + st::pipPlaybackFont->height
- + _playback.icon.height()
- - height),
- outer.width(),
- height);
- paintProgressBar(p, rect, progress, radius, active);
- }
- void Pip::paintProgressBar(
- QPainter &p,
- const QRect &rect,
- float64 progress,
- int radius,
- float64 active) const {
- const auto done = int(base::SafeRound(rect.width() * progress));
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- if (done > 0) {
- p.setBrush(anim::brush(
- st::mediaviewPipControlsFg,
- st::mediaviewPipPlaybackActive,
- active));
- p.setClipRect(rect.x(), rect.y(), done, rect.height());
- p.drawRoundedRect(
- rect.x(),
- rect.y(),
- std::min(done + radius, rect.width()),
- rect.height(),
- radius,
- radius);
- }
- if (done < rect.width()) {
- const auto from = std::max(rect.x() + done - radius, rect.x());
- p.setBrush(st::mediaviewPipPlaybackInactive);
- p.setClipRect(
- rect.x() + done,
- rect.y(),
- rect.width() - done,
- rect.height());
- p.drawRoundedRect(
- from,
- rect.y(),
- rect.x() + rect.width() - from,
- rect.height(),
- radius,
- radius);
- }
- p.setClipping(false);
- }
- void Pip::paintPlaybackTexts(QPainter &p, QRect outer) const {
- const auto left = outer.x()
- - _playback.icon.x()
- + _playback.area.x()
- + st::pipPlaybackTextSkip;
- const auto right = outer.x()
- - _playback.icon.x()
- + _playback.area.x()
- + _playback.area.width()
- - st::pipPlaybackTextSkip;
- const auto top = outer.y() + st::pipPlaybackFont->ascent;
- p.setFont(st::pipPlaybackFont);
- p.setPen(st::mediaviewPipControlsFgOver);
- p.drawText(left, top, _timeAlready);
- p.drawText(right - _timeLeftWidth, top, _timeLeft);
- }
- void Pip::paintVolumeController(
- not_null<Renderer*> renderer,
- float64 shown) const {
- if (_volumeController.icon.isEmpty()) {
- return;
- }
- renderer->paintVolumeController(_volumeController.icon, shown);
- }
- void Pip::paintVolumeControllerContent(
- QPainter &p,
- QRect outer,
- float64 shown) const {
- p.setOpacity(shown);
- const auto radius = _volumeController.icon.height() / 2;
- const auto volume = Core::App().settings().videoVolume();
- const auto active = activeValue(_volumeController);
- const auto height = anim::interpolate(
- st::pipPlaybackWidth,
- _volumeController.icon.height(),
- active);
- const auto rect = QRect(
- outer.x(),
- outer.y() + radius - height / 2,
- outer.width(),
- height);
- paintProgressBar(p, rect, volume, radius, active);
- }
- void Pip::handleStreamingUpdate(Streaming::Update &&update) {
- using namespace Streaming;
- v::match(update.data, [&](const Information &update) {
- _panel.setAspectRatio(
- FlipSizeByRotation(update.video.size, _rotation));
- _qualityChangeFrame = QImage();
- }, [&](PreloadedVideo) {
- updatePlaybackState();
- }, [&](UpdateVideo update) {
- _panel.update();
- Core::App().updateNonIdle();
- updatePlaybackState();
- _lastUpdatePosition = update.position;
- }, [&](PreloadedAudio) {
- updatePlaybackState();
- }, [&](UpdateAudio) {
- updatePlaybackState();
- }, [](WaitingForData) {
- }, [](SpeedEstimate) {
- }, [](MutedByOther) {
- }, [&](Finished) {
- updatePlaybackState();
- });
- }
- void Pip::updatePlaybackState() {
- const auto state = _instance->player().prepareLegacyState();
- updatePlayPauseResumeState(state);
- if (state.position == kTimeUnknown
- || state.length == kTimeUnknown
- || _pausedBySeek) {
- return;
- }
- _playbackProgress->updateState(state);
- updatePowerSaveBlocker(state);
- qint64 position = 0;
- if (Player::IsStoppedAtEnd(state.state)) {
- position = state.length;
- } else if (!Player::IsStoppedOrStopping(state.state)) {
- position = state.position;
- } else {
- position = 0;
- }
- const auto playFrequency = state.frequency;
- _lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
- if (_seekPositionMs < 0) {
- updatePlaybackTexts(position, state.length, playFrequency);
- }
- }
- void Pip::updatePowerSaveBlocker(const Player::TrackState &state) {
- const auto block = _data->isVideoFile()
- && !IsPausedOrPausing(state.state)
- && !IsStoppedOrStopping(state.state);
- base::UpdatePowerSaveBlocker(
- _powerSaveBlocker,
- block,
- base::PowerSaveBlockType::PreventDisplaySleep,
- [] { return u"Video playback is active"_q; },
- [=] { return _panel.widget()->windowHandle(); });
- }
- void Pip::updatePlaybackTexts(
- int64 position,
- int64 length,
- int64 frequency) {
- const auto playAlready = position / frequency;
- const auto playLeft = (length / frequency) - playAlready;
- const auto already = Ui::FormatDurationText(playAlready);
- const auto minus = QChar(8722);
- const auto left = minus + Ui::FormatDurationText(playLeft);
- if (_timeAlready == already && _timeLeft == left) {
- return;
- }
- _timeAlready = already;
- _timeLeft = left;
- _timeLeftWidth = st::pipPlaybackFont->width(_timeLeft);
- _panel.widget()->update(QRect(
- _playback.area.x(),
- _playback.icon.y() - st::pipPlaybackFont->height,
- _playback.area.width(),
- st::pipPlaybackFont->height));
- }
- void Pip::handleStreamingError(Streaming::Error &&error) {
- _panel.widget()->close();
- }
- void Pip::playbackPauseResume() {
- if (_instance->player().failed()) {
- _panel.widget()->close();
- } else if (_instance->player().finished()
- || !_instance->player().active()) {
- _startPaused = false;
- restartAtSeekPosition(0);
- } else if (_instance->player().paused()) {
- _instance->resume();
- updatePlaybackState();
- } else {
- _instance->pause();
- updatePlaybackState();
- }
- }
- void Pip::restartAtSeekPosition(crl::time position) {
- _lastUpdatePosition = position;
- if (!_instance->info().video.cover.isNull()) {
- _preparedCoverStorage = QImage();
- _preparedCoverState = ThumbState::Empty;
- _instance->saveFrameToCover();
- }
- auto options = Streaming::PlaybackOptions();
- options.position = position;
- options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo();
- options.audioId = _instance->player().prepareLegacyState().id;
- options.speed = _delegate->pipPlaybackSpeed();
- _instance->play(options);
- if (_startPaused) {
- _instance->pause();
- }
- _pausedBySeek = false;
- updatePlaybackState();
- }
- bool Pip::canUseVideoFrame() const {
- return _instance->player().ready()
- && !_instance->info().video.cover.isNull();
- }
- QImage Pip::videoFrame(const FrameRequest &request) const {
- Expects(canUseVideoFrame());
- return _instance->frame(request);
- }
- Streaming::FrameWithInfo Pip::videoFrameWithInfo() const {
- Expects(canUseVideoFrame());
- return _instance->frameWithInfo();
- }
- QImage Pip::staticContent() const {
- const auto &cover = !_qualityChangeFrame.isNull()
- ? _qualityChangeFrame
- : _instance->info().video.cover;
- const auto media = _data->activeMediaView();
- const auto use = media
- ? media
- : _data->inlineThumbnailBytes().isEmpty()
- ? nullptr
- : _data->createMediaView();
- if (use) {
- use->goodThumbnailWanted();
- }
- const auto good = use ? use->goodThumbnail() : nullptr;
- const auto thumb = use ? use->thumbnail() : nullptr;
- const auto blurred = use ? use->thumbnailInline() : nullptr;
- const auto state = !cover.isNull()
- ? ThumbState::Cover
- : good
- ? ThumbState::Good
- : thumb
- ? ThumbState::Thumb
- : blurred
- ? ThumbState::Inline
- : ThumbState::Empty;
- if (!_preparedCoverStorage.isNull() && _preparedCoverState >= state) {
- return _preparedCoverStorage;
- }
- _preparedCoverState = state;
- if (state == ThumbState::Cover) {
- _preparedCoverStorage = cover;
- } else {
- _preparedCoverStorage = (good
- ? good
- : thumb
- ? thumb
- : blurred
- ? blurred
- : Image::BlankMedia().get())->original();
- if (!good) {
- _preparedCoverStorage = Images::Blur(
- std::move(_preparedCoverStorage));
- }
- }
- return _preparedCoverStorage;
- }
- void Pip::paintRadialLoadingContent(
- QPainter &p,
- const QRect &inner,
- QColor fg) const {
- const auto arc = inner.marginsRemoved(QMargins(
- st::radialLine,
- st::radialLine,
- st::radialLine,
- st::radialLine));
- p.setOpacity(_instance->waitingOpacity());
- p.setPen(Qt::NoPen);
- p.setBrush(st::radialBg);
- {
- PainterHighQualityEnabler hq(p);
- p.drawEllipse(inner);
- }
- p.setOpacity(1.);
- Ui::InfiniteRadialAnimation::Draw(
- p,
- _instance->waitingState(),
- arc.topLeft(),
- arc.size(),
- _panel.widget()->width(),
- fg,
- st::radialLine);
- }
- QRect Pip::countRadialRect() const {
- const auto outer = _panel.inner();
- return {
- outer.x() + (outer.width() - st::radialSize.width()) / 2,
- outer.y() + (outer.height() - st::radialSize.height()) / 2,
- st::radialSize.width(),
- st::radialSize.height()
- };
- }
- Pip::OverState Pip::computeState(QPoint position) const {
- if (!_panel.inner().contains(position)) {
- return OverState::None;
- } else if (_close.area.contains(position)) {
- return OverState::Close;
- } else if (_enlarge.area.contains(position)) {
- return OverState::Enlarge;
- } else if (_playback.area.contains(position)) {
- return OverState::Playback;
- } else if (_volumeToggle.area.contains(position)) {
- return OverState::VolumeToggle;
- } else if (_volumeController.area.contains(position)) {
- return OverState::VolumeController;
- } else {
- return OverState::Other;
- }
- }
- void Pip::waitingAnimationCallback() {
- _panel.widget()->update(countRadialRect());
- }
- } // namespace View
- } // namespace Media
|