diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 9eb5bec587..9f00b64841 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -590,6 +590,8 @@ PRIVATE history/view/history_view_cursor_state.h history/view/history_view_element.cpp history/view/history_view_element.h + history/view/history_view_emoji_interactions.cpp + history/view/history_view_emoji_interactions.h history/view/history_view_empty_list_bubble.cpp history/view/history_view_empty_list_bubble.h history/view/history_view_group_call_tracker.cpp diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp index f182772f78..3a27c2b310 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp @@ -31,6 +31,7 @@ namespace { constexpr auto kMinDelay = crl::time(200); constexpr auto kAccumulateDelay = crl::time(1000); +constexpr auto kMaxDelay = 2 * crl::time(1000); constexpr auto kTimeNever = std::numeric_limits::max(); constexpr auto kVersion = 1; @@ -102,6 +103,14 @@ auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult { auto waitingForDownload = false; for (auto &[item, animations] : _animations) { auto lastStartedAt = crl::time(); + + // Erase too old requests. + const auto i = ranges::find_if(animations, [&](const Animation &a) { + return !a.startedAt && (a.scheduledAt + kMaxDelay <= now); + }); + if (i != end(animations)) { + animations.erase(i, end(animations)); + } for (auto &animation : animations) { if (animation.startedAt) { lastStartedAt = animation.startedAt; @@ -111,7 +120,11 @@ auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult { break; } else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) { animation.startedAt = now; - _playRequests.fire({ item, animation.media }); + _playRequests.fire({ + item, + animation.media, + animation.scheduledAt, + }); break; } else { nearest = std::min(nearest, lastStartedAt + kMinDelay); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h index 830fb7dbc5..d22be9979a 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h @@ -29,6 +29,7 @@ namespace ChatHelpers { struct EmojiInteractionPlayRequest { not_null item; std::shared_ptr media; + crl::time shouldHaveStartedAt = 0; }; class EmojiInteractions final { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e5ef4e1f4e..157205df2a 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -203,9 +203,9 @@ HistoryInner::HistoryInner( _controller->emojiInteractions().playRequests( ) | rpl::filter([=](const PlayRequest &request) { return (request.item->history() == _history); - }) | rpl::start_with_next([=](const PlayRequest &request) { + }) | rpl::start_with_next([=](PlayRequest &&request) { if (const auto view = request.item->mainView()) { - _emojiInteractions->play(request, view); + _emojiInteractions->play(std::move(request), view); } }, lifetime()); _emojiInteractions->updateRequests( diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index a321eaaa3d..411dd79d36 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -26,6 +26,11 @@ namespace { constexpr auto kSizeMultiplier = 3; +constexpr auto kMaxPlays = 5; +constexpr auto kMaxPlaysWithSmallDelay = 3; +constexpr auto kSmallDelay = crl::time(200); +constexpr auto kDropDelayedAfterDelay = crl::time(2000); + [[nodiscard]] QPoint GenerateRandomShift(QSize emoji) { // Random shift in [-0.08 ... 0.08] of animated emoji size. const auto maxShift = emoji * 2 / 25; @@ -54,6 +59,17 @@ EmojiInteractions::~EmojiInteractions() = default; void EmojiInteractions::play( ChatHelpers::EmojiInteractionPlayRequest request, not_null view) { + if (_plays.empty()) { + play(view, std::move(request.media)); + } else { + _delayed.push_back({ view, request.media, crl::now() }); + checkDelayed(); + } +} + +void EmojiInteractions::play( + not_null view, + std::shared_ptr media) { const auto top = view->block()->y() + view->y(); const auto bottom = top + view->height(); if (_visibleTop >= bottom @@ -62,7 +78,7 @@ void EmojiInteractions::play( return; } auto lottie = ChatHelpers::LottiePlayerFromDocument( - request.media.get(), + media.get(), nullptr, ChatHelpers::StickerLottieSize::EmojiInteraction, _emojiSize * kSizeMultiplier * style::DevicePixelRatio(), @@ -123,7 +139,13 @@ void EmojiInteractions::paint(QPainter &p) { request.colored = st::msgStickerOverlay->c; } const auto frame = play.lottie->frameInfo(request); - if (frame.index + 1 == play.lottie->information().framesCount) { + play.frame = frame.index; + if (!play.framesCount) { + const auto &information = play.lottie->information(); + play.framesCount = information.framesCount; + play.frameRate = information.frameRate; + } + if (play.frame + 1 == play.framesCount) { play.finished = true; } const auto rect = computeRect(play.view); @@ -133,6 +155,43 @@ void EmojiInteractions::paint(QPainter &p) { play.lottie->markFrameShown(); } _plays.erase(ranges::remove(_plays, true, &Play::finished), end(_plays)); + checkDelayed(); +} + +void EmojiInteractions::checkDelayed() { + if (_delayed.empty() || _plays.size() >= kMaxPlays) { + return; + } + auto withTooLittleDelay = false; + auto withHalfPlayed = false; + for (const auto &play : _plays) { + if (!play.framesCount + || !play.frameRate + || !play.frame + || (play.frame * crl::time(1000) + < kSmallDelay * play.frameRate)) { + withTooLittleDelay = true; + break; + } else if (play.frame * 2 > play.framesCount) { + withHalfPlayed = true; + } + } + if (withTooLittleDelay) { + return; + } else if (_plays.size() >= kMaxPlaysWithSmallDelay && !withHalfPlayed) { + return; + } + const auto now = crl::now(); + const auto i = ranges::find_if(_delayed, [&](const Delayed &delayed) { + return (delayed.shouldHaveStartedAt + kDropDelayedAfterDelay > now); + }); + if (i == end(_delayed)) { + _delayed.clear(); + return; + } + auto good = std::move(*i); + _delayed.erase(begin(_delayed), i + 1); + play(good.view, std::move(good.media)); } rpl::producer EmojiInteractions::updateRequests() const { diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index dbbcfb5ec7..d45376bd58 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Data { +class DocumentMedia; +} // namespace Data + namespace ChatHelpers { struct EmojiInteractionPlayRequest; } // namespace ChatHelpers @@ -41,11 +45,24 @@ private: not_null view; std::unique_ptr lottie; QPoint shift; + int frame = 0; + int framesCount = 0; + int frameRate = 0; bool finished = false; }; + struct Delayed { + not_null view; + std::shared_ptr media; + crl::time shouldHaveStartedAt = 0; + }; [[nodiscard]] QRect computeRect(not_null view) const; + void play( + not_null view, + std::shared_ptr media); + void checkDelayed(); + const not_null _session; int _visibleTop = 0; @@ -53,6 +70,7 @@ private: QSize _emojiSize; std::vector _plays; + std::vector _delayed; rpl::event_stream _updateRequests; rpl::lifetime _lifetime;