| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "lottie/lottie_multi_player.h"
- #include "lottie/details/lottie_frame_renderer.h"
- #include "lottie/lottie_animation.h"
- #include <range/v3/algorithm/remove.hpp>
- namespace Lottie {
- MultiPlayer::MultiPlayer(
- Quality quality,
- std::shared_ptr<FrameRenderer> renderer)
- : _quality(quality)
- , _timer([=] { checkNextFrameRender(); })
- , _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
- crl::on_main_update_requests(
- ) | rpl::start_with_next([=] {
- checkStep();
- }, _lifetime);
- }
- MultiPlayer::~MultiPlayer() {
- for (const auto &[animation, state] : _active) {
- _renderer->remove(state);
- }
- for (const auto &[animation, info] : _paused) {
- _renderer->remove(info.state);
- }
- }
- not_null<Animation*> MultiPlayer::append(
- FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
- FnMut<void(QByteArray &&cached)> put, // Unknown thread.
- const QByteArray &content,
- const FrameRequest &request) {
- #ifdef LOTTIE_USE_CACHE
- _animations.push_back(std::make_unique<Animation>(
- this,
- std::move(get),
- std::move(put),
- content,
- request,
- _quality));
- return _animations.back().get();
- #else // LOTTIE_USE_CACHE
- return append(content, request);
- #endif // LOTTIE_USE_CACHE
- }
- not_null<Animation*> MultiPlayer::append(
- const QByteArray &content,
- const FrameRequest &request) {
- _animations.push_back(std::make_unique<Animation>(
- this,
- content,
- request,
- _quality));
- return _animations.back().get();
- }
- void MultiPlayer::startAtRightTime(std::unique_ptr<SharedState> state) {
- if (_started == kTimeUnknown) {
- _started = crl::now();
- _lastSyncTime = kTimeUnknown;
- _delay = 0;
- }
- const auto lastSyncTime = (_lastSyncTime != kTimeUnknown)
- ? _lastSyncTime
- : _started;
- const auto frameIndex = countFrameIndex(
- state.get(),
- lastSyncTime,
- _delay);
- state->start(this, _started, _delay, frameIndex);
- const auto request = state->frameForPaint()->request;
- _renderer->append(std::move(state), request);
- }
- int MultiPlayer::countFrameIndex(
- not_null<SharedState*> state,
- crl::time time,
- crl::time delay) const {
- Expects(time != kTimeUnknown);
- const auto rate = state->information().frameRate;
- Assert(rate != 0);
- const auto framesTime = time - _started - delay;
- return ((framesTime + 1) * rate - 1) / 1000;
- }
- void MultiPlayer::start(
- not_null<Animation*> animation,
- std::unique_ptr<SharedState> state) {
- Expects(state != nullptr);
- const auto paused = _pausedBeforeStart.remove(animation);
- auto info = StartingInfo{ std::move(state), paused };
- if (_active.empty()
- || (_lastSyncTime == kTimeUnknown
- && _nextFrameTime == kTimeUnknown)) {
- addNewToActive(animation, std::move(info));
- } else {
- // We always try to mark as shown at the same time, so we start a new
- // animation at the same time we mark all existing as shown.
- _pendingToStart.emplace(animation, std::move(info));
- }
- _updates.fire({});
- }
- void MultiPlayer::addNewToActive(
- 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::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)) {
- addNewToActive(animation, std::move(info));
- }
- for (const auto &animation : base::take(_pendingRemove)) {
- removeNow(animation);
- }
- }
- void MultiPlayer::remove(not_null<Animation*> animation) {
- if (!_active.empty()) {
- _pendingRemove.emplace(animation);
- } else {
- removeNow(animation);
- }
- }
- void MultiPlayer::removeNow(not_null<Animation*> animation) {
- const auto i = _active.find(animation);
- if (i != end(_active)) {
- _renderer->remove(i->second);
- _active.erase(i);
- }
- const auto j = _paused.find(animation);
- if (j != end(_paused)) {
- _renderer->remove(j->second.state);
- _paused.erase(j);
- }
- _pendingRemove.remove(animation);
- _pendingToStart.remove(animation);
- _pendingPause.remove(animation);
- _pendingUnpause.remove(animation);
- _pausedBeforeStart.remove(animation);
- _animations.erase(
- ranges::remove(
- _animations,
- animation.get(),
- &std::unique_ptr<Animation>::get),
- end(_animations));
- if (_active.empty()) {
- _nextFrameTime = kTimeUnknown;
- _timer.cancel();
- if (_paused.empty()) {
- _started = kTimeUnknown;
- _lastSyncTime = kTimeUnknown;
- _delay = 0;
- }
- }
- }
- void MultiPlayer::pause(not_null<Animation*> animation) {
- if (_active.contains(animation)) {
- _pendingPause.emplace(animation);
- } else if (_paused.contains(animation)) {
- _pendingUnpause.remove(animation);
- } else if (const auto i = _pendingToStart.find(animation); i != end(_pendingToStart)) {
- i->second.paused = true;
- } else {
- _pausedBeforeStart.emplace(animation);
- }
- }
- void MultiPlayer::unpause(not_null<Animation*> animation) {
- if (const auto i = _paused.find(animation); i != end(_paused)) {
- if (_active.empty()) {
- unpauseFirst(animation, i->second.state);
- _paused.erase(i);
- } else {
- _pendingUnpause.emplace(animation);
- }
- } else if (_pendingPause.contains(animation)) {
- _pendingPause.remove(animation);
- } else {
- const auto i = _pendingToStart.find(animation);
- if (i != end(_pendingToStart)) {
- i->second.paused = false;
- } else {
- _pausedBeforeStart.remove(animation);
- }
- }
- }
- void MultiPlayer::unpauseFirst(
- not_null<Animation*> animation,
- not_null<SharedState*> state) {
- Expects(_lastSyncTime != kTimeUnknown);
- _active.emplace(animation, state);
- const auto now = crl::now();
- addTimelineDelay(now - _lastSyncTime);
- _lastSyncTime = now;
- markFrameShown();
- }
- void MultiPlayer::pauseAndSaveState(not_null<Animation*> animation) {
- Expects(_lastSyncTime != kTimeUnknown);
- const auto i = _active.find(animation);
- Assert(i != end(_active));
- _paused.emplace(
- animation,
- PausedInfo{ i->second, _lastSyncTime, _delay });
- _active.erase(i);
- }
- void MultiPlayer::unpauseAndKeepUp(not_null<Animation*> animation) {
- Expects(_lastSyncTime != kTimeUnknown);
- const auto i = _paused.find(animation);
- Assert(i != end(_paused));
- const auto state = i->second.state;
- const auto frameIndexAtPaused = countFrameIndex(
- state,
- i->second.pauseTime,
- i->second.pauseDelay);
- const auto frameIndexNow = countFrameIndex(
- state,
- _lastSyncTime,
- _delay);
- state->addTimelineDelay(
- (_delay - i->second.pauseDelay),
- frameIndexNow - frameIndexAtPaused);
- _active.emplace(animation, state);
- _paused.erase(i);
- }
- void MultiPlayer::failed(not_null<Animation*> animation, Error error) {
- //_updates.fire({ animation, error });
- }
- rpl::producer<MultiUpdate> MultiPlayer::updates() const {
- return _updates.events();
- }
- void MultiPlayer::checkStep() {
- if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) {
- return;
- } else if (_nextFrameTime != kTimeUnknown) {
- checkNextFrameRender();
- } else {
- checkNextFrameAvailability();
- }
- }
- void MultiPlayer::checkNextFrameAvailability() {
- Expects(_nextFrameTime == kTimeUnknown);
- auto next = kTimeUnknown;
- for (const auto &[animation, state] : _active) {
- const auto time = state->nextFrameDisplayTime();
- if (time == kTimeUnknown) {
- for (const auto &[animation, state] : _active) {
- if (state->nextFrameDisplayTime() != kTimeUnknown) {
- break;
- }
- }
- return;
- } else if (time == kFrameDisplayTimeAlreadyDone) {
- continue;
- }
- if (next == kTimeUnknown || next > time) {
- next = time;
- }
- }
- if (next == kTimeUnknown) {
- return;
- }
- _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();
- markFrameDisplayed(now);
- addTimelineDelay(now - _nextFrameTime);
- _lastSyncTime = now;
- _nextFrameTime = kFrameDisplayTimeAlreadyDone;
- processPending();
- _updates.fire({});
- }
- }
- void MultiPlayer::updateFrameRequest(
- not_null<const Animation*> animation,
- const FrameRequest &request) {
- const auto state = [&]() -> Lottie::SharedState* {
- if (const auto i = _active.find(animation); i != end(_active)) {
- return i->second;
- } else if (const auto j = _paused.find(animation);
- j != end(_paused)) {
- return j->second.state;
- } else if (const auto k = _pendingToStart.find(animation);
- k != end(_pendingToStart)) {
- return nullptr;
- }
- Unexpected("Animation in MultiPlayer::updateFrameRequest.");
- }();
- if (state) {
- _renderer->updateFrameRequest(state, request);
- }
- }
- void MultiPlayer::markFrameDisplayed(crl::time now) {
- Expects(!_active.empty());
- for (const auto &[animation, state] : _active) {
- const auto time = state->nextFrameDisplayTime();
- Assert(time != kTimeUnknown);
- if (time == kFrameDisplayTimeAlreadyDone) {
- continue;
- } else if (now >= time) {
- state->markFrameDisplayed(now);
- }
- }
- }
- void MultiPlayer::addTimelineDelay(crl::time delayed) {
- Expects(!_active.empty());
- for (const auto &[animation, state] : _active) {
- state->addTimelineDelay(delayed);
- }
- _delay += delayed;
- }
- bool MultiPlayer::markFrameShown() {
- if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
- _nextFrameTime = kTimeUnknown;
- }
- auto count = 0;
- for (const auto &[animation, state] : _active) {
- if (state->markFrameShown()) {
- ++count;
- }
- }
- if (count) {
- _renderer->frameShown();
- return true;
- }
- return false;
- }
- } // namespace Lottie
|