From f99960e1f67619fda9e05f26f1859f083791c34c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Jul 2020 21:44:21 +0400 Subject: [PATCH] Play video userpics in photo change messages. --- Telegram/SourceFiles/data/data_streaming.cpp | 47 +++-- Telegram/SourceFiles/data/data_streaming.h | 6 + .../history/view/media/history_view_gif.cpp | 6 +- .../history/view/media/history_view_photo.cpp | 172 ++++++++++++++++-- .../history/view/media/history_view_photo.h | 20 ++ Telegram/SourceFiles/ui/special_buttons.cpp | 4 +- Telegram/SourceFiles/ui/special_buttons.h | 3 +- 7 files changed, 212 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/data/data_streaming.cpp b/Telegram/SourceFiles/data/data_streaming.cpp index 0b791261e..dd3b13b38 100644 --- a/Telegram/SourceFiles/data/data_streaming.cpp +++ b/Telegram/SourceFiles/data/data_streaming.cpp @@ -102,6 +102,30 @@ template return result; } +template +void Streaming::keepAlive( + base::flat_map, std::weak_ptr> &documents, + not_null data) { + const auto i = documents.find(data); + if (i == end(documents)) { + return; + } + auto shared = i->second.lock(); + if (!shared) { + return; + } + const auto till = crl::now() + kKeepAliveTimeout; + const auto j = _keptAlive.find(shared); + if (j != end(_keptAlive)) { + j->second = till; + } else { + _keptAlive.emplace(std::move(shared), till); + } + if (!_keptAliveTimer.isActive()) { + _keptAliveTimer.callOnce(kKeepAliveTimeout); + } +} + std::shared_ptr Streaming::sharedReader( not_null document, FileOrigin origin, @@ -129,24 +153,11 @@ std::shared_ptr Streaming::sharedDocument( } void Streaming::keepAlive(not_null document) { - const auto i = _fileDocuments.find(document); - if (i == end(_fileDocuments)) { - return; - } - auto shared = i->second.lock(); - if (!shared) { - return; - } - const auto till = crl::now() + kKeepAliveTimeout; - const auto j = _keptAlive.find(shared); - if (j != end(_keptAlive)) { - j->second = till; - } else { - _keptAlive.emplace(std::move(shared), till); - } - if (!_keptAliveTimer.isActive()) { - _keptAliveTimer.callOnce(kKeepAliveTimeout); - } + keepAlive(_fileDocuments, document); +} + +void Streaming::keepAlive(not_null photo) { + keepAlive(_photoDocuments, photo); } void Streaming::clearKeptAlive() { diff --git a/Telegram/SourceFiles/data/data_streaming.h b/Telegram/SourceFiles/data/data_streaming.h index 7a30bd7f7..14e863f4a 100644 --- a/Telegram/SourceFiles/data/data_streaming.h +++ b/Telegram/SourceFiles/data/data_streaming.h @@ -51,6 +51,7 @@ public: FileOrigin origin); void keepAlive(not_null document); + void keepAlive(not_null photo); private: void clearKeptAlive(); @@ -69,6 +70,11 @@ private: not_null data, FileOrigin origin); + template + void keepAlive( + base::flat_map, std::weak_ptr> &documents, + not_null data); + const not_null _owner; base::flat_map< diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 43658fda8..9c1c725aa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -99,8 +99,8 @@ Gif::~Gif() { } if (_dataMedia) { _data->owner().keepAlive(base::take(_dataMedia)); + _parent->checkHeavyPart(); } - _parent->checkHeavyPart(); } } @@ -1464,8 +1464,8 @@ void Gif::repaintStreamedContent() { const auto own = activeOwnStreamed(); if (own && !own->frozenFrame.isNull()) { return; - } - if (_parent->delegate()->elementIsGifPaused() && !activeRoundStreamed()) { + } else if (_parent->delegate()->elementIsGifPaused() + && !activeRoundStreamed()) { return; } history()->owner().requestViewRepaint(_parent); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 65dea9c18..a758792ab 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -14,10 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" +#include "media/streaming/media_streaming_instance.h" +#include "media/streaming/media_streaming_player.h" +#include "media/streaming/media_streaming_document.h" #include "main/main_session.h" #include "ui/image/image.h" #include "ui/grouped_layout.h" #include "data/data_session.h" +#include "data/data_streaming.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_file_origin.h" @@ -54,9 +58,15 @@ Photo::Photo( } Photo::~Photo() { - if (_dataMedia) { - _data->owner().keepAlive(base::take(_dataMedia)); - _parent->checkHeavyPart(); + if (_streamed || _dataMedia) { + if (_streamed) { + _data->owner().streaming().keepAlive(_data); + setStreamed(nullptr); + } + if (_dataMedia) { + _data->owner().keepAlive(base::take(_dataMedia)); + _parent->checkHeavyPart(); + } } } @@ -93,10 +103,11 @@ void Photo::dataMediaCreated() const { } bool Photo::hasHeavyPart() const { - return (_dataMedia != nullptr); + return _streamed || _dataMedia; } void Photo::unloadHeavyPart() { + setStreamed(nullptr); _dataMedia = nullptr; } @@ -209,22 +220,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); if (_serviceWidth > 0) { - const auto pix = [&] { - if (const auto large = _dataMedia->image(PhotoSize::Large)) { - return large->pixCircled(_pixw, _pixh); - } else if (const auto thumbnail = _dataMedia->image( - PhotoSize::Thumbnail)) { - return thumbnail->pixBlurredCircled(_pixw, _pixh); - } else if (const auto small = _dataMedia->image( - PhotoSize::Small)) { - return small->pixBlurredCircled(_pixw, _pixh); - } else if (const auto blurred = _dataMedia->thumbnailInline()) { - return blurred->pixBlurredCircled(_pixw, _pixh); - } else { - return QPixmap(); - } - }(); - p.drawPixmap(rthumb.topLeft(), pix); + paintUserpicFrame(p, rthumb.topLeft()); } else { if (bubble) { if (!_caption.isEmpty()) { @@ -320,6 +316,42 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time } } +void Photo::paintUserpicFrame(Painter &p, QPoint photoPosition) const { + const_cast(this)->validateVideo(); + + if (_streamed + && _streamed->player().ready() + && !_streamed->player().videoSize().isEmpty()) { + const auto paused = _parent->delegate()->elementIsGifPaused(); + auto request = ::Media::Streaming::FrameRequest(); + auto size = QSize{ _pixw, _pixh }; + request.outer = size * cIntRetinaFactor(); + request.resize = size * cIntRetinaFactor(); + request.radius = ImageRoundRadius::Ellipse; + p.drawImage(QRect(photoPosition, size), _streamed->frame(request)); + if (!paused) { + _streamed->markFrameShown(); + } + return; + } + const auto pix = [&] { + if (const auto large = _dataMedia->image(PhotoSize::Large)) { + return large->pixCircled(_pixw, _pixh); + } else if (const auto thumbnail = _dataMedia->image( + PhotoSize::Thumbnail)) { + return thumbnail->pixBlurredCircled(_pixw, _pixh); + } else if (const auto small = _dataMedia->image( + PhotoSize::Small)) { + return small->pixBlurredCircled(_pixw, _pixh); + } else if (const auto blurred = _dataMedia->thumbnailInline()) { + return blurred->pixBlurredCircled(_pixw, _pixh); + } else { + return QPixmap(); + } + }(); + p.drawPixmap(photoPosition, pix); +} + TextState Photo::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); @@ -580,6 +612,106 @@ void Photo::validateGroupedCache( *cache = image->pixNoCache(pixWidth, pixHeight, options, width, height); } +bool Photo::createStreamingObjects() { + using namespace ::Media::Streaming; + + setStreamed(std::make_unique( + history()->owner().streaming().sharedDocument( + _data, + _realParent->fullId()), + nullptr)); + _streamed->player().updates( + ) | rpl::start_with_next_error([=](Update &&update) { + handleStreamingUpdate(std::move(update)); + }, [=](Error &&error) { + handleStreamingError(std::move(error)); + }, _streamed->lifetime()); + if (_streamed->ready()) { + streamingReady(base::duplicate(_streamed->info())); + } + if (!_streamed->valid()) { + setStreamed(nullptr); + return false; + } + return true; +} + +void Photo::setStreamed(std::unique_ptr<::Media::Streaming::Instance> value) { + const auto removed = (_streamed && !value); + const auto set = (!_streamed && value); + _streamed = std::move(value); + if (set) { + history()->owner().registerHeavyViewPart(_parent); + } else if (removed) { + _parent->checkHeavyPart(); + } +} + +void Photo::handleStreamingUpdate(::Media::Streaming::Update &&update) { + using namespace ::Media::Streaming; + + update.data.match([&](Information &update) { + streamingReady(std::move(update)); + }, [&](const PreloadedVideo &update) { + }, [&](const UpdateVideo &update) { + repaintStreamedContent(); + }, [&](const PreloadedAudio &update) { + }, [&](const UpdateAudio &update) { + }, [&](const WaitingForData &update) { + }, [&](MutedByOther) { + }, [&](Finished) { + }); +} + +void Photo::handleStreamingError(::Media::Streaming::Error &&error) { + _data->setVideoPlaybackFailed(); + setStreamed(nullptr); +} + +void Photo::repaintStreamedContent() { + /* const auto own = activeOwnStreamed(); + if (own && !own->frozenFrame.isNull()) { + return; + } else */if (_parent->delegate()->elementIsGifPaused()) { + return; + } + history()->owner().requestViewRepaint(_parent); +} + +void Photo::streamingReady(::Media::Streaming::Information &&info) { + history()->owner().requestViewRepaint(_parent); +} + +void Photo::validateVideo() { + if (!_data->videoCanBePlayed()) { + setStreamed(nullptr); + return; + } else if (_streamed) { + return; + } + if (!createStreamingObjects()) { + _data->setVideoPlaybackFailed(); + return; + } + checkStreamedIsStarted(); +} + +void Photo::checkStreamedIsStarted() { + if (!_streamed) { + return; + } else if (_streamed->paused()) { + _streamed->resume(); + } + if (_streamed && !_streamed->active() && !_streamed->failed()) { + const auto position = _data->videoStartPosition(); + auto options = ::Media::Streaming::PlaybackOptions(); + options.position = position; + options.mode = ::Media::Streaming::Mode::Video; + options.loop = true; + _streamed->play(options); + } +} + TextForMimeData Photo::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 679fe2c0b..4402f0a00 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -13,6 +13,15 @@ namespace Data { class PhotoMedia; } // namespace Data +namespace Media { +namespace Streaming { +class Instance; +struct Update; +enum class Error; +struct Information; +} // namespace Streaming +} // namespace Media + namespace HistoryView { class Photo : public File { @@ -104,12 +113,23 @@ private: not_null cacheKey, not_null cache) const; + void validateVideo(); + void setStreamed(std::unique_ptr<::Media::Streaming::Instance> value); + void repaintStreamedContent(); + void checkStreamedIsStarted(); + bool createStreamingObjects(); + void handleStreamingUpdate(::Media::Streaming::Update &&update); + void handleStreamingError(::Media::Streaming::Error &&error); + void streamingReady(::Media::Streaming::Information &&info); + void paintUserpicFrame(Painter &p, QPoint photoPosition) const; + not_null _data; int _serviceWidth = 0; int _pixw = 1; int _pixh = 1; Ui::Text::String _caption; mutable std::shared_ptr _dataMedia; + mutable std::unique_ptr<::Media::Streaming::Instance> _streamed; }; diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index 3d92c159c..3ed369776 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -839,7 +839,7 @@ bool UserpicButton::createStreamingObjects(not_null photo) { : Data::FileOrigin(Data::FileOriginPeerPhoto(_peer->id)); _streamed = std::make_unique( photo->owner().streaming().sharedDocument(photo, origin), - [=] { update(); }); + nullptr); _streamed->player().updates( ) | rpl::start_with_next_error([=](Update &&update) { handleStreamingUpdate(std::move(update)); @@ -892,8 +892,6 @@ void UserpicButton::streamingReady(Media::Streaming::Information &&info) { void UserpicButton::updateVideo() { Expects(_role == Role::OpenPhoto); - using namespace Media::Streaming; - const auto id = _peer->userpicPhotoId(); if (!id) { clearStreaming(); diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index 19546c23a..40236a077 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -26,7 +26,6 @@ class SessionController; namespace Media { namespace Streaming { class Instance; -class Document; struct Update; enum class Error; struct Information; @@ -222,13 +221,13 @@ private: void setCursorInChangeOverlay(bool inOverlay); void updateCursor(); void updateVideo(); + bool showSavedMessages() const; void checkStreamedIsStarted(); bool createStreamingObjects(not_null photo); void clearStreaming(); void handleStreamingUpdate(Media::Streaming::Update &&update); void handleStreamingError(Media::Streaming::Error &&error); void streamingReady(Media::Streaming::Information &&info); - bool showSavedMessages() const; void paintUserpicFrame(Painter &p, QPoint photoPosition); void grabOldUserpic();