mirror of https://github.com/procxx/kepka.git
Implement pause/unpause for Lottie::MultiPlayer.
This commit is contained in:
parent
1da5d1c64f
commit
5375e7958c
|
@ -763,10 +763,11 @@ object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||||
void StickersListWidget::visibleTopBottomUpdated(
|
void StickersListWidget::visibleTopBottomUpdated(
|
||||||
int visibleTop,
|
int visibleTop,
|
||||||
int visibleBottom) {
|
int visibleBottom) {
|
||||||
auto top = getVisibleTop();
|
|
||||||
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
|
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
|
||||||
if (_section == Section::Featured) {
|
if (_section == Section::Featured) {
|
||||||
readVisibleSets();
|
readVisibleSets();
|
||||||
|
} else {
|
||||||
|
pauseInvisibleLottie();
|
||||||
}
|
}
|
||||||
validateSelectedIcon(ValidateIconAnimations::Full);
|
validateSelectedIcon(ValidateIconAnimations::Full);
|
||||||
}
|
}
|
||||||
|
@ -1202,13 +1203,6 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto &set = sets[info.section];
|
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) {
|
if (set.externalLayout) {
|
||||||
const auto size = (set.flags
|
const auto size = (set.flags
|
||||||
& MTPDstickerSet_ClientFlag::f_not_loaded)
|
& MTPDstickerSet_ClientFlag::f_not_loaded)
|
||||||
|
@ -1280,6 +1274,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||||
auto deleteSelected = false;
|
auto deleteSelected = false;
|
||||||
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected);
|
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected);
|
||||||
}
|
}
|
||||||
|
markLottieFrameShown(set);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (setHasTitle(set) && clip.top() < info.rowsTop) {
|
if (setHasTitle(set) && clip.top() < info.rowsTop) {
|
||||||
|
@ -1307,28 +1302,93 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||||
p.setPen(st::emojiPanHeaderFg);
|
p.setPen(st::emojiPanHeaderFg);
|
||||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), titleText, titleWidth);
|
p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), titleText, titleWidth);
|
||||||
}
|
}
|
||||||
if (clip.top() + clip.height() > info.rowsTop) {
|
if (clip.top() + clip.height() <= info.rowsTop) {
|
||||||
if (set.id == Stickers::MegagroupSetId && set.stickers.empty()) {
|
return true;
|
||||||
auto buttonSelected = (base::get_if<OverGroupAdd>(&_selected) != nullptr);
|
} else if (set.id == Stickers::MegagroupSetId && set.stickers.empty()) {
|
||||||
paintMegagroupEmptySet(p, info.rowsTop, buttonSelected);
|
auto buttonSelected = (base::get_if<OverGroupAdd>(&_selected) != nullptr);
|
||||||
} else {
|
paintMegagroupEmptySet(p, info.rowsTop, buttonSelected);
|
||||||
auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0;
|
return true;
|
||||||
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);
|
auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0;
|
||||||
for (int i = fromRow; i < toRow; ++i) {
|
auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
|
||||||
for (int j = fromColumn; j < toColumn; ++j) {
|
auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
|
||||||
int index = i * _columnCount + j;
|
for (int i = fromRow; i < toRow; ++i) {
|
||||||
if (index >= info.count) break;
|
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 selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
|
||||||
auto deleteSelected = selected && selectedSticker->overDelete;
|
auto deleteSelected = selected && selectedSticker->overDelete;
|
||||||
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected);
|
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) {
|
void StickersListWidget::paintEmptySearchResults(Painter &p) {
|
||||||
|
@ -1452,6 +1512,8 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
||||||
frame);
|
frame);
|
||||||
|
|
||||||
|
set.lottiePlayer->unpause(sticker.animated);
|
||||||
} else if (const auto image = document->getStickerSmall()) {
|
} else if (const auto image = document->getStickerSmall()) {
|
||||||
if (image->loaded()) {
|
if (image->loaded()) {
|
||||||
p.drawPixmapLeft(
|
p.drawPixmapLeft(
|
||||||
|
|
|
@ -229,6 +229,9 @@ private:
|
||||||
|
|
||||||
void ensureLottiePlayer(Set &set);
|
void ensureLottiePlayer(Set &set);
|
||||||
void setupLottie(Set &set, int section, int index);
|
void setupLottie(Set &set, int section, int index);
|
||||||
|
void markLottieFrameShown(Set &set);
|
||||||
|
void pauseInvisibleLottie();
|
||||||
|
void pauseInvisibleLottieIn(const SectionInfo &info);
|
||||||
|
|
||||||
int stickersRight() const;
|
int stickersRight() const;
|
||||||
bool featuredHasAddButton(int index) const;
|
bool featuredHasAddButton(int index) const;
|
||||||
|
|
|
@ -25,8 +25,6 @@ QImage prepareColored(QColor add, QImage image);
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kDisplaySkipped = crl::time(-1);
|
|
||||||
|
|
||||||
std::weak_ptr<FrameRenderer> GlobalInstance;
|
std::weak_ptr<FrameRenderer> GlobalInstance;
|
||||||
|
|
||||||
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
|
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].request = request;
|
||||||
_frames[0].original = std::move(cover);
|
_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(
|
void SharedState::start(
|
||||||
|
@ -316,14 +309,11 @@ void SharedState::start(
|
||||||
_started = started;
|
_started = started;
|
||||||
_delay = delay;
|
_delay = delay;
|
||||||
_skippedFrames = skippedFrames;
|
_skippedFrames = skippedFrames;
|
||||||
|
|
||||||
_frames[0].position = currentFramePosition();
|
|
||||||
_counter.store(0, std::memory_order_release);
|
_counter.store(0, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsRendered(not_null<const Frame*> frame) {
|
bool IsRendered(not_null<const Frame*> frame) {
|
||||||
return (frame->position != kTimeUnknown)
|
return (frame->displayed == kTimeUnknown);
|
||||||
&& (frame->displayed == kTimeUnknown);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SharedState::renderNextFrame(
|
void SharedState::renderNextFrame(
|
||||||
|
@ -332,15 +322,12 @@ void SharedState::renderNextFrame(
|
||||||
Expects(_framesCount > 0);
|
Expects(_framesCount > 0);
|
||||||
|
|
||||||
renderFrame(frame->original, request, (++_frameIndex) % _framesCount);
|
renderFrame(frame->original, request, (++_frameIndex) % _framesCount);
|
||||||
|
frame->request = request;
|
||||||
PrepareFrameByRequest(frame);
|
PrepareFrameByRequest(frame);
|
||||||
frame->position = currentFramePosition();
|
frame->index = _frameIndex;
|
||||||
frame->displayed = kTimeUnknown;
|
frame->displayed = kTimeUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time SharedState::currentFramePosition() const {
|
|
||||||
return crl::time(1000) * (_skippedFrames + _frameIndex) / _frameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SharedState::renderNextFrame(const FrameRequest &request)
|
auto SharedState::renderNextFrame(const FrameRequest &request)
|
||||||
-> RenderResult {
|
-> RenderResult {
|
||||||
const auto prerender = [&](int index) -> RenderResult {
|
const auto prerender = [&](int index) -> RenderResult {
|
||||||
|
@ -360,7 +347,8 @@ auto SharedState::renderNextFrame(const FrameRequest &request)
|
||||||
if (!IsRendered(frame)) {
|
if (!IsRendered(frame)) {
|
||||||
renderNextFrame(frame, request);
|
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.
|
// Release this frame to the main thread for rendering.
|
||||||
_counter.store(
|
_counter.store(
|
||||||
|
@ -382,6 +370,12 @@ auto SharedState::renderNextFrame(const FrameRequest &request)
|
||||||
Unexpected("Counter value in Lottie::SharedState::renderNextFrame.");
|
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 {
|
int SharedState::counter() const {
|
||||||
return _counter.load(std::memory_order_acquire);
|
return _counter.load(std::memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
@ -416,7 +410,6 @@ Information SharedState::information() const {
|
||||||
not_null<Frame*> SharedState::frameForPaint() {
|
not_null<Frame*> SharedState::frameForPaint() {
|
||||||
const auto result = getFrame(counter() / 2);
|
const auto result = getFrame(counter() / 2);
|
||||||
Assert(!result->original.isNull());
|
Assert(!result->original.isNull());
|
||||||
Assert(result->position != kTimeUnknown);
|
|
||||||
Assert(result->displayed != kTimeUnknown);
|
Assert(result->displayed != kTimeUnknown);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -450,27 +443,48 @@ crl::time SharedState::nextFrameDisplayTime() const {
|
||||||
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SharedState::addTimelineDelay(crl::time delayed) {
|
void SharedState::addTimelineDelay(crl::time delayed, int skippedFrames) {
|
||||||
if (!delayed) {
|
if (!delayed && !skippedFrames) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(counter() % 2 == 1);
|
const auto recountCurrentFrame = [&](int counter) {
|
||||||
_delay += delayed;
|
_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 mark = [&](int counter) {
|
||||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||||
const auto index = next / 2;
|
const auto index = next / 2;
|
||||||
const auto frame = getFrame(index);
|
const auto frame = getFrame(index);
|
||||||
Assert(frame->position != kTimeUnknown);
|
if (frame->displayed == kTimeUnknown) {
|
||||||
|
frame->displayed = now;
|
||||||
if (frame->displayed != kTimeUnknown) {
|
|
||||||
return kTimeUnknown;
|
|
||||||
}
|
}
|
||||||
frame->displayed = now;
|
|
||||||
return frame->position;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
|
@ -486,29 +500,28 @@ crl::time SharedState::markFrameDisplayed(crl::time now) {
|
||||||
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time SharedState::markFrameShown() {
|
bool SharedState::markFrameShown() {
|
||||||
const auto jump = [&](int counter) {
|
const auto jump = [&](int counter) {
|
||||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||||
const auto index = next / 2;
|
const auto index = next / 2;
|
||||||
const auto frame = getFrame(index);
|
const auto frame = getFrame(index);
|
||||||
Assert(frame->position != kTimeUnknown);
|
|
||||||
if (frame->displayed == kTimeUnknown) {
|
if (frame->displayed == kTimeUnknown) {
|
||||||
return kTimeUnknown;
|
return false;
|
||||||
}
|
}
|
||||||
_counter.store(
|
_counter.store(
|
||||||
next,
|
next,
|
||||||
std::memory_order_release);
|
std::memory_order_release);
|
||||||
return frame->position;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
case 0: return kTimeUnknown;
|
case 0: return false;
|
||||||
case 1: return jump(1);
|
case 1: return jump(1);
|
||||||
case 2: return kTimeUnknown;
|
case 2: return false;
|
||||||
case 3: return jump(3);
|
case 3: return jump(3);
|
||||||
case 4: return kTimeUnknown;
|
case 4: return false;
|
||||||
case 5: return jump(5);
|
case 5: return jump(5);
|
||||||
case 6: return kTimeUnknown;
|
case 6: return false;
|
||||||
case 7: return jump(7);
|
case 7: return jump(7);
|
||||||
}
|
}
|
||||||
Unexpected("Counter value in Lottie::SharedState::markFrameShown.");
|
Unexpected("Counter value in Lottie::SharedState::markFrameShown.");
|
||||||
|
|
|
@ -28,15 +28,16 @@ inline constexpr auto kMaxSize = 3096;
|
||||||
inline constexpr auto kMaxFramesCount = 600;
|
inline constexpr auto kMaxFramesCount = 600;
|
||||||
inline constexpr auto kFrameDisplayTimeAlreadyDone
|
inline constexpr auto kFrameDisplayTimeAlreadyDone
|
||||||
= std::numeric_limits<crl::time>::max();
|
= std::numeric_limits<crl::time>::max();
|
||||||
|
inline constexpr auto kDisplayedInitial = crl::time(-1);
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
class Cache;
|
class Cache;
|
||||||
|
|
||||||
struct Frame {
|
struct Frame {
|
||||||
QImage original;
|
QImage original;
|
||||||
crl::time position = kTimeUnknown;
|
crl::time displayed = kDisplayedInitial;
|
||||||
crl::time displayed = kTimeUnknown;
|
|
||||||
crl::time display = kTimeUnknown;
|
crl::time display = kTimeUnknown;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
FrameRequest request;
|
FrameRequest request;
|
||||||
QImage prepared;
|
QImage prepared;
|
||||||
|
@ -68,9 +69,9 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] not_null<Frame*> frameForPaint();
|
[[nodiscard]] not_null<Frame*> frameForPaint();
|
||||||
[[nodiscard]] crl::time nextFrameDisplayTime() const;
|
[[nodiscard]] crl::time nextFrameDisplayTime() const;
|
||||||
void addTimelineDelay(crl::time delayed);
|
void addTimelineDelay(crl::time delayed, int skippedFrames = 0);
|
||||||
crl::time markFrameDisplayed(crl::time now);
|
void markFrameDisplayed(crl::time now);
|
||||||
crl::time markFrameShown();
|
bool markFrameShown();
|
||||||
|
|
||||||
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
||||||
|
|
||||||
|
@ -90,10 +91,10 @@ private:
|
||||||
void renderNextFrame(
|
void renderNextFrame(
|
||||||
not_null<Frame*> frame,
|
not_null<Frame*> frame,
|
||||||
const FrameRequest &request);
|
const FrameRequest &request);
|
||||||
|
[[nodiscard]] crl::time countFrameDisplayTime(int index) const;
|
||||||
[[nodiscard]] not_null<Frame*> getFrame(int index);
|
[[nodiscard]] not_null<Frame*> getFrame(int index);
|
||||||
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
|
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
|
||||||
[[nodiscard]] int counter() const;
|
[[nodiscard]] int counter() const;
|
||||||
[[nodiscard]] crl::time currentFramePosition() const;
|
|
||||||
|
|
||||||
QByteArray _content;
|
QByteArray _content;
|
||||||
std::unique_ptr<rlottie::Animation> _animation;
|
std::unique_ptr<rlottie::Animation> _animation;
|
||||||
|
|
|
@ -32,6 +32,9 @@ MultiPlayer::~MultiPlayer() {
|
||||||
for (const auto &[animation, state] : _active) {
|
for (const auto &[animation, state] : _active) {
|
||||||
_renderer->remove(state);
|
_renderer->remove(state);
|
||||||
}
|
}
|
||||||
|
for (const auto &[animation, info] : _paused) {
|
||||||
|
_renderer->remove(info.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<Animation*> MultiPlayer::append(
|
not_null<Animation*> MultiPlayer::append(
|
||||||
|
@ -58,22 +61,35 @@ not_null<Animation*> MultiPlayer::append(
|
||||||
return _animations.back().get();
|
return _animations.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
|
void MultiPlayer::startAtRightTime(std::unique_ptr<SharedState> state) {
|
||||||
Expects(!_active.empty());
|
if (_started == kTimeUnknown) {
|
||||||
Expects((_active.size() == 1) == (_started == kTimeUnknown));
|
_started = crl::now();
|
||||||
|
_lastSyncTime = kTimeUnknown;
|
||||||
const auto now = crl::now();
|
_delay = 0;
|
||||||
if (_active.size() == 1) {
|
|
||||||
_started = now;
|
|
||||||
}
|
}
|
||||||
|
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;
|
const auto rate = state->information().frameRate;
|
||||||
Assert(rate != 0);
|
Assert(rate != 0);
|
||||||
|
|
||||||
const auto started = _started + _delay;
|
const auto framesTime = time - _started - delay;
|
||||||
const auto skipFrames = (now - started) * rate / 1000;
|
return ((framesTime + 1) * rate - 1) / 1000;
|
||||||
|
|
||||||
state->start(this, _started, _delay, skipFrames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiPlayer::start(
|
void MultiPlayer::start(
|
||||||
|
@ -81,39 +97,86 @@ void MultiPlayer::start(
|
||||||
std::unique_ptr<SharedState> state) {
|
std::unique_ptr<SharedState> state) {
|
||||||
Expects(state != nullptr);
|
Expects(state != nullptr);
|
||||||
|
|
||||||
if (_nextFrameTime == kTimeUnknown) {
|
const auto paused = _pausedBeforeStart.remove(animation);
|
||||||
appendToActive(animation, std::move(state));
|
auto info = StartingInfo{ std::move(state), paused };
|
||||||
|
if (_active.empty()
|
||||||
|
|| (_lastSyncTime == kTimeUnknown
|
||||||
|
&& _nextFrameTime == kTimeUnknown)) {
|
||||||
|
startBeforeLifeCycle(animation, std::move(info));
|
||||||
} else {
|
} else {
|
||||||
// We always try to mark as shown at the same time, so we start a new
|
// 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.
|
// 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({});
|
_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) {
|
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);
|
const auto i = _active.find(animation);
|
||||||
if (i != end(_active)) {
|
if (i != end(_active)) {
|
||||||
_renderer->remove(i->second);
|
_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(
|
_animations.erase(
|
||||||
ranges::remove(
|
ranges::remove(
|
||||||
_animations,
|
_animations,
|
||||||
|
@ -122,13 +185,95 @@ void MultiPlayer::remove(not_null<Animation*> animation) {
|
||||||
end(_animations));
|
end(_animations));
|
||||||
|
|
||||||
if (_active.empty()) {
|
if (_active.empty()) {
|
||||||
_started = kTimeUnknown;
|
|
||||||
_delay = 0;
|
|
||||||
_nextFrameTime = kTimeUnknown;
|
_nextFrameTime = kTimeUnknown;
|
||||||
_timer.cancel();
|
_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) {
|
void MultiPlayer::failed(not_null<Animation*> animation, Error error) {
|
||||||
//_updates.fire({ animation, error });
|
//_updates.fire({ animation, error });
|
||||||
}
|
}
|
||||||
|
@ -191,8 +336,9 @@ void MultiPlayer::checkNextFrameRender() {
|
||||||
|
|
||||||
markFrameDisplayed(now);
|
markFrameDisplayed(now);
|
||||||
addTimelineDelay(now - _nextFrameTime);
|
addTimelineDelay(now - _nextFrameTime);
|
||||||
|
_lastSyncTime = now;
|
||||||
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
||||||
|
processPending();
|
||||||
_updates.fire({});
|
_updates.fire({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,10 +346,20 @@ void MultiPlayer::checkNextFrameRender() {
|
||||||
void MultiPlayer::updateFrameRequest(
|
void MultiPlayer::updateFrameRequest(
|
||||||
not_null<const Animation*> animation,
|
not_null<const Animation*> animation,
|
||||||
const FrameRequest &request) {
|
const FrameRequest &request) {
|
||||||
const auto i = _active.find(animation);
|
const auto state = [&] {
|
||||||
Assert(i != _active.end());
|
const auto key = animation;
|
||||||
|
if (const auto i = _active.find(animation); i != end(_active)) {
|
||||||
_renderer->updateFrameRequest(i->second, request);
|
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) {
|
void MultiPlayer::markFrameDisplayed(crl::time now) {
|
||||||
|
@ -238,11 +394,10 @@ void MultiPlayer::addTimelineDelay(crl::time delayed) {
|
||||||
void MultiPlayer::markFrameShown() {
|
void MultiPlayer::markFrameShown() {
|
||||||
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
||||||
_nextFrameTime = kTimeUnknown;
|
_nextFrameTime = kTimeUnknown;
|
||||||
appendPendingToActive();
|
|
||||||
}
|
}
|
||||||
auto count = 0;
|
auto count = 0;
|
||||||
for (const auto &[animation, state] : _active) {
|
for (const auto &[animation, state] : _active) {
|
||||||
if (state->markFrameShown() != kTimeUnknown) {
|
if (state->markFrameShown()) {
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_player.h"
|
#include "lottie/lottie_player.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "base/algorithm.h"
|
#include "base/algorithm.h"
|
||||||
|
#include "base/flat_set.h"
|
||||||
|
#include "base/flat_map.h"
|
||||||
|
|
||||||
#include <rpl/event_stream.h>
|
#include <rpl/event_stream.h>
|
||||||
|
|
||||||
|
@ -55,26 +57,55 @@ public:
|
||||||
|
|
||||||
void remove(not_null<Animation*> animation);
|
void remove(not_null<Animation*> animation);
|
||||||
|
|
||||||
|
void pause(not_null<Animation*> animation);
|
||||||
|
void unpause(not_null<Animation*> animation);
|
||||||
|
|
||||||
private:
|
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,
|
not_null<Animation*> animation,
|
||||||
std::unique_ptr<SharedState> state);
|
StartingInfo &&info);
|
||||||
void startAtRightTime(not_null<SharedState*> state);
|
void startInsideLifeCycle(
|
||||||
void appendPendingToActive();
|
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 markFrameDisplayed(crl::time now);
|
||||||
void addTimelineDelay(crl::time delayed);
|
void addTimelineDelay(crl::time delayed);
|
||||||
void checkNextFrameAvailability();
|
void checkNextFrameAvailability();
|
||||||
void checkNextFrameRender();
|
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;
|
base::Timer _timer;
|
||||||
const std::shared_ptr<FrameRenderer> _renderer;
|
const std::shared_ptr<FrameRenderer> _renderer;
|
||||||
std::vector<std::unique_ptr<Animation>> _animations;
|
std::vector<std::unique_ptr<Animation>> _animations;
|
||||||
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
|
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
|
||||||
base::flat_map<
|
base::flat_map<not_null<Animation*>, PausedInfo> _paused;
|
||||||
not_null<Animation*>,
|
base::flat_set<not_null<Animation*>> _pendingPause;
|
||||||
std::unique_ptr<SharedState>> _pendingToStart;
|
base::flat_set<not_null<Animation*>> _pendingUnpause;
|
||||||
//base::flat_map<not_null<Animation*>, not_null<SharedState*>> _paused;
|
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 _started = kTimeUnknown;
|
||||||
|
crl::time _lastSyncTime = kTimeUnknown;
|
||||||
crl::time _delay = 0;
|
crl::time _delay = 0;
|
||||||
crl::time _nextFrameTime = kTimeUnknown;
|
crl::time _nextFrameTime = kTimeUnknown;
|
||||||
rpl::event_stream<MultiUpdate> _updates;
|
rpl::event_stream<MultiUpdate> _updates;
|
||||||
|
|
|
@ -102,11 +102,11 @@ void SinglePlayer::checkNextFrameRender() {
|
||||||
} else {
|
} else {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
|
||||||
const auto position = _state->markFrameDisplayed(now);
|
_state->markFrameDisplayed(now);
|
||||||
_state->addTimelineDelay(now - _nextFrameTime);
|
_state->addTimelineDelay(now - _nextFrameTime);
|
||||||
|
|
||||||
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
||||||
_updates.fire({ DisplayFrameRequest{ position } });
|
_updates.fire({ DisplayFrameRequest() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ void SinglePlayer::markFrameShown() {
|
||||||
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
||||||
_nextFrameTime = kTimeUnknown;
|
_nextFrameTime = kTimeUnknown;
|
||||||
}
|
}
|
||||||
if (_state->markFrameShown() != kTimeUnknown) {
|
if (_state->markFrameShown()) {
|
||||||
_renderer->frameShown();
|
_renderer->frameShown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ namespace Lottie {
|
||||||
class FrameRenderer;
|
class FrameRenderer;
|
||||||
|
|
||||||
struct DisplayFrameRequest {
|
struct DisplayFrameRequest {
|
||||||
crl::time time = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Update {
|
struct Update {
|
||||||
|
|
Loading…
Reference in New Issue