mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-15 19:47:03 +00:00
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;
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
|
@ -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>>;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
203
Telegram/SourceFiles/lottie/lottie_multi_player.cpp
Normal file
203
Telegram/SourceFiles/lottie/lottie_multi_player.cpp
Normal 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
|
77
Telegram/SourceFiles/lottie/lottie_multi_player.h
Normal file
77
Telegram/SourceFiles/lottie/lottie_multi_player.h
Normal 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
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -111,7 +111,7 @@ void Player::checkNextFrameAvailability() {
|
||||
|
||||
_nextFrameTime = _video->nextFrameDisplayTime();
|
||||
if (_nextFrameTime != kTimeUnknown) {
|
||||
checkVideoStep();
|
||||
checkNextFrameRender();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user