mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Implement pause/unpause for Lottie::MultiPlayer.
This commit is contained in:
parent
1da5d1c64f
commit
5375e7958c
8 changed files with 386 additions and 122 deletions
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ namespace Lottie {
|
|||
class FrameRenderer;
|
||||
|
||||
struct DisplayFrameRequest {
|
||||
crl::time time = 0;
|
||||
};
|
||||
|
||||
struct Update {
|
||||
|
|
Loading…
Add table
Reference in a new issue