Implement Lottie::MultiPlayer.

This commit is contained in:
John Preston 2019-06-28 15:24:33 +02:00
parent cbffeca8d5
commit 09c9f4ef9a
15 changed files with 437 additions and 133 deletions

View File

@ -30,6 +30,44 @@ inline bool contains(const Container &container, const T &value) {
return std::find(std::begin(container), end, value) != end;
}
// We need a custom comparator for set<std::unique_ptr<T>>::find to work with pointers.
// thanks to http://stackoverflow.com/questions/18939882/raw-pointer-lookup-for-sets-of-unique-ptrs
template <typename T>
struct pointer_comparator {
using is_transparent = std::true_type;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
const T *ptr = nullptr;
helper() = default;
helper(const helper &other) = default;
helper(const T *p) : ptr(p) {
}
template <typename ...Ts>
helper(const std::shared_ptr<Ts...> &other) : ptr(other.get()) {
}
template <typename ...Ts>
helper(const std::unique_ptr<Ts...> &other) : ptr(other.get()) {
}
bool operator<(helper other) const {
return std::less<const T*>()(ptr, other.ptr);
}
};
// without helper, we'd need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly.
// && helps enforce rvalue use only
bool operator()(const helper &&lhs, const helper &&rhs) const {
return lhs < rhs;
}
};
} // namespace base
template <typename T>

View File

@ -53,12 +53,19 @@ struct flat_multi_map_pair_type {
, second(std::forward<OtherValue>(value)) {
}
flat_multi_map_pair_type(const flat_multi_map_pair_type&) = default;
flat_multi_map_pair_type(flat_multi_map_pair_type&&) = default;
flat_multi_map_pair_type(const flat_multi_map_pair_type &pair)
: first(pair.first)
, second(pair.second) {
}
flat_multi_map_pair_type(flat_multi_map_pair_type &&pair)
: first(std::move(const_cast<Key&>(pair.first)))
, second(std::move(pair.second)) {
}
flat_multi_map_pair_type &operator=(const flat_multi_map_pair_type&) = delete;
flat_multi_map_pair_type &operator=(flat_multi_map_pair_type &&other) {
const_cast<Key&>(first) = other.first;
const_cast<Key&>(first) = std::move(const_cast<Key&>(other.first));
second = std::move(other.second);
return *this;
}
@ -474,6 +481,28 @@ public:
return compare()(key, where->first) ? impl().end() : where;
}
template <typename OtherKey, typename = typename Compare::is_transparent>
iterator findFirst(const OtherKey &key) {
if (empty()
|| compare()(key, front().first)
|| compare()(back().first, key)) {
return end();
}
auto where = getLowerBound(key);
return compare()(key, where->first) ? impl().end() : where;
}
template <typename OtherKey, typename = typename Compare::is_transparent>
const_iterator findFirst(const OtherKey &key) const {
if (empty()
|| compare()(key, front().first)
|| compare()(back().first, key)) {
return end();
}
auto where = getLowerBound(key);
return compare()(key, where->first) ? impl().end() : where;
}
bool contains(const Key &key) const {
return findFirst(key) != end();
}
@ -558,48 +587,54 @@ private:
return _data.elements;
}
typename impl_t::iterator getLowerBound(const Key &key) {
template <typename OtherKey>
typename impl_t::iterator getLowerBound(const OtherKey &key) {
return std::lower_bound(
std::begin(impl()),
std::end(impl()),
key,
compare());
}
typename impl_t::const_iterator getLowerBound(const Key &key) const {
template <typename OtherKey>
typename impl_t::const_iterator getLowerBound(const OtherKey &key) const {
return std::lower_bound(
std::begin(impl()),
std::end(impl()),
key,
compare());
}
typename impl_t::iterator getUpperBound(const Key &key) {
template <typename OtherKey>
typename impl_t::iterator getUpperBound(const OtherKey &key) {
return std::upper_bound(
std::begin(impl()),
std::end(impl()),
key,
compare());
}
typename impl_t::const_iterator getUpperBound(const Key &key) const {
template <typename OtherKey>
typename impl_t::const_iterator getUpperBound(const OtherKey &key) const {
return std::upper_bound(
std::begin(impl()),
std::end(impl()),
key,
compare());
}
template <typename OtherKey>
std::pair<
typename impl_t::iterator,
typename impl_t::iterator
> getEqualRange(const Key &key) {
> getEqualRange(const OtherKey &key) {
return std::equal_range(
std::begin(impl()),
std::end(impl()),
key,
compare());
}
template <typename OtherKey>
std::pair<
typename impl_t::const_iterator,
typename impl_t::const_iterator
> getEqualRange(const Key &key) const {
> getEqualRange(const OtherKey &key) const {
return std::equal_range(
std::begin(impl()),
std::end(impl()),
@ -720,12 +755,12 @@ public:
where->second = std::move(value);
return { where, false };
}
template <typename... Args>
template <typename OtherKey, typename... Args>
std::pair<iterator, bool> emplace(
const Key &key,
OtherKey &&key,
Args&&... args) {
return this->insert(value_type(
key,
std::forward<OtherKey>(key),
Type(std::forward<Args>(args)...)));
}
template <typename... Args>
@ -775,6 +810,14 @@ public:
const_iterator find(const Key &key) const {
return this->findFirst(key);
}
template <typename OtherKey, typename = typename Compare::is_transparent>
iterator find(const OtherKey &key) {
return this->findFirst<OtherKey>(key);
}
template <typename OtherKey, typename = typename Compare::is_transparent>
const_iterator find(const OtherKey &key) const {
return this->findFirst<OtherKey>(key);
}
Type &operator[](const Key &key) {
if (this->empty() || this->compare()(key, this->front().first)) {

View File

@ -69,10 +69,10 @@ TEST_CASE("simple flat_maps tests", "[flat_map]") {
TEST_CASE("flat_maps custom comparator", "[flat_map]") {
base::flat_map<int_wrap, string, int_wrap_comparator> v;
v.emplace({ 0 }, "a");
v.emplace({ 5 }, "b");
v.emplace({ 4 }, "d");
v.emplace({ 2 }, "e");
v.emplace(int_wrap{ 0 }, "a");
v.emplace(int_wrap{ 5 }, "b");
v.emplace(int_wrap{ 4 }, "d");
v.emplace(int_wrap{ 2 }, "e");
auto checkSorted = [&] {
auto prev = v.begin();
@ -85,7 +85,7 @@ TEST_CASE("flat_maps custom comparator", "[flat_map]") {
checkSorted();
SECTION("adding item puts it in the right position") {
v.emplace({ 3 }, "c");
v.emplace(int_wrap{ 3 }, "c");
REQUIRE(v.size() == 5);
REQUIRE(v.find({ 3 }) != v.end());
checkSorted();

View File

@ -36,44 +36,6 @@ inline constexpr D up_cast(T object) {
}
}
// We need a custom comparator for std::set<std::unique_ptr<T>>::find to work with pointers.
// thanks to http://stackoverflow.com/questions/18939882/raw-pointer-lookup-for-sets-of-unique-ptrs
template <typename T>
struct pointer_comparator {
using is_transparent = std::true_type;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
T *ptr = nullptr;
helper() = default;
helper(const helper &other) = default;
helper(T *p) : ptr(p) {
}
template <typename ...Ts>
helper(const std::shared_ptr<Ts...> &other) : ptr(other.get()) {
}
template <typename ...Ts>
helper(const std::unique_ptr<Ts...> &other) : ptr(other.get()) {
}
bool operator<(helper other) const {
return std::less<T*>()(ptr, other.ptr);
}
};
// without helper, we'd need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly.
// && helps enforce rvalue use only
bool operator()(const helper &&lhs, const helper &&rhs) const {
return lhs < rhs;
}
};
template <typename T>
using set_of_unique_ptr = std::set<std::unique_ptr<T>, base::pointer_comparator<T>>;

View File

@ -10,12 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_common.h"
#include "base/weak_ptr.h"
#include <QSize>
#include <crl/crl_time.h>
#include <rpl/event_stream.h>
#include <vector>
#include <optional>
class QImage;
class QString;
class QByteArray;
@ -28,7 +22,6 @@ namespace Lottie {
class Player;
class SharedState;
class FrameRenderer;
QImage ReadThumbnail(const QByteArray &content);

View File

@ -27,16 +27,6 @@ struct Information {
QSize size;
};
struct DisplayFrameRequest {
crl::time time = 0;
};
struct Update {
base::variant<
Information,
DisplayFrameRequest> data;
};
enum class Error {
ParseFailed,
NotSupported,

View File

@ -49,7 +49,7 @@ public:
crl::weak_on_queue<FrameRendererObject> weak);
void append(std::unique_ptr<SharedState> entry);
void frameShown(not_null<SharedState*> entry);
void frameShown();
void updateFrameRequest(
not_null<SharedState*> entry,
const FrameRequest &request);
@ -140,7 +140,7 @@ void FrameRendererObject::append(std::unique_ptr<SharedState> state) {
queueGenerateFrames();
}
void FrameRendererObject::frameShown(not_null<SharedState*> entry) {
void FrameRendererObject::frameShown() {
queueGenerateFrames();
}
@ -423,28 +423,29 @@ crl::time SharedState::nextFrameDisplayTime() const {
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
}
crl::time SharedState::markFrameDisplayed(crl::time now) {
crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) {
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);
Assert(frame->displayed == kTimeUnknown);
if (frame->displayed != kTimeUnknown) {
return kTimeUnknown;
}
frame->displayed = now;
_accumulatedDelayMs += (frame->displayed - frame->display);
return frame->position;
};
_accumulatedDelayMs += delayed;
switch (counter()) {
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
case 0: return kTimeUnknown;
case 1: return mark(1);
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
case 2: return kTimeUnknown;
case 3: return mark(3);
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
case 4: return kTimeUnknown;
case 5: return mark(5);
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
case 6: return kTimeUnknown;
case 7: return mark(7);
}
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
@ -480,11 +481,15 @@ crl::time SharedState::markFrameShown() {
SharedState::~SharedState() = default;
std::shared_ptr<FrameRenderer> FrameRenderer::CreateIndependent() {
return std::make_shared<FrameRenderer>();
}
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
if (auto result = GlobalInstance.lock()) {
return result;
}
auto result = std::make_shared<FrameRenderer>();
auto result = CreateIndependent();
GlobalInstance = result;
return result;
}
@ -496,9 +501,9 @@ void FrameRenderer::append(std::unique_ptr<SharedState> entry) {
});
}
void FrameRenderer::frameShown(not_null<SharedState*> entry) {
void FrameRenderer::frameShown() {
_wrapped.with([=](FrameRendererObject &unwrapped) {
unwrapped.frameShown(entry);
unwrapped.frameShown();
});
}

View File

@ -62,7 +62,7 @@ public:
[[nodiscard]] not_null<Frame*> frameForPaint();
[[nodiscard]] crl::time nextFrameDisplayTime() const;
crl::time markFrameDisplayed(crl::time now);
crl::time markFrameDisplayed(crl::time now, crl::time delayed);
crl::time markFrameShown();
void renderFrame(QImage &image, const FrameRequest &request, int index);
@ -108,6 +108,7 @@ class FrameRendererObject;
class FrameRenderer final {
public:
static std::shared_ptr<FrameRenderer> CreateIndependent();
static std::shared_ptr<FrameRenderer> Instance();
void append(std::unique_ptr<SharedState> entry);
@ -115,7 +116,7 @@ public:
void updateFrameRequest(
not_null<SharedState*> entry,
const FrameRequest &request);
void frameShown(not_null<SharedState*> entry);
void frameShown();
void remove(not_null<SharedState*> state);
private:

View File

@ -0,0 +1,203 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "lottie/lottie_multi_player.h"
#include "lottie/lottie_frame_renderer.h"
#include "lottie/lottie_animation.h"
#include <range/v3/algorithm/remove.hpp>
namespace Lottie {
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
return FrameRenderer::CreateIndependent();
}
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
: _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
}
MultiPlayer::~MultiPlayer() {
for (const auto &[animation, state] : _active) {
_renderer->remove(state);
}
}
not_null<Animation*> MultiPlayer::append(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request) {
_animations.push_back(std::make_unique<Animation>(
this,
std::move(get),
std::move(put),
content,
request));
return _animations.back().get();
}
not_null<Animation*> MultiPlayer::append(
const QByteArray &content,
const FrameRequest &request) {
_animations.push_back(std::make_unique<Animation>(
this,
content,
request));
return _animations.back().get();
}
crl::time MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
Expects(!_active.empty());
Expects((_active.size() == 1) == (_started == kTimeUnknown));
if (_active.size() == 1) {
_started = crl::now();
state->start(this, _started);
return _started;
}
const auto now = crl::now();
const auto rate = state->information().frameRate;
Assert(rate != 0);
const auto started = _started + _accumulatedDelay;
const auto skipFrames = (now - started) * rate / 1000;
const auto startAt = started + (1000 * skipFrames / rate);
state->start(this, startAt);
return startAt;
}
void MultiPlayer::start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) {
Expects(state != nullptr);
_active.emplace(animation, state.get());
auto information = state->information();
startAtRightTime(state.get());
_renderer->append(std::move(state));
_updates.fire({});
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
checkStep();
}, _lifetime);
_nextFrameTime = kTimeUnknown;
_timer.cancel();
checkStep();
}
void MultiPlayer::remove(not_null<Animation*> animation) {
const auto i = _active.find(animation);
if (i != end(_active)) {
_renderer->remove(i->second);
}
_animations.erase(
ranges::remove(
_animations,
animation.get(),
&std::unique_ptr<Animation>::get),
end(_animations));
if (_active.empty()) {
_started = kTimeUnknown;
_accumulatedDelay = 0;
_nextFrameTime = kTimeUnknown;
_timer.cancel();
}
}
void MultiPlayer::failed(not_null<Animation*> animation, Error error) {
//_updates.fire({ animation, error });
}
rpl::producer<MultiUpdate> MultiPlayer::updates() const {
return _updates.events();
}
void MultiPlayer::checkStep() {
if (_nextFrameTime != kTimeUnknown) {
checkNextFrameRender();
} else {
checkNextFrameAvailability();
}
}
void MultiPlayer::checkNextFrameAvailability() {
if (_active.empty()) {
return;
}
auto next = kTimeUnknown;
for (const auto &[animation, state] : _active) {
const auto time = state->nextFrameDisplayTime();
if (time == kTimeUnknown) {
return;
}
if (next == kTimeUnknown || next > time) {
next = time;
}
}
Assert(next != kTimeUnknown);
_nextFrameTime = next;
checkNextFrameRender();
}
void MultiPlayer::checkNextFrameRender() {
Expects(_nextFrameTime != kTimeUnknown);
const auto now = crl::now();
if (now < _nextFrameTime) {
if (!_timer.isActive()) {
_timer.callOnce(_nextFrameTime - now);
}
} else {
_timer.cancel();
const auto exact = std::exchange(_nextFrameTime, kTimeUnknown);
markFrameDisplayed(now, now - exact);
_updates.fire({});
checkStep();
}
}
void MultiPlayer::updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) {
const auto i = _active.find(animation.get());
Assert(i != _active.end());
_renderer->updateFrameRequest(i->second, request);
}
void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
Expects(!_active.empty());
for (const auto &[animation, state] : _active) {
const auto time = state->nextFrameDisplayTime();
Assert(time != kTimeUnknown);
if (now >= time) {
state->markFrameDisplayed(now, delayed);
}
}
}
void MultiPlayer::markFrameShown() {
if (_active.empty()) {
return;
}
for (const auto &[animation, state] : _active) {
state->markFrameShown();
}
_renderer->frameShown();
}
} // namespace Lottie

View File

@ -0,0 +1,77 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "lottie/lottie_player.h"
#include "base/timer.h"
#include "base/algorithm.h"
#include <rpl/event_stream.h>
namespace Lottie {
class Animation;
class FrameRenderer;
struct MultiUpdate {
//base::variant<
// std::pair<Animation*, Information>,
// DisplayMultiFrameRequest,
// std::pair<Animation*, Error>> data;
};
std::shared_ptr<FrameRenderer> MakeFrameRenderer();
class MultiPlayer final : public Player {
public:
explicit MultiPlayer(std::shared_ptr<FrameRenderer> renderer = nullptr);
~MultiPlayer();
void start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) override;
void failed(not_null<Animation*> animation, Error error) override;
void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) override;
void markFrameShown() override;
void checkStep() override;
not_null<Animation*> append(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request);
not_null<Animation*> append(
const QByteArray &content,
const FrameRequest &request);
rpl::producer<MultiUpdate> updates() const;
void remove(not_null<Animation*> animation);
private:
crl::time startAtRightTime(not_null<SharedState*> state);
void markFrameDisplayed(crl::time now, crl::time delayed);
void checkNextFrameAvailability();
void checkNextFrameRender();
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*>, not_null<SharedState*>> _paused;
crl::time _started = kTimeUnknown;
crl::time _accumulatedDelay = 0;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<MultiUpdate> _updates;
rpl::lifetime _lifetime;
};
} // namespace Lottie

View File

@ -22,17 +22,10 @@ public:
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) = 0;
virtual void failed(not_null<Animation*> animation, Error error) = 0;
[[nodiscard]] virtual rpl::producer<Update, Error> updates() = 0;
virtual void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) = 0;
// Returns frame position, if any frame was marked as displayed.
virtual crl::time markFrameDisplayed(crl::time now) = 0;
virtual crl::time markFrameShown() = 0;
virtual void markFrameShown() = 0;
virtual void checkStep() = 0;
virtual ~Player() = default;

View File

@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_frame_renderer.h"
namespace Lottie {
namespace {
} // namespace
SinglePlayer::SinglePlayer(
const QByteArray &content,
@ -32,6 +29,12 @@ SinglePlayer::SinglePlayer(
, _renderer(FrameRenderer::Instance()) {
}
SinglePlayer::~SinglePlayer() {
if (_state) {
_renderer->remove(_state);
}
}
void SinglePlayer::start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) {
@ -40,7 +43,6 @@ void SinglePlayer::start(
_state = state.get();
auto information = state->information();
state->start(this, crl::now());
_renderer = FrameRenderer::Instance();
_renderer->append(std::move(state));
_updates.fire({ std::move(information) });
@ -56,13 +58,7 @@ void SinglePlayer::failed(not_null<Animation*> animation, Error error) {
_updates.fire_error(std::move(error));
}
SinglePlayer::~SinglePlayer() {
if (_state) {
_renderer->remove(_state);
}
}
rpl::producer<Update, Error> SinglePlayer::updates() {
rpl::producer<Update, Error> SinglePlayer::updates() const {
return _updates.events();
}
@ -87,7 +83,7 @@ void SinglePlayer::checkNextFrameAvailability() {
_nextFrameTime = _state->nextFrameDisplayTime();
if (_nextFrameTime != kTimeUnknown) {
checkStep();
checkNextFrameRender();
}
}
@ -102,8 +98,8 @@ void SinglePlayer::checkNextFrameRender() {
} else {
_timer.cancel();
_nextFrameTime = kTimeUnknown;
const auto position = markFrameDisplayed(now);
const auto exact = std::exchange(_nextFrameTime, kTimeUnknown);
const auto position = _state->markFrameDisplayed(now, now - exact);
_updates.fire({ DisplayFrameRequest{ position } });
}
}
@ -117,19 +113,11 @@ void SinglePlayer::updateFrameRequest(
_renderer->updateFrameRequest(_state, request);
}
crl::time SinglePlayer::markFrameDisplayed(crl::time now) {
void SinglePlayer::markFrameShown() {
Expects(_state != nullptr);
return _state->markFrameDisplayed(now);
}
crl::time SinglePlayer::markFrameShown() {
Expects(_renderer != nullptr);
const auto result = _state->markFrameShown();
_renderer->frameShown(_state);
return result;
_state->markFrameShown();
_renderer->frameShown();
}
} // namespace Lottie

View File

@ -11,8 +11,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_animation.h"
#include "base/timer.h"
#include <rpl/event_stream.h>
namespace Lottie {
class FrameRenderer;
struct DisplayFrameRequest {
crl::time time = 0;
};
struct Update {
base::variant<
Information,
DisplayFrameRequest> data;
};
class SinglePlayer final : public Player {
public:
SinglePlayer(
@ -29,29 +43,24 @@ public:
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) override;
void failed(not_null<Animation*> animation, Error error) override;
rpl::producer<Update, Error> updates() override;
[[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame(const FrameRequest &request) const;
void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) override;
// Returns frame position, if any frame was marked as displayed.
crl::time markFrameDisplayed(crl::time now) override;
crl::time markFrameShown() override;
void markFrameShown() override;
void checkStep() override;
rpl::producer<Update, Error> updates() const;
[[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame(const FrameRequest &request) const;
private:
void checkNextFrameAvailability();
void checkNextFrameRender();
Animation _animation;
base::Timer _timer;
std::shared_ptr<FrameRenderer> _renderer;
const std::shared_ptr<FrameRenderer> _renderer;
SharedState *_state = nullptr;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<Update, Error> _updates;

View File

@ -111,7 +111,7 @@ void Player::checkNextFrameAvailability() {
_nextFrameTime = _video->nextFrameDisplayTime();
if (_nextFrameTime != kTimeUnknown) {
checkVideoStep();
checkNextFrameRender();
}
}

View File

@ -64,6 +64,8 @@
'<(src_loc)/lottie/lottie_common.h',
'<(src_loc)/lottie/lottie_frame_renderer.cpp',
'<(src_loc)/lottie/lottie_frame_renderer.h',
'<(src_loc)/lottie/lottie_multi_player.cpp',
'<(src_loc)/lottie/lottie_multi_player.h',
'<(src_loc)/lottie/lottie_player.h',
'<(src_loc)/lottie/lottie_single_player.cpp',
'<(src_loc)/lottie/lottie_single_player.h',