From 5375e7958cddeb3bc7c2c8f92ec91e4f676dd187 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 1 Jul 2019 13:20:53 +0200 Subject: [PATCH] Implement pause/unpause for Lottie::MultiPlayer. --- .../chat_helpers/stickers_list_widget.cpp | 114 +++++++-- .../chat_helpers/stickers_list_widget.h | 3 + .../lottie/lottie_frame_renderer.cpp | 87 ++++--- .../lottie/lottie_frame_renderer.h | 13 +- .../lottie/lottie_multi_player.cpp | 237 +++++++++++++++--- .../SourceFiles/lottie/lottie_multi_player.h | 47 +++- .../lottie/lottie_single_player.cpp | 6 +- .../SourceFiles/lottie/lottie_single_player.h | 1 - 8 files changed, 386 insertions(+), 122 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 22f212bee3..f8b4748e04 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -763,10 +763,11 @@ object_ptr StickersListWidget::createFooter() { void StickersListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { - auto top = getVisibleTop(); Inner::visibleTopBottomUpdated(visibleTop, visibleBottom); if (_section == Section::Featured) { readVisibleSets(); + } else { + pauseInvisibleLottie(); } validateSelectedIcon(ValidateIconAnimations::Full); } @@ -1202,13 +1203,6 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { return false; } auto &set = sets[info.section]; - if (const auto player = set.lottiePlayer.get()) { - const auto paused = controller()->isGifPausedAtLeastFor( - Window::GifPauseReason::SavedGifs); - if (!paused) { - player->markFrameShown(); - } - } if (set.externalLayout) { const auto size = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) @@ -1280,6 +1274,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { auto deleteSelected = false; paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected); } + markLottieFrameShown(set); return true; } if (setHasTitle(set) && clip.top() < info.rowsTop) { @@ -1307,28 +1302,93 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { p.setPen(st::emojiPanHeaderFg); p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), titleText, titleWidth); } - if (clip.top() + clip.height() > info.rowsTop) { - if (set.id == Stickers::MegagroupSetId && set.stickers.empty()) { - auto buttonSelected = (base::get_if(&_selected) != nullptr); - paintMegagroupEmptySet(p, info.rowsTop, buttonSelected); - } else { - auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0; - auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); - auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); - for (int i = fromRow; i < toRow; ++i) { - for (int j = fromColumn; j < toColumn; ++j) { - int index = i * _columnCount + j; - if (index >= info.count) break; + if (clip.top() + clip.height() <= info.rowsTop) { + return true; + } else if (set.id == Stickers::MegagroupSetId && set.stickers.empty()) { + auto buttonSelected = (base::get_if(&_selected) != nullptr); + paintMegagroupEmptySet(p, info.rowsTop, buttonSelected); + return true; + } + auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0; + auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); + auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); + for (int i = fromRow; i < toRow; ++i) { + for (int j = fromColumn; j < toColumn; ++j) { + int index = i * _columnCount + j; + if (index >= info.count) break; - auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; - auto deleteSelected = selected && selectedSticker->overDelete; - paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected); - } + auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; + auto deleteSelected = selected && selectedSticker->overDelete; + paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected); + } + } + markLottieFrameShown(set); + return true; + }); +} + +void StickersListWidget::markLottieFrameShown(Set &set) { + if (const auto player = set.lottiePlayer.get()) { + const auto paused = controller()->isGifPausedAtLeastFor( + Window::GifPauseReason::SavedGifs); + if (!paused) { + player->markFrameShown(); + } + } +} + +void StickersListWidget::pauseInvisibleLottie() { + if (shownSets().empty()) { + return; + } + const auto visibleBottom = getVisibleBottom(); + const auto top = sectionInfoByOffset(getVisibleTop()); + pauseInvisibleLottieIn(top); + if (top.rowsBottom < visibleBottom) { + pauseInvisibleLottieIn(sectionInfoByOffset(visibleBottom)); + } +} + +void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) { + auto &set = shownSets()[info.section]; + const auto player = set.lottiePlayer.get(); + if (!player) { + return; + } + const auto pauseInRows = [&](int fromRow, int tillRow) { + Expects(fromRow <= tillRow); + + for (auto i = fromRow; i != tillRow; ++i) { + for (auto j = 0; j != _columnCount; ++j) { + const auto index = i * _columnCount + j; + if (index >= info.count) { + break; + } + if (const auto animated = set.stickers[index].animated) { + player->pause(animated); } } } - return true; - }); + }; + + const auto visibleTop = getVisibleTop(); + const auto visibleBottom = getVisibleBottom(); + if (visibleTop >= info.rowsTop + _singleSize.height() + && visibleTop < info.rowsBottom) { + const auto pauseHeight = (visibleTop - info.rowsTop); + const auto pauseRows = std::min( + pauseHeight / _singleSize.height(), + info.rowsCount); + pauseInRows(0, pauseRows); + } + if (visibleBottom > info.rowsTop + && visibleBottom + _singleSize.height() <= info.rowsBottom) { + const auto pauseHeight = (info.rowsBottom - visibleBottom); + const auto pauseRows = std::min( + pauseHeight / _singleSize.height(), + info.rowsCount); + pauseInRows(info.rowsCount - pauseRows, info.rowsCount); + } } void StickersListWidget::paintEmptySearchResults(Painter &p) { @@ -1452,6 +1512,8 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, p.drawImage( QRect(ppos, frame.size() / cIntRetinaFactor()), frame); + + set.lottiePlayer->unpause(sticker.animated); } else if (const auto image = document->getStickerSmall()) { if (image->loaded()) { p.drawPixmapLeft( diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 08eecaa785..aa52b56bc2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -229,6 +229,9 @@ private: void ensureLottiePlayer(Set &set); void setupLottie(Set &set, int section, int index); + void markLottieFrameShown(Set &set); + void pauseInvisibleLottie(); + void pauseInvisibleLottieIn(const SectionInfo &info); int stickersRight() const; bool featuredHasAddButton(int index) const; diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp index cb7eaf11bc..04fba3b978 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp @@ -25,8 +25,6 @@ QImage prepareColored(QColor add, QImage image); namespace Lottie { namespace { -constexpr auto kDisplaySkipped = crl::time(-1); - std::weak_ptr GlobalInstance; constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; @@ -300,11 +298,6 @@ void SharedState::init(QImage cover, const FrameRequest &request) { _frames[0].request = request; _frames[0].original = std::move(cover); - _frames[0].position = 0; - - // Usually main thread sets displayed time before _counter increment. - // But in this case we update _counter, so we set a fake displayed time. - _frames[0].displayed = kDisplaySkipped; } void SharedState::start( @@ -316,14 +309,11 @@ void SharedState::start( _started = started; _delay = delay; _skippedFrames = skippedFrames; - - _frames[0].position = currentFramePosition(); _counter.store(0, std::memory_order_release); } bool IsRendered(not_null frame) { - return (frame->position != kTimeUnknown) - && (frame->displayed == kTimeUnknown); + return (frame->displayed == kTimeUnknown); } void SharedState::renderNextFrame( @@ -332,15 +322,12 @@ void SharedState::renderNextFrame( Expects(_framesCount > 0); renderFrame(frame->original, request, (++_frameIndex) % _framesCount); + frame->request = request; PrepareFrameByRequest(frame); - frame->position = currentFramePosition(); + frame->index = _frameIndex; frame->displayed = kTimeUnknown; } -crl::time SharedState::currentFramePosition() const { - return crl::time(1000) * (_skippedFrames + _frameIndex) / _frameRate; -} - auto SharedState::renderNextFrame(const FrameRequest &request) -> RenderResult { const auto prerender = [&](int index) -> RenderResult { @@ -360,7 +347,8 @@ auto SharedState::renderNextFrame(const FrameRequest &request) if (!IsRendered(frame)) { renderNextFrame(frame, request); } - frame->display = _started + _delay + frame->position; + frame->display = countFrameDisplayTime(frame->index); + PROFILE_LOG(("DISPLAY AT: %1 (STARTED %2, DELAY %3, FRAME: %4, RATE: %5, {SKIPPED %6, INDEX: %7})").arg(frame->display).arg(_started).arg(_delay).arg(_skippedFrames + frame->index).arg(_frameRate).arg(_skippedFrames).arg(frame->index)); // Release this frame to the main thread for rendering. _counter.store( @@ -382,6 +370,12 @@ auto SharedState::renderNextFrame(const FrameRequest &request) Unexpected("Counter value in Lottie::SharedState::renderNextFrame."); } +crl::time SharedState::countFrameDisplayTime(int index) const { + return _started + + _delay + + crl::time(1000) * (_skippedFrames + index) / _frameRate; +} + int SharedState::counter() const { return _counter.load(std::memory_order_acquire); } @@ -416,7 +410,6 @@ Information SharedState::information() const { not_null SharedState::frameForPaint() { const auto result = getFrame(counter() / 2); Assert(!result->original.isNull()); - Assert(result->position != kTimeUnknown); Assert(result->displayed != kTimeUnknown); return result; @@ -450,27 +443,48 @@ crl::time SharedState::nextFrameDisplayTime() const { Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); } -void SharedState::addTimelineDelay(crl::time delayed) { - if (!delayed) { +void SharedState::addTimelineDelay(crl::time delayed, int skippedFrames) { + if (!delayed && !skippedFrames) { return; } - Assert(counter() % 2 == 1); - _delay += delayed; + const auto recountCurrentFrame = [&](int counter) { + _delay += delayed; + _skippedFrames += skippedFrames; + + const auto next = (counter + 1) % (2 * kFramesCount); + const auto index = next / 2; + const auto frame = getFrame(index); + if (frame->displayed != kTimeUnknown) { + // Frame already displayed. + return; + } + Assert(IsRendered(frame)); + Assert(frame->display != kTimeUnknown); + frame->display = countFrameDisplayTime(frame->index); + }; + + switch (counter()) { + case 0: Unexpected("Value 0 in SharedState::addTimelineDelay."); + case 1: return recountCurrentFrame(1); + case 2: Unexpected("Value 2 in SharedState::addTimelineDelay."); + case 3: return recountCurrentFrame(3); + case 4: Unexpected("Value 4 in SharedState::addTimelineDelay."); + case 5: return recountCurrentFrame(5); + case 6: Unexpected("Value 6 in SharedState::addTimelineDelay."); + case 7: return recountCurrentFrame(7); + } + Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); } -crl::time SharedState::markFrameDisplayed(crl::time now) { +void SharedState::markFrameDisplayed(crl::time now) { const auto mark = [&](int counter) { const auto next = (counter + 1) % (2 * kFramesCount); const auto index = next / 2; const auto frame = getFrame(index); - Assert(frame->position != kTimeUnknown); - - if (frame->displayed != kTimeUnknown) { - return kTimeUnknown; + if (frame->displayed == kTimeUnknown) { + frame->displayed = now; } - frame->displayed = now; - return frame->position; }; switch (counter()) { @@ -486,29 +500,28 @@ crl::time SharedState::markFrameDisplayed(crl::time now) { Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed."); } -crl::time SharedState::markFrameShown() { +bool SharedState::markFrameShown() { const auto jump = [&](int counter) { const auto next = (counter + 1) % (2 * kFramesCount); const auto index = next / 2; const auto frame = getFrame(index); - Assert(frame->position != kTimeUnknown); if (frame->displayed == kTimeUnknown) { - return kTimeUnknown; + return false; } _counter.store( next, std::memory_order_release); - return frame->position; + return true; }; switch (counter()) { - case 0: return kTimeUnknown; + case 0: return false; case 1: return jump(1); - case 2: return kTimeUnknown; + case 2: return false; case 3: return jump(3); - case 4: return kTimeUnknown; + case 4: return false; case 5: return jump(5); - case 6: return kTimeUnknown; + case 6: return false; case 7: return jump(7); } Unexpected("Counter value in Lottie::SharedState::markFrameShown."); diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h index efc3143ac9..ed634a55cd 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h @@ -28,15 +28,16 @@ inline constexpr auto kMaxSize = 3096; inline constexpr auto kMaxFramesCount = 600; inline constexpr auto kFrameDisplayTimeAlreadyDone = std::numeric_limits::max(); +inline constexpr auto kDisplayedInitial = crl::time(-1); class Player; class Cache; struct Frame { QImage original; - crl::time position = kTimeUnknown; - crl::time displayed = kTimeUnknown; + crl::time displayed = kDisplayedInitial; crl::time display = kTimeUnknown; + int index = 0; FrameRequest request; QImage prepared; @@ -68,9 +69,9 @@ public: [[nodiscard]] not_null frameForPaint(); [[nodiscard]] crl::time nextFrameDisplayTime() const; - void addTimelineDelay(crl::time delayed); - crl::time markFrameDisplayed(crl::time now); - crl::time markFrameShown(); + void addTimelineDelay(crl::time delayed, int skippedFrames = 0); + void markFrameDisplayed(crl::time now); + bool markFrameShown(); void renderFrame(QImage &image, const FrameRequest &request, int index); @@ -90,10 +91,10 @@ private: void renderNextFrame( not_null frame, const FrameRequest &request); + [[nodiscard]] crl::time countFrameDisplayTime(int index) const; [[nodiscard]] not_null getFrame(int index); [[nodiscard]] not_null getFrame(int index) const; [[nodiscard]] int counter() const; - [[nodiscard]] crl::time currentFramePosition() const; QByteArray _content; std::unique_ptr _animation; diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp b/Telegram/SourceFiles/lottie/lottie_multi_player.cpp index f0b5d1d41d..c46bff8338 100644 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_multi_player.cpp @@ -32,6 +32,9 @@ MultiPlayer::~MultiPlayer() { for (const auto &[animation, state] : _active) { _renderer->remove(state); } + for (const auto &[animation, info] : _paused) { + _renderer->remove(info.state); + } } not_null MultiPlayer::append( @@ -58,22 +61,35 @@ not_null MultiPlayer::append( return _animations.back().get(); } -void MultiPlayer::startAtRightTime(not_null state) { - Expects(!_active.empty()); - Expects((_active.size() == 1) == (_started == kTimeUnknown)); - - const auto now = crl::now(); - if (_active.size() == 1) { - _started = now; +void MultiPlayer::startAtRightTime(std::unique_ptr state) { + if (_started == kTimeUnknown) { + _started = crl::now(); + _lastSyncTime = kTimeUnknown; + _delay = 0; } + const auto lastSyncTime = (_lastSyncTime != kTimeUnknown) + ? _lastSyncTime + : _started; + const auto frameIndex = countFrameIndex( + state.get(), + lastSyncTime, + _delay); + state->start(this, _started, _delay, frameIndex); + + _renderer->append(std::move(state)); +} + +int MultiPlayer::countFrameIndex( + not_null state, + crl::time time, + crl::time delay) const { + Expects(time != kTimeUnknown); const auto rate = state->information().frameRate; Assert(rate != 0); - const auto started = _started + _delay; - const auto skipFrames = (now - started) * rate / 1000; - - state->start(this, _started, _delay, skipFrames); + const auto framesTime = time - _started - delay; + return ((framesTime + 1) * rate - 1) / 1000; } void MultiPlayer::start( @@ -81,39 +97,86 @@ void MultiPlayer::start( std::unique_ptr state) { Expects(state != nullptr); - if (_nextFrameTime == kTimeUnknown) { - appendToActive(animation, std::move(state)); + const auto paused = _pausedBeforeStart.remove(animation); + auto info = StartingInfo{ std::move(state), paused }; + if (_active.empty() + || (_lastSyncTime == kTimeUnknown + && _nextFrameTime == kTimeUnknown)) { + startBeforeLifeCycle(animation, std::move(info)); } else { // We always try to mark as shown at the same time, so we start a new // animation at the same time we mark all existing as shown. - _pendingToStart.emplace(animation, std::move(state)); + _pendingToStart.emplace(animation, std::move(info)); } -} - -void MultiPlayer::appendPendingToActive() { - for (auto &[animation, state] : base::take(_pendingToStart)) { - appendToActive(animation, std::move(state)); - } -} - -void MultiPlayer::appendToActive( - not_null animation, - std::unique_ptr state) { - Expects(_nextFrameTime == kTimeUnknown); - - _active.emplace(animation, state.get()); - - auto information = state->information(); - startAtRightTime(state.get()); - _renderer->append(std::move(state)); _updates.fire({}); } +void MultiPlayer::startBeforeLifeCycle( + not_null animation, + StartingInfo &&info) { + _active.emplace(animation, info.state.get()); + startAtRightTime(std::move(info.state)); + if (info.paused) { + _pendingPause.emplace(animation); + } +} + +void MultiPlayer::startInsideLifeCycle( + not_null animation, + StartingInfo &&info) { + const auto state = info.state.get(); + if (info.paused) { + _paused.emplace( + animation, + PausedInfo{ state, _lastSyncTime, _delay }); + } else { + _active.emplace(animation, state); + } + startAtRightTime(std::move(info.state)); +} + +void MultiPlayer::processPending() { + Expects(_lastSyncTime != kTimeUnknown); + + for (const auto &animation : base::take(_pendingPause)) { + pauseAndSaveState(animation); + } + for (const auto &animation : base::take(_pendingUnpause)) { + unpauseAndKeepUp(animation); + } + for (auto &[animation, info] : base::take(_pendingToStart)) { + startInsideLifeCycle(animation, std::move(info)); + } + for (const auto &animation : base::take(_pendingRemove)) { + removeNow(animation); + } +} + void MultiPlayer::remove(not_null animation) { + if (!_active.empty()) { + _pendingRemove.emplace(animation); + } else { + removeNow(animation); + } +} + +void MultiPlayer::removeNow(not_null animation) { const auto i = _active.find(animation); if (i != end(_active)) { _renderer->remove(i->second); + _active.erase(i); } + const auto j = _paused.find(animation); + if (j != end(_paused)) { + _renderer->remove(j->second.state); + _paused.erase(j); + } + + _pendingRemove.remove(animation); + _pendingToStart.remove(animation); + _pendingPause.remove(animation); + _pendingUnpause.remove(animation); + _pausedBeforeStart.remove(animation); _animations.erase( ranges::remove( _animations, @@ -122,13 +185,95 @@ void MultiPlayer::remove(not_null animation) { end(_animations)); if (_active.empty()) { - _started = kTimeUnknown; - _delay = 0; _nextFrameTime = kTimeUnknown; _timer.cancel(); + if (_paused.empty()) { + _started = kTimeUnknown; + _lastSyncTime = kTimeUnknown; + _delay = 0; + } } } +void MultiPlayer::pause(not_null animation) { + if (_active.contains(animation)) { + _pendingPause.emplace(animation); + } else if (_paused.contains(animation)) { + _pendingUnpause.remove(animation); + } else if (const auto i = _pendingToStart.find(animation); i != end(_pendingToStart)) { + i->second.paused = true; + } else { + _pausedBeforeStart.emplace(animation); + } +} + +void MultiPlayer::unpause(not_null animation) { + if (const auto i = _paused.find(animation); i != end(_paused)) { + if (_active.empty()) { + unpauseFirst(animation, i->second.state); + _paused.erase(i); + } else { + _pendingUnpause.emplace(animation); + } + } else if (_pendingPause.contains(animation)) { + _pendingPause.remove(animation); + } else { + const auto i = _pendingToStart.find(animation); + if (i != end(_pendingToStart)) { + i->second.paused = false; + } else { + _pausedBeforeStart.remove(animation); + } + } +} + +void MultiPlayer::unpauseFirst( + not_null animation, + not_null state) { + Expects(_lastSyncTime != kTimeUnknown); + + _active.emplace(animation, state); + + const auto now = crl::now(); + addTimelineDelay(now - _lastSyncTime); + _lastSyncTime = now; + + markFrameShown(); +} + +void MultiPlayer::pauseAndSaveState(not_null animation) { + Expects(_lastSyncTime != kTimeUnknown); + + const auto i = _active.find(animation); + Assert(i != end(_active)); + _paused.emplace( + animation, + PausedInfo{ i->second, _lastSyncTime, _delay }); + _active.erase(i); +} + +void MultiPlayer::unpauseAndKeepUp(not_null animation) { + Expects(_lastSyncTime != kTimeUnknown); + + const auto i = _paused.find(animation); + Assert(i != end(_paused)); + const auto state = i->second.state; + const auto frameIndexAtPaused = countFrameIndex( + state, + i->second.pauseTime, + i->second.pauseDelay); + const auto frameIndexNow = countFrameIndex( + state, + _lastSyncTime, + _delay); + PROFILE_LOG(("UNPAUSED WITH %1 DELAY AND %2 SKIPPED FRAMES").arg(_delay - i->second.pauseDelay).arg(frameIndexNow - frameIndexAtPaused)); + state->addTimelineDelay( + (_delay - i->second.pauseDelay), + frameIndexNow - frameIndexAtPaused); + _active.emplace(animation, state); + _paused.erase(i); +} + void MultiPlayer::failed(not_null animation, Error error) { //_updates.fire({ animation, error }); } @@ -191,8 +336,9 @@ void MultiPlayer::checkNextFrameRender() { markFrameDisplayed(now); addTimelineDelay(now - _nextFrameTime); - + _lastSyncTime = now; _nextFrameTime = kFrameDisplayTimeAlreadyDone; + processPending(); _updates.fire({}); } } @@ -200,10 +346,20 @@ void MultiPlayer::checkNextFrameRender() { void MultiPlayer::updateFrameRequest( not_null animation, const FrameRequest &request) { - const auto i = _active.find(animation); - Assert(i != _active.end()); - - _renderer->updateFrameRequest(i->second, request); + const auto state = [&] { + const auto key = animation; + if (const auto i = _active.find(animation); i != end(_active)) { + return i->second.get(); + } else if (const auto j = _paused.find(animation); + j != end(_paused)) { + return j->second.state.get(); + } else if (const auto k = _pendingToStart.find(animation); + k != end(_pendingToStart)) { + return k->second.state.get(); + } + Unexpected("Animation in MultiPlayer::updateFrameRequest."); + }(); + _renderer->updateFrameRequest(state, request); } void MultiPlayer::markFrameDisplayed(crl::time now) { @@ -238,11 +394,10 @@ void MultiPlayer::addTimelineDelay(crl::time delayed) { void MultiPlayer::markFrameShown() { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { _nextFrameTime = kTimeUnknown; - appendPendingToActive(); } auto count = 0; for (const auto &[animation, state] : _active) { - if (state->markFrameShown() != kTimeUnknown) { + if (state->markFrameShown()) { ++count; } } diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.h b/Telegram/SourceFiles/lottie/lottie_multi_player.h index 53be168519..ba7e7818e9 100644 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.h +++ b/Telegram/SourceFiles/lottie/lottie_multi_player.h @@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_player.h" #include "base/timer.h" #include "base/algorithm.h" +#include "base/flat_set.h" +#include "base/flat_map.h" #include @@ -55,26 +57,55 @@ public: void remove(not_null animation); + void pause(not_null animation); + void unpause(not_null animation); + private: - void appendToActive( + struct PausedInfo { + not_null state; + crl::time pauseTime = kTimeUnknown; + crl::time pauseDelay = kTimeUnknown; + }; + struct StartingInfo { + std::unique_ptr state; + bool paused = false; + }; + + void startBeforeLifeCycle( not_null animation, - std::unique_ptr state); - void startAtRightTime(not_null state); - void appendPendingToActive(); + StartingInfo &&info); + void startInsideLifeCycle( + not_null animation, + StartingInfo &&info); + [[nodiscard]] int countFrameIndex( + not_null state, + crl::time time, + crl::time delay) const; + void startAtRightTime(std::unique_ptr state); + void processPending(); void markFrameDisplayed(crl::time now); void addTimelineDelay(crl::time delayed); void checkNextFrameAvailability(); void checkNextFrameRender(); + void unpauseFirst( + not_null animation, + not_null state); + void pauseAndSaveState(not_null animation); + void unpauseAndKeepUp(not_null animation); + void removeNow(not_null animation); base::Timer _timer; const std::shared_ptr _renderer; std::vector> _animations; base::flat_map, not_null> _active; - base::flat_map< - not_null, - std::unique_ptr> _pendingToStart; - //base::flat_map, not_null> _paused; + base::flat_map, PausedInfo> _paused; + base::flat_set> _pendingPause; + base::flat_set> _pendingUnpause; + base::flat_set> _pausedBeforeStart; + base::flat_set> _pendingRemove; + base::flat_map, StartingInfo> _pendingToStart; crl::time _started = kTimeUnknown; + crl::time _lastSyncTime = kTimeUnknown; crl::time _delay = 0; crl::time _nextFrameTime = kTimeUnknown; rpl::event_stream _updates; diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.cpp b/Telegram/SourceFiles/lottie/lottie_single_player.cpp index c7cc5e071c..aaa0e4a1d1 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_single_player.cpp @@ -102,11 +102,11 @@ void SinglePlayer::checkNextFrameRender() { } else { _timer.cancel(); - const auto position = _state->markFrameDisplayed(now); + _state->markFrameDisplayed(now); _state->addTimelineDelay(now - _nextFrameTime); _nextFrameTime = kFrameDisplayTimeAlreadyDone; - _updates.fire({ DisplayFrameRequest{ position } }); + _updates.fire({ DisplayFrameRequest() }); } } @@ -125,7 +125,7 @@ void SinglePlayer::markFrameShown() { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { _nextFrameTime = kTimeUnknown; } - if (_state->markFrameShown() != kTimeUnknown) { + if (_state->markFrameShown()) { _renderer->frameShown(); } } diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.h b/Telegram/SourceFiles/lottie/lottie_single_player.h index fe4f41983a..0afe6c2d35 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.h +++ b/Telegram/SourceFiles/lottie/lottie_single_player.h @@ -18,7 +18,6 @@ namespace Lottie { class FrameRenderer; struct DisplayFrameRequest { - crl::time time = 0; }; struct Update {