mirror of https://github.com/procxx/kepka.git
Implement Lottie::MultiPlayer.
This commit is contained in:
parent
cbffeca8d5
commit
09c9f4ef9a
|
@ -30,6 +30,44 @@ inline bool contains(const Container &container, const T &value) {
|
||||||
return std::find(std::begin(container), end, value) != end;
|
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
|
} // namespace base
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -53,12 +53,19 @@ struct flat_multi_map_pair_type {
|
||||||
, second(std::forward<OtherValue>(value)) {
|
, second(std::forward<OtherValue>(value)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
flat_multi_map_pair_type(const flat_multi_map_pair_type&) = default;
|
flat_multi_map_pair_type(const flat_multi_map_pair_type &pair)
|
||||||
flat_multi_map_pair_type(flat_multi_map_pair_type&&) = default;
|
: 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=(const flat_multi_map_pair_type&) = delete;
|
||||||
flat_multi_map_pair_type &operator=(flat_multi_map_pair_type &&other) {
|
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);
|
second = std::move(other.second);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -474,6 +481,28 @@ public:
|
||||||
return compare()(key, where->first) ? impl().end() : where;
|
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 {
|
bool contains(const Key &key) const {
|
||||||
return findFirst(key) != end();
|
return findFirst(key) != end();
|
||||||
}
|
}
|
||||||
|
@ -558,48 +587,54 @@ private:
|
||||||
return _data.elements;
|
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(
|
return std::lower_bound(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
key,
|
key,
|
||||||
compare());
|
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(
|
return std::lower_bound(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
key,
|
key,
|
||||||
compare());
|
compare());
|
||||||
}
|
}
|
||||||
typename impl_t::iterator getUpperBound(const Key &key) {
|
template <typename OtherKey>
|
||||||
|
typename impl_t::iterator getUpperBound(const OtherKey &key) {
|
||||||
return std::upper_bound(
|
return std::upper_bound(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
key,
|
key,
|
||||||
compare());
|
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(
|
return std::upper_bound(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
key,
|
key,
|
||||||
compare());
|
compare());
|
||||||
}
|
}
|
||||||
|
template <typename OtherKey>
|
||||||
std::pair<
|
std::pair<
|
||||||
typename impl_t::iterator,
|
typename impl_t::iterator,
|
||||||
typename impl_t::iterator
|
typename impl_t::iterator
|
||||||
> getEqualRange(const Key &key) {
|
> getEqualRange(const OtherKey &key) {
|
||||||
return std::equal_range(
|
return std::equal_range(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
key,
|
key,
|
||||||
compare());
|
compare());
|
||||||
}
|
}
|
||||||
|
template <typename OtherKey>
|
||||||
std::pair<
|
std::pair<
|
||||||
typename impl_t::const_iterator,
|
typename impl_t::const_iterator,
|
||||||
typename impl_t::const_iterator
|
typename impl_t::const_iterator
|
||||||
> getEqualRange(const Key &key) const {
|
> getEqualRange(const OtherKey &key) const {
|
||||||
return std::equal_range(
|
return std::equal_range(
|
||||||
std::begin(impl()),
|
std::begin(impl()),
|
||||||
std::end(impl()),
|
std::end(impl()),
|
||||||
|
@ -720,12 +755,12 @@ public:
|
||||||
where->second = std::move(value);
|
where->second = std::move(value);
|
||||||
return { where, false };
|
return { where, false };
|
||||||
}
|
}
|
||||||
template <typename... Args>
|
template <typename OtherKey, typename... Args>
|
||||||
std::pair<iterator, bool> emplace(
|
std::pair<iterator, bool> emplace(
|
||||||
const Key &key,
|
OtherKey &&key,
|
||||||
Args&&... args) {
|
Args&&... args) {
|
||||||
return this->insert(value_type(
|
return this->insert(value_type(
|
||||||
key,
|
std::forward<OtherKey>(key),
|
||||||
Type(std::forward<Args>(args)...)));
|
Type(std::forward<Args>(args)...)));
|
||||||
}
|
}
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
|
@ -775,6 +810,14 @@ public:
|
||||||
const_iterator find(const Key &key) const {
|
const_iterator find(const Key &key) const {
|
||||||
return this->findFirst(key);
|
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) {
|
Type &operator[](const Key &key) {
|
||||||
if (this->empty() || this->compare()(key, this->front().first)) {
|
if (this->empty() || this->compare()(key, this->front().first)) {
|
||||||
|
|
|
@ -69,10 +69,10 @@ TEST_CASE("simple flat_maps tests", "[flat_map]") {
|
||||||
|
|
||||||
TEST_CASE("flat_maps custom comparator", "[flat_map]") {
|
TEST_CASE("flat_maps custom comparator", "[flat_map]") {
|
||||||
base::flat_map<int_wrap, string, int_wrap_comparator> v;
|
base::flat_map<int_wrap, string, int_wrap_comparator> v;
|
||||||
v.emplace({ 0 }, "a");
|
v.emplace(int_wrap{ 0 }, "a");
|
||||||
v.emplace({ 5 }, "b");
|
v.emplace(int_wrap{ 5 }, "b");
|
||||||
v.emplace({ 4 }, "d");
|
v.emplace(int_wrap{ 4 }, "d");
|
||||||
v.emplace({ 2 }, "e");
|
v.emplace(int_wrap{ 2 }, "e");
|
||||||
|
|
||||||
auto checkSorted = [&] {
|
auto checkSorted = [&] {
|
||||||
auto prev = v.begin();
|
auto prev = v.begin();
|
||||||
|
@ -85,7 +85,7 @@ TEST_CASE("flat_maps custom comparator", "[flat_map]") {
|
||||||
checkSorted();
|
checkSorted();
|
||||||
|
|
||||||
SECTION("adding item puts it in the right position") {
|
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.size() == 5);
|
||||||
REQUIRE(v.find({ 3 }) != v.end());
|
REQUIRE(v.find({ 3 }) != v.end());
|
||||||
checkSorted();
|
checkSorted();
|
||||||
|
|
|
@ -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>
|
template <typename T>
|
||||||
using set_of_unique_ptr = std::set<std::unique_ptr<T>, base::pointer_comparator<T>>;
|
using set_of_unique_ptr = std::set<std::unique_ptr<T>, base::pointer_comparator<T>>;
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_common.h"
|
#include "lottie/lottie_common.h"
|
||||||
#include "base/weak_ptr.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 QImage;
|
||||||
class QString;
|
class QString;
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
|
@ -28,7 +22,6 @@ namespace Lottie {
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
class SharedState;
|
class SharedState;
|
||||||
class FrameRenderer;
|
|
||||||
|
|
||||||
QImage ReadThumbnail(const QByteArray &content);
|
QImage ReadThumbnail(const QByteArray &content);
|
||||||
|
|
||||||
|
|
|
@ -27,16 +27,6 @@ struct Information {
|
||||||
QSize size;
|
QSize size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DisplayFrameRequest {
|
|
||||||
crl::time time = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Update {
|
|
||||||
base::variant<
|
|
||||||
Information,
|
|
||||||
DisplayFrameRequest> data;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Error {
|
enum class Error {
|
||||||
ParseFailed,
|
ParseFailed,
|
||||||
NotSupported,
|
NotSupported,
|
||||||
|
|
|
@ -49,7 +49,7 @@ public:
|
||||||
crl::weak_on_queue<FrameRendererObject> weak);
|
crl::weak_on_queue<FrameRendererObject> weak);
|
||||||
|
|
||||||
void append(std::unique_ptr<SharedState> entry);
|
void append(std::unique_ptr<SharedState> entry);
|
||||||
void frameShown(not_null<SharedState*> entry);
|
void frameShown();
|
||||||
void updateFrameRequest(
|
void updateFrameRequest(
|
||||||
not_null<SharedState*> entry,
|
not_null<SharedState*> entry,
|
||||||
const FrameRequest &request);
|
const FrameRequest &request);
|
||||||
|
@ -140,7 +140,7 @@ void FrameRendererObject::append(std::unique_ptr<SharedState> state) {
|
||||||
queueGenerateFrames();
|
queueGenerateFrames();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameRendererObject::frameShown(not_null<SharedState*> entry) {
|
void FrameRendererObject::frameShown() {
|
||||||
queueGenerateFrames();
|
queueGenerateFrames();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,28 +423,29 @@ crl::time SharedState::nextFrameDisplayTime() const {
|
||||||
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
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 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);
|
Assert(frame->position != kTimeUnknown);
|
||||||
Assert(frame->displayed == kTimeUnknown);
|
|
||||||
|
|
||||||
|
if (frame->displayed != kTimeUnknown) {
|
||||||
|
return kTimeUnknown;
|
||||||
|
}
|
||||||
frame->displayed = now;
|
frame->displayed = now;
|
||||||
_accumulatedDelayMs += (frame->displayed - frame->display);
|
|
||||||
|
|
||||||
return frame->position;
|
return frame->position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_accumulatedDelayMs += delayed;
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
|
case 0: return kTimeUnknown;
|
||||||
case 1: return mark(1);
|
case 1: return mark(1);
|
||||||
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
|
case 2: return kTimeUnknown;
|
||||||
case 3: return mark(3);
|
case 3: return mark(3);
|
||||||
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
|
case 4: return kTimeUnknown;
|
||||||
case 5: return mark(5);
|
case 5: return mark(5);
|
||||||
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
|
case 6: return kTimeUnknown;
|
||||||
case 7: return mark(7);
|
case 7: return mark(7);
|
||||||
}
|
}
|
||||||
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
||||||
|
@ -480,11 +481,15 @@ crl::time SharedState::markFrameShown() {
|
||||||
|
|
||||||
SharedState::~SharedState() = default;
|
SharedState::~SharedState() = default;
|
||||||
|
|
||||||
|
std::shared_ptr<FrameRenderer> FrameRenderer::CreateIndependent() {
|
||||||
|
return std::make_shared<FrameRenderer>();
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
|
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
|
||||||
if (auto result = GlobalInstance.lock()) {
|
if (auto result = GlobalInstance.lock()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
auto result = std::make_shared<FrameRenderer>();
|
auto result = CreateIndependent();
|
||||||
GlobalInstance = result;
|
GlobalInstance = result;
|
||||||
return 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) {
|
_wrapped.with([=](FrameRendererObject &unwrapped) {
|
||||||
unwrapped.frameShown(entry);
|
unwrapped.frameShown();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] not_null<Frame*> frameForPaint();
|
[[nodiscard]] not_null<Frame*> frameForPaint();
|
||||||
[[nodiscard]] crl::time nextFrameDisplayTime() const;
|
[[nodiscard]] crl::time nextFrameDisplayTime() const;
|
||||||
crl::time markFrameDisplayed(crl::time now);
|
crl::time markFrameDisplayed(crl::time now, crl::time delayed);
|
||||||
crl::time markFrameShown();
|
crl::time markFrameShown();
|
||||||
|
|
||||||
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
||||||
|
@ -108,6 +108,7 @@ class FrameRendererObject;
|
||||||
|
|
||||||
class FrameRenderer final {
|
class FrameRenderer final {
|
||||||
public:
|
public:
|
||||||
|
static std::shared_ptr<FrameRenderer> CreateIndependent();
|
||||||
static std::shared_ptr<FrameRenderer> Instance();
|
static std::shared_ptr<FrameRenderer> Instance();
|
||||||
|
|
||||||
void append(std::unique_ptr<SharedState> entry);
|
void append(std::unique_ptr<SharedState> entry);
|
||||||
|
@ -115,7 +116,7 @@ public:
|
||||||
void updateFrameRequest(
|
void updateFrameRequest(
|
||||||
not_null<SharedState*> entry,
|
not_null<SharedState*> entry,
|
||||||
const FrameRequest &request);
|
const FrameRequest &request);
|
||||||
void frameShown(not_null<SharedState*> entry);
|
void frameShown();
|
||||||
void remove(not_null<SharedState*> state);
|
void remove(not_null<SharedState*> state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -22,17 +22,10 @@ public:
|
||||||
not_null<Animation*> animation,
|
not_null<Animation*> animation,
|
||||||
std::unique_ptr<SharedState> state) = 0;
|
std::unique_ptr<SharedState> state) = 0;
|
||||||
virtual void failed(not_null<Animation*> animation, Error error) = 0;
|
virtual void failed(not_null<Animation*> animation, Error error) = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual rpl::producer<Update, Error> updates() = 0;
|
|
||||||
|
|
||||||
virtual void updateFrameRequest(
|
virtual void updateFrameRequest(
|
||||||
not_null<const Animation*> animation,
|
not_null<const Animation*> animation,
|
||||||
const FrameRequest &request) = 0;
|
const FrameRequest &request) = 0;
|
||||||
|
virtual void markFrameShown() = 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 checkStep() = 0;
|
virtual void checkStep() = 0;
|
||||||
|
|
||||||
virtual ~Player() = default;
|
virtual ~Player() = default;
|
||||||
|
|
|
@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_frame_renderer.h"
|
#include "lottie/lottie_frame_renderer.h"
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
namespace {
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
SinglePlayer::SinglePlayer(
|
SinglePlayer::SinglePlayer(
|
||||||
const QByteArray &content,
|
const QByteArray &content,
|
||||||
|
@ -32,6 +29,12 @@ SinglePlayer::SinglePlayer(
|
||||||
, _renderer(FrameRenderer::Instance()) {
|
, _renderer(FrameRenderer::Instance()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SinglePlayer::~SinglePlayer() {
|
||||||
|
if (_state) {
|
||||||
|
_renderer->remove(_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SinglePlayer::start(
|
void SinglePlayer::start(
|
||||||
not_null<Animation*> animation,
|
not_null<Animation*> animation,
|
||||||
std::unique_ptr<SharedState> state) {
|
std::unique_ptr<SharedState> state) {
|
||||||
|
@ -40,7 +43,6 @@ void SinglePlayer::start(
|
||||||
_state = state.get();
|
_state = state.get();
|
||||||
auto information = state->information();
|
auto information = state->information();
|
||||||
state->start(this, crl::now());
|
state->start(this, crl::now());
|
||||||
_renderer = FrameRenderer::Instance();
|
|
||||||
_renderer->append(std::move(state));
|
_renderer->append(std::move(state));
|
||||||
_updates.fire({ std::move(information) });
|
_updates.fire({ std::move(information) });
|
||||||
|
|
||||||
|
@ -56,13 +58,7 @@ void SinglePlayer::failed(not_null<Animation*> animation, Error error) {
|
||||||
_updates.fire_error(std::move(error));
|
_updates.fire_error(std::move(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
SinglePlayer::~SinglePlayer() {
|
rpl::producer<Update, Error> SinglePlayer::updates() const {
|
||||||
if (_state) {
|
|
||||||
_renderer->remove(_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<Update, Error> SinglePlayer::updates() {
|
|
||||||
return _updates.events();
|
return _updates.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +83,7 @@ void SinglePlayer::checkNextFrameAvailability() {
|
||||||
|
|
||||||
_nextFrameTime = _state->nextFrameDisplayTime();
|
_nextFrameTime = _state->nextFrameDisplayTime();
|
||||||
if (_nextFrameTime != kTimeUnknown) {
|
if (_nextFrameTime != kTimeUnknown) {
|
||||||
checkStep();
|
checkNextFrameRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,8 +98,8 @@ void SinglePlayer::checkNextFrameRender() {
|
||||||
} else {
|
} else {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
|
||||||
_nextFrameTime = kTimeUnknown;
|
const auto exact = std::exchange(_nextFrameTime, kTimeUnknown);
|
||||||
const auto position = markFrameDisplayed(now);
|
const auto position = _state->markFrameDisplayed(now, now - exact);
|
||||||
_updates.fire({ DisplayFrameRequest{ position } });
|
_updates.fire({ DisplayFrameRequest{ position } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,19 +113,11 @@ void SinglePlayer::updateFrameRequest(
|
||||||
_renderer->updateFrameRequest(_state, request);
|
_renderer->updateFrameRequest(_state, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time SinglePlayer::markFrameDisplayed(crl::time now) {
|
void SinglePlayer::markFrameShown() {
|
||||||
Expects(_state != nullptr);
|
Expects(_state != nullptr);
|
||||||
|
|
||||||
return _state->markFrameDisplayed(now);
|
_state->markFrameShown();
|
||||||
}
|
_renderer->frameShown();
|
||||||
|
|
||||||
crl::time SinglePlayer::markFrameShown() {
|
|
||||||
Expects(_renderer != nullptr);
|
|
||||||
|
|
||||||
const auto result = _state->markFrameShown();
|
|
||||||
_renderer->frameShown(_state);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Lottie
|
} // namespace Lottie
|
||||||
|
|
|
@ -11,8 +11,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_animation.h"
|
#include "lottie/lottie_animation.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
#include <rpl/event_stream.h>
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
|
|
||||||
|
class FrameRenderer;
|
||||||
|
|
||||||
|
struct DisplayFrameRequest {
|
||||||
|
crl::time time = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Update {
|
||||||
|
base::variant<
|
||||||
|
Information,
|
||||||
|
DisplayFrameRequest> data;
|
||||||
|
};
|
||||||
|
|
||||||
class SinglePlayer final : public Player {
|
class SinglePlayer final : public Player {
|
||||||
public:
|
public:
|
||||||
SinglePlayer(
|
SinglePlayer(
|
||||||
|
@ -29,29 +43,24 @@ public:
|
||||||
not_null<Animation*> animation,
|
not_null<Animation*> animation,
|
||||||
std::unique_ptr<SharedState> state) override;
|
std::unique_ptr<SharedState> state) override;
|
||||||
void failed(not_null<Animation*> animation, Error error) 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(
|
void updateFrameRequest(
|
||||||
not_null<const Animation*> animation,
|
not_null<const Animation*> animation,
|
||||||
const FrameRequest &request) override;
|
const FrameRequest &request) override;
|
||||||
|
void markFrameShown() override;
|
||||||
// Returns frame position, if any frame was marked as displayed.
|
|
||||||
crl::time markFrameDisplayed(crl::time now) override;
|
|
||||||
crl::time markFrameShown() override;
|
|
||||||
|
|
||||||
void checkStep() override;
|
void checkStep() override;
|
||||||
|
|
||||||
|
rpl::producer<Update, Error> updates() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool ready() const;
|
||||||
|
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkNextFrameAvailability();
|
void checkNextFrameAvailability();
|
||||||
void checkNextFrameRender();
|
void checkNextFrameRender();
|
||||||
|
|
||||||
Animation _animation;
|
Animation _animation;
|
||||||
base::Timer _timer;
|
base::Timer _timer;
|
||||||
std::shared_ptr<FrameRenderer> _renderer;
|
const std::shared_ptr<FrameRenderer> _renderer;
|
||||||
SharedState *_state = nullptr;
|
SharedState *_state = nullptr;
|
||||||
crl::time _nextFrameTime = kTimeUnknown;
|
crl::time _nextFrameTime = kTimeUnknown;
|
||||||
rpl::event_stream<Update, Error> _updates;
|
rpl::event_stream<Update, Error> _updates;
|
||||||
|
|
|
@ -111,7 +111,7 @@ void Player::checkNextFrameAvailability() {
|
||||||
|
|
||||||
_nextFrameTime = _video->nextFrameDisplayTime();
|
_nextFrameTime = _video->nextFrameDisplayTime();
|
||||||
if (_nextFrameTime != kTimeUnknown) {
|
if (_nextFrameTime != kTimeUnknown) {
|
||||||
checkVideoStep();
|
checkNextFrameRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,8 @@
|
||||||
'<(src_loc)/lottie/lottie_common.h',
|
'<(src_loc)/lottie/lottie_common.h',
|
||||||
'<(src_loc)/lottie/lottie_frame_renderer.cpp',
|
'<(src_loc)/lottie/lottie_frame_renderer.cpp',
|
||||||
'<(src_loc)/lottie/lottie_frame_renderer.h',
|
'<(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_player.h',
|
||||||
'<(src_loc)/lottie/lottie_single_player.cpp',
|
'<(src_loc)/lottie/lottie_single_player.cpp',
|
||||||
'<(src_loc)/lottie/lottie_single_player.h',
|
'<(src_loc)/lottie/lottie_single_player.h',
|
||||||
|
|
Loading…
Reference in New Issue