diff --git a/Telegram/SourceFiles/main/main_settings.cpp b/Telegram/SourceFiles/main/main_settings.cpp index 40c7d9eb7..00c24c45d 100644 --- a/Telegram/SourceFiles/main/main_settings.cpp +++ b/Telegram/SourceFiles/main/main_settings.cpp @@ -44,12 +44,14 @@ Settings::Variables::Variables() QByteArray Settings::serialize() const { const auto autoDownload = _variables.autoDownload.serialize(); - auto size = sizeof(qint32) * 30; + auto size = sizeof(qint32) * 38; for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) { size += Serialize::stringSize(i.key()) + Serialize::stringSize(i.value()); } size += _variables.groupStickersSectionHidden.size() * sizeof(quint64); + size += _variables.mediaLastPlaybackPosition.size() * 2 * sizeof(quint64); size += Serialize::bytearraySize(autoDownload); + size += Serialize::bytearraySize(_variables.videoPipGeometry); auto result = QByteArray(); result.reserve(size); @@ -107,6 +109,7 @@ QByteArray Settings::serialize() const { stream << quint64(id) << qint64(time); } stream << qint32(SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current())); + stream << _variables.videoPipGeometry; } return result; } @@ -158,6 +161,7 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { qint32 spellcheckerEnabled = _variables.spellcheckerEnabled.current() ? 1 : 0; std::vector> mediaLastPlaybackPosition; qint32 videoPlaybackSpeed = SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current()); + QByteArray videoPipGeometry = _variables.videoPipGeometry; stream >> versionTag; if (versionTag == kVersionTag) { @@ -281,6 +285,9 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> videoPlaybackSpeed; } + if (!stream.atEnd()) { + stream >> videoPipGeometry; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Main::Settings::constructFromSerialized()")); @@ -369,6 +376,7 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { _variables.spellcheckerEnabled = (spellcheckerEnabled == 1); _variables.mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition); _variables.videoPlaybackSpeed = DeserializePlaybackSpeed(videoPlaybackSpeed); + _variables.videoPipGeometry = videoPipGeometry; } void Settings::setSupportChatsTimeSlice(int slice) { diff --git a/Telegram/SourceFiles/main/main_settings.h b/Telegram/SourceFiles/main/main_settings.h index ee922a8f4..c8cedc9d4 100644 --- a/Telegram/SourceFiles/main/main_settings.h +++ b/Telegram/SourceFiles/main/main_settings.h @@ -247,6 +247,12 @@ public: void setVideoPlaybackSpeed(float64 speed) { _variables.videoPlaybackSpeed = speed; } + [[nodiscard]] QByteArray videoPipGeometry() const { + return _variables.videoPipGeometry; + } + void setVideoPipGeometry(QByteArray geometry) { + _variables.videoPipGeometry = geometry; + } private: struct Variables { @@ -289,6 +295,7 @@ private: rpl::variable spellcheckerEnabled = true; std::vector> mediaLastPlaybackPosition; rpl::variable videoPlaybackSpeed = 1.; + QByteArray videoPipGeometry; static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 734cd86a3..10e6f572a 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -83,6 +83,43 @@ constexpr auto kIdsLimit = 48; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; +class PipDelegate final : public Pip::Delegate { +public: + PipDelegate(QWidget *parent, not_null session); + + void pipSaveGeometry(QByteArray geometry) override; + QByteArray pipLoadGeometry() override; + float64 pipPlaybackSpeed() override; + QWidget *pipParentWidget() override; + +private: + QWidget *_parent = nullptr; + not_null _session; + +}; + +PipDelegate::PipDelegate(QWidget *parent, not_null session) +: _parent(parent) +, _session(session) { +} + +void PipDelegate::pipSaveGeometry(QByteArray geometry) { + _session->settings().setVideoPipGeometry(geometry); + _session->saveSettingsDelayed(); +} + +QByteArray PipDelegate::pipLoadGeometry() { + return _session->settings().videoPipGeometry(); +} + +float64 PipDelegate::pipPlaybackSpeed() { + return _session->settings().videoPlaybackSpeed(); +} + +QWidget *PipDelegate::pipParentWidget() { + return _parent; +} + Images::Options VideoThumbOptions(not_null document) { const auto result = Images::Option::Smooth | Images::Option::Blurred; return (document && document->isVideoMessage()) @@ -204,6 +241,21 @@ struct OverlayWidget::Streamed { bool resumeOnCallEnd = false; }; +struct OverlayWidget::PipWrap { + PipWrap( + QWidget *parent, + not_null document, + std::shared_ptr shared, + FnMut closeAndContinue, + FnMut destroy); + + PipWrap(const PipWrap &other) = delete; + PipWrap &operator=(const PipWrap &other) = delete; + + PipDelegate delegate; + Pip wrapped; +}; + OverlayWidget::Streamed::Streamed( not_null document, Data::FileOrigin origin, @@ -214,6 +266,20 @@ OverlayWidget::Streamed::Streamed( , controls(controlsParent, controlsDelegate) { } +OverlayWidget::PipWrap::PipWrap( + QWidget *parent, + not_null document, + std::shared_ptr shared, + FnMut closeAndContinue, + FnMut destroy) +: delegate(parent, &document->session()) +, wrapped( + &delegate, + std::move(shared), + std::move(closeAndContinue), + std::move(destroy)) { +} + OverlayWidget::OverlayWidget() : OverlayParent(nullptr) , _transparentBrush(style::transparentPlaceholderBrush()) @@ -985,6 +1051,7 @@ void OverlayWidget::clearData() { _fromName = QString(); _photo = nullptr; _doc = nullptr; + _pip = nullptr; _fullScreenVideo = false; _caption.clear(); } @@ -2440,13 +2507,20 @@ float64 OverlayWidget::playbackControlsCurrentSpeed() { } void OverlayWidget::switchToPip() { + Expects(_streamed != nullptr); + Expects(_doc != nullptr); + const auto document = _doc; const auto msgId = _msgid; - _pip = std::make_unique(this, _streamed->instance.shared(), [=] { - showDocument(_doc, Auth().data().message(msgId), {}, true); - }, [=] { - _pip = nullptr; - }); + const auto closeAndContinue = [=] { + showDocument(document, document->owner().message(msgId), {}, true); + }; + _pip = std::make_unique( + this, + document, + _streamed->instance.shared(), + closeAndContinue, + [=] { _pip = nullptr; }); close(); } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index f4ed8f485..0133a198e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -129,6 +129,7 @@ private slots: private: struct Streamed; + struct PipWrap; enum OverState { OverNone, @@ -384,7 +385,7 @@ private: bool _blurred = true; std::unique_ptr _streamed; - std::unique_ptr _pip; + std::unique_ptr _pip; const style::icon *_docIcon = nullptr; style::color _docIconColor; diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index fe9a0240d..6391d505f 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -11,8 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_document.h" #include "media/streaming/media_streaming_utility.h" #include "media/audio/media_audio.h" -#include "main/main_session.h" -#include "main/main_settings.h" #include "data/data_document.h" #include "core/application.h" #include "ui/platform/ui_platform_utility.h" @@ -32,6 +30,7 @@ namespace View { namespace { constexpr auto kPipLoaderPriority = 2; +constexpr auto kSaveGeometryTimeout = crl::time(1000); [[nodiscard]] QRect ScreenFromPosition(QPoint point) { const auto screen = QGuiApplication::screenAt(point); @@ -199,6 +198,34 @@ constexpr auto kPipLoaderPriority = 2; 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; +} + } // namespace PipPanel::PipPanel( @@ -574,14 +601,16 @@ void PipPanel::moveAnimated(QPoint to) { } Pip::Pip( - QWidget *parent, + not_null delegate, std::shared_ptr document, FnMut closeAndContinue, FnMut destroy) -: _instance(document, [=] { waitingAnimationCallback(); }) -, _panel(parent, [=](QPainter &p, const FrameRequest &request) { - paint(p, request); -}) +: _delegate(delegate) +, _instance(document, [=] { waitingAnimationCallback(); }) +, _panel( + _delegate->pipParentWidget(), + [=](QPainter &p, const FrameRequest &request) { paint(p, request); }) +, _saveGeometryTimer([=] { saveGeometry(); }) , _playPauseResume( std::in_place, &_panel, @@ -596,6 +625,46 @@ Pip::Pip( object_ptr(&_panel, st::boxTitleClose)) , _closeAndContinue(std::move(closeAndContinue)) , _destroy(std::move(destroy)) { + setupPanel(); + setupButtons(); + 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); + } + _panel.setPosition(Deserialize(_delegate->pipLoadGeometry())); + _panel.show(); + + _panel.geometryValue( + ) | rpl::skip(1) | rpl::start_with_next([=] { + _saveGeometryTimer.callOnce(kSaveGeometryTimeout); + }, _panel.lifetime()); + + _panel.events( + ) | rpl::filter([=](not_null e) { + return e->type() == QEvent::Close; + }) | rpl::start_with_next([=] { + _destroy(); + }, _panel.lifetime()); +} + +void Pip::setupButtons() { + _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()); + _close->entity()->addClickHandler([=] { _panel.close(); }); @@ -608,37 +677,10 @@ Pip::Pip( _close->show(anim::type::instant); _pictureInPicture->show(anim::type::instant); _playPauseResume->show(anim::type::instant); - setupPanel(); - 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); - } - _panel.setPosition(PipPanel::Position()); - _panel.show(); - - _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()); - - _panel.events( - ) | rpl::filter([=](not_null e) { - return e->type() == QEvent::Close; - }) | rpl::start_with_next([=] { - _destroy(); - }, _panel.lifetime()); +void Pip::saveGeometry() { + _delegate->pipSaveGeometry(Serialize(_panel.countPosition())); } void Pip::updatePlayPauseResumeState(const Player::TrackState &state) { @@ -726,9 +768,7 @@ void Pip::restartAtSeekPosition(crl::time position) { auto options = Streaming::PlaybackOptions(); options.position = position; options.audioId = _instance.player().prepareLegacyState().id; - options.speed = options.audioId.audio() - ? options.audioId.audio()->session().settings().videoPlaybackSpeed() - : 1.; + options.speed = _delegate->pipPlaybackSpeed(); _instance.play(options); updatePlaybackState(); } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.h b/Telegram/SourceFiles/media/view/media_view_pip.h index 2b76e7bbd..752822aea 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.h +++ b/Telegram/SourceFiles/media/view/media_view_pip.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_instance.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" +#include "base/timer.h" #include @@ -76,6 +77,9 @@ private: RectParts _attached = RectParts(); QSize _ratio; + bool _useTransparency = true; + style::margins _padding; + RectPart _overState = RectPart(); std::optional _pressState; QPoint _pressPoint; @@ -89,8 +93,16 @@ private: class Pip final { public: + class Delegate { + public: + virtual void pipSaveGeometry(QByteArray geometry) = 0; + [[nodiscard]] virtual QByteArray pipLoadGeometry() = 0; + [[nodiscard]] virtual float64 pipPlaybackSpeed() = 0; + [[nodiscard]] virtual QWidget *pipParentWidget() = 0; + }; + Pip( - QWidget *parent, + not_null delegate, std::shared_ptr document, FnMut closeAndContinue, FnMut destroy); @@ -99,12 +111,14 @@ private: using FrameRequest = Streaming::FrameRequest; void setupPanel(); + void setupButtons(); void setupStreaming(); void paint(QPainter &p, FrameRequest request); void playbackPauseResume(); void waitingAnimationCallback(); void handleStreamingUpdate(Streaming::Update &&update); void handleStreamingError(Streaming::Error &&error); + void saveGeometry(); void updatePlaybackState(); void updatePlayPauseResumeState(const Player::TrackState &state); @@ -114,9 +128,11 @@ private: [[nodiscard]] QImage videoFrameForDirectPaint( const FrameRequest &request) const; + const not_null _delegate; Streaming::Instance _instance; PipPanel _panel; QSize _size; + base::Timer _saveGeometryTimer; base::unique_qptr> _playPauseResume; base::unique_qptr< Ui::FadeWrap> _pictureInPicture;