Implement pause/unpause for Lottie::MultiPlayer.

This commit is contained in:
John Preston 2019-07-01 13:20:53 +02:00
parent 1da5d1c64f
commit 5375e7958c
8 changed files with 386 additions and 122 deletions

View File

@ -763,10 +763,11 @@ object_ptr<TabbedSelector::InnerFooter> 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<OverGroupAdd>(&_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<OverGroupAdd>(&_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(

View File

@ -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;

View File

@ -25,8 +25,6 @@ QImage prepareColored(QColor add, QImage image);
namespace Lottie {
namespace {
constexpr auto kDisplaySkipped = crl::time(-1);
std::weak_ptr<FrameRenderer> 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<const Frame*> 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<Frame*> 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.");

View File

@ -28,15 +28,16 @@ inline constexpr auto kMaxSize = 3096;
inline constexpr auto kMaxFramesCount = 600;
inline constexpr auto kFrameDisplayTimeAlreadyDone
= std::numeric_limits<crl::time>::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<Frame*> 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*> frame,
const FrameRequest &request);
[[nodiscard]] crl::time countFrameDisplayTime(int index) const;
[[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const;
[[nodiscard]] crl::time currentFramePosition() const;
QByteArray _content;
std::unique_ptr<rlottie::Animation> _animation;

View File

@ -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<Animation*> MultiPlayer::append(
@ -58,22 +61,35 @@ not_null<Animation*> MultiPlayer::append(
return _animations.back().get();
}
void MultiPlayer::startAtRightTime(not_null<SharedState*> 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<SharedState> 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<SharedState*> 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<SharedState> 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*> animation,
std::unique_ptr<SharedState> 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*> 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*> 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*> animation) {
if (!_active.empty()) {
_pendingRemove.emplace(animation);
} else {
removeNow(animation);
}
}
void MultiPlayer::removeNow(not_null<Animation*> 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*> 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*> 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*> 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*> animation,
not_null<SharedState*> 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*> 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*> 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*> 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<const Animation*> 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;
}
}

View File

@ -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 <rpl/event_stream.h>
@ -55,26 +57,55 @@ public:
void remove(not_null<Animation*> animation);
void pause(not_null<Animation*> animation);
void unpause(not_null<Animation*> animation);
private:
void appendToActive(
struct PausedInfo {
not_null<SharedState*> state;
crl::time pauseTime = kTimeUnknown;
crl::time pauseDelay = kTimeUnknown;
};
struct StartingInfo {
std::unique_ptr<SharedState> state;
bool paused = false;
};
void startBeforeLifeCycle(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state);
void startAtRightTime(not_null<SharedState*> state);
void appendPendingToActive();
StartingInfo &&info);
void startInsideLifeCycle(
not_null<Animation*> animation,
StartingInfo &&info);
[[nodiscard]] int countFrameIndex(
not_null<SharedState*> state,
crl::time time,
crl::time delay) const;
void startAtRightTime(std::unique_ptr<SharedState> state);
void processPending();
void markFrameDisplayed(crl::time now);
void addTimelineDelay(crl::time delayed);
void checkNextFrameAvailability();
void checkNextFrameRender();
void unpauseFirst(
not_null<Animation*> animation,
not_null<SharedState*> state);
void pauseAndSaveState(not_null<Animation*> animation);
void unpauseAndKeepUp(not_null<Animation*> animation);
void removeNow(not_null<Animation*> animation);
base::Timer _timer;
const std::shared_ptr<FrameRenderer> _renderer;
std::vector<std::unique_ptr<Animation>> _animations;
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
base::flat_map<
not_null<Animation*>,
std::unique_ptr<SharedState>> _pendingToStart;
//base::flat_map<not_null<Animation*>, not_null<SharedState*>> _paused;
base::flat_map<not_null<Animation*>, PausedInfo> _paused;
base::flat_set<not_null<Animation*>> _pendingPause;
base::flat_set<not_null<Animation*>> _pendingUnpause;
base::flat_set<not_null<Animation*>> _pausedBeforeStart;
base::flat_set<not_null<Animation*>> _pendingRemove;
base::flat_map<not_null<Animation*>, StartingInfo> _pendingToStart;
crl::time _started = kTimeUnknown;
crl::time _lastSyncTime = kTimeUnknown;
crl::time _delay = 0;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<MultiUpdate> _updates;

View File

@ -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();
}
}

View File

@ -18,7 +18,6 @@ namespace Lottie {
class FrameRenderer;
struct DisplayFrameRequest {
crl::time time = 0;
};
struct Update {