Play premium sticker effects.

This commit is contained in:
John Preston 2022-04-22 20:23:47 +04:00
parent d87c9c72fb
commit 935fb79c52
15 changed files with 168 additions and 40 deletions

View file

@ -254,6 +254,7 @@ void DocumentMedia::videoThumbnailWanted(Data::FileOrigin origin) {
void DocumentMedia::setVideoThumbnail(QByteArray content) {
_videoThumbnailBytes = std::move(content);
_videoThumbnailBytes.detach();
}
void DocumentMedia::checkStickerLarge() {

View file

@ -414,6 +414,7 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
| Flag::Scam
| Flag::Fake
| Flag::BotInlineGeo
| Flag::Premium
| Flag::Support
| (!minimal
? Flag::Contact
@ -2827,7 +2828,7 @@ void Session::documentApplyFields(
? Images::FromVideoSize(_session, data, *videoThumbnailSize)
: ImageWithLocation();
const auto isPremiumSticker = videoThumbnailSize
&& (videoThumbnailSize->c_videoSize().vtype().v == "fp");
&& (videoThumbnailSize->c_videoSize().vtype().v == "f");
documentApplyFields(
document,
data.vaccess_hash().v,

View file

@ -673,6 +673,9 @@ void InnerWidget::elementReplyTo(const FullMsgId &to) {
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
}
void InnerWidget::elementStartPremium(not_null<const Element*> view) {
}
void InnerWidget::elementShowSpoilerAnimation() {
_spoilerOpacity.stop();
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);

View file

@ -138,6 +138,8 @@ public:
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(
not_null<const HistoryView::Element*> view) override;
void elementStartPremium(
not_null<const HistoryView::Element*> view) override;
void elementShowSpoilerAnimation() override;
~InnerWidget();

View file

@ -254,6 +254,11 @@ public:
_widget->elementStartInteraction(view);
}
}
void elementStartPremium(not_null<const Element*> view) override {
if (_widget) {
_widget->elementStartPremium(view);
}
}
void elementShowSpoilerAnimation() override {
if (_widget) {
_widget->elementShowSpoilerAnimation();
@ -3171,6 +3176,11 @@ void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
_controller->emojiInteractions().startOutgoing(view);
}
void HistoryInner::elementStartPremium(not_null<const Element*> view) {
_emojiInteractions->playPremiumEffect(view);
_animatedStickersPlayed.emplace(view->data());
}
void HistoryInner::elementShowSpoilerAnimation() {
_spoilerOpacity.stop();
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);

View file

@ -145,6 +145,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
void elementReplyTo(const FullMsgId &to);
void elementStartInteraction(not_null<const Element*> view);
void elementStartPremium(not_null<const Element*> view);
void elementShowSpoilerAnimation();
void updateBotInfo(bool recount = true);

View file

@ -205,6 +205,10 @@ void SimpleElementDelegate::elementStartInteraction(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementStartPremium(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementShowSpoilerAnimation() {
}

View file

@ -104,6 +104,7 @@ public:
virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
virtual void elementReplyTo(const FullMsgId &to) = 0;
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
virtual void elementStartPremium(not_null<const Element*> view) = 0;
virtual void elementShowSpoilerAnimation() = 0;
virtual ~ElementDelegate() {
@ -162,6 +163,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementStartPremium(not_null<const Element*> view) override;
void elementShowSpoilerAnimation() override;
protected:

View file

@ -24,8 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
namespace {
constexpr auto kSizeMultiplier = 3;
constexpr auto kCachesCount = 4;
constexpr auto kEmojiMultiplier = 3;
constexpr auto kPremiumMultiplier = 2.25;
constexpr auto kEmojiCachesCount = 4;
constexpr auto kPremiumCachesCount = 8;
constexpr auto kMaxPlays = 5;
constexpr auto kMaxPlaysWithSmallDelay = 3;
constexpr auto kSmallDelay = crl::time(200);
@ -55,6 +57,7 @@ EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
}, _lifetime);
_emojiSize = Sticker::EmojiSize();
_premiumSize = Sticker::Size();
}
EmojiInteractions::~EmojiInteractions() = default;
@ -84,27 +87,63 @@ void EmojiInteractions::play(
}
}
void EmojiInteractions::playPremiumEffect(not_null<const Element*> view) {
if (const auto media = view->media()) {
if (const auto document = media->getDocument()) {
if (document->isPremiumSticker()) {
play(
QString(),
view,
document,
document->createMediaView()->videoThumbnailContent(),
QString(),
false,
true);
}
}
}
}
void EmojiInteractions::play(
QString emoticon,
not_null<Element*> view,
not_null<const Element*> view,
std::shared_ptr<Data::DocumentMedia> media,
bool incoming) {
play(
std::move(emoticon),
view,
media->owner(),
media->bytes(),
media->owner()->filepath(),
incoming,
false);
}
void EmojiInteractions::play(
QString emoticon,
not_null<const Element*> view,
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool incoming,
bool premium) {
const auto top = view->block()->y() + view->y();
const auto bottom = top + view->height();
if (_visibleTop >= bottom
|| _visibleBottom <= top
|| _visibleTop == _visibleBottom) {
|| _visibleTop == _visibleBottom
|| (data.isEmpty() && filepath.isEmpty())) {
return;
}
auto lottie = preparePlayer(media.get());
auto lottie = preparePlayer(document, data, filepath, premium);
const auto shift = GenerateRandomShift(_emojiSize);
const auto shift = premium ? QPoint() : GenerateRandomShift(_emojiSize);
lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
v::match(update.data, [&](const Lottie::Information &information) {
}, [&](const Lottie::DisplayFrameRequest &request) {
const auto rect = computeRect(view).translated(shift);
const auto rect = computeRect(view, premium).translated(shift);
if (rect.y() + rect.height() >= _visibleTop
&& rect.y() <= _visibleBottom) {
_updateRequests.fire_copy(rect);
@ -115,19 +154,30 @@ void EmojiInteractions::play(
.view = view,
.lottie = std::move(lottie),
.shift = shift,
.premium = premium,
});
if (incoming) {
_playStarted.fire(std::move(emoticon));
}
if (const auto media = view->media()) {
if (!premium) {
media->stickerClearLoopPlayed();
}
}
}
QSize EmojiInteractions::sizeFor(bool premium) const {
return premium
? (_premiumSize * kPremiumMultiplier)
: (_emojiSize * kEmojiMultiplier);
}
std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
not_null<Data::DocumentMedia*> media) {
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool premium) {
// Shortened copy from stickers_lottie module.
const auto document = media->owner();
const auto baseKey = document->bigFileBaseCacheKey();
const auto tag = uint8(0);
const auto keyShift = ((tag << 4) & 0xF0)
@ -149,10 +199,8 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
std::move(data));
});
};
const auto data = media->bytes();
const auto filepath = document->filepath();
const auto request = Lottie::FrameRequest{
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
sizeFor(premium) * style::DevicePixelRatio(),
};
auto &weakProvider = _sharedProviders[document];
auto shared = [&] {
@ -160,7 +208,7 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
return result;
}
const auto result = Lottie::SinglePlayer::SharedProvider(
kCachesCount,
premium ? kPremiumCachesCount : kEmojiCachesCount,
get,
put,
Lottie::ReadContent(data, filepath),
@ -179,19 +227,23 @@ void EmojiInteractions::visibleAreaUpdated(
_visibleBottom = visibleBottom;
}
QRect EmojiInteractions::computeRect(not_null<Element*> view) const {
QRect EmojiInteractions::computeRect(
not_null<const Element*> view,
bool premium) const {
const auto fullWidth = view->width();
const auto shift = (_emojiSize.width() * kSizeMultiplier) / 40;
const auto sticker = premium ? _premiumSize : _emojiSize;
const auto size = sizeFor(premium);
const auto shift = size.width() / 40;
const auto skip = (view->hasFromPhoto() ? st::msgPhotoSkip : 0)
+ st::msgMargin.left();
const auto rightAligned = view->hasOutLayout()
&& !view->delegate()->elementIsChatWide();
const auto left = rightAligned
? (fullWidth - skip + shift - _emojiSize.width() * kSizeMultiplier)
? (fullWidth - skip + shift - size.width())
: (skip - shift);
const auto viewTop = view->block()->y() + view->y() + view->marginTop();
const auto top = viewTop - _emojiSize.height();
return QRect(QPoint(left, top), _emojiSize * kSizeMultiplier);
const auto top = viewTop + (sticker.height() - size.height()) / 2;
return QRect(QPoint(left, top), size);
}
void EmojiInteractions::paint(QPainter &p) {
@ -201,7 +253,7 @@ void EmojiInteractions::paint(QPainter &p) {
continue;
}
auto request = Lottie::FrameRequest();
request.box = _emojiSize * kSizeMultiplier * factor;
request.box = sizeFor(play.premium) * factor;
const auto rightAligned = play.view->hasOutLayout()
&& !play.view->delegate()->elementIsChatWide();
if (!rightAligned) {
@ -217,7 +269,7 @@ void EmojiInteractions::paint(QPainter &p) {
if (play.frame + 1 == play.framesCount) {
play.finished = true;
}
const auto rect = computeRect(play.view);
const auto rect = computeRect(play.view, play.premium);
p.drawImage(
QRect(rect.topLeft() + play.shift, frame.image.size() / factor),
frame.image);

View file

@ -36,6 +36,7 @@ public:
void play(
ChatHelpers::EmojiInteractionPlayRequest request,
not_null<Element*> view);
void playPremiumEffect(not_null<const Element*> view);
void visibleAreaUpdated(int visibleTop, int visibleBottom);
void paint(QPainter &p);
@ -44,39 +45,55 @@ public:
private:
struct Play {
not_null<Element*> view;
not_null<const Element*> view;
std::unique_ptr<Lottie::SinglePlayer> lottie;
QPoint shift;
int frame = 0;
int framesCount = 0;
int frameRate = 0;
bool premium = false;
bool finished = false;
};
struct Delayed {
QString emoticon;
not_null<Element*> view;
not_null<const Element*> view;
std::shared_ptr<Data::DocumentMedia> media;
crl::time shouldHaveStartedAt = 0;
bool incoming = false;
};
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
[[nodiscard]] QRect computeRect(
not_null<const Element*> view,
bool premium) const;
void play(
QString emoticon,
not_null<Element*> view,
not_null<const Element*> view,
std::shared_ptr<Data::DocumentMedia> media,
bool incoming);
void play(
QString emoticon,
not_null<const Element*> view,
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool incoming,
bool premium);
void checkDelayed();
[[nodiscard]] QSize sizeFor(bool premium) const;
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> preparePlayer(
not_null<Data::DocumentMedia*> media);
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool premium);
const not_null<Main::Session*> _session;
int _visibleTop = 0;
int _visibleBottom = 0;
QSize _emojiSize;
QSize _premiumSize;
std::vector<Play> _plays;
std::vector<Delayed> _delayed;

View file

@ -1519,6 +1519,9 @@ void ListWidget::elementReplyTo(const FullMsgId &to) {
void ListWidget::elementStartInteraction(not_null<const Element*> view) {
}
void ListWidget::elementStartPremium(not_null<const Element*> view) {
}
void ListWidget::elementShowSpoilerAnimation() {
_spoilerOpacity.stop();
_spoilerOpacity.start([=] { update(); }, 0., 1., st::fadeWrapDuration);

View file

@ -292,6 +292,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementStartPremium(not_null<const Element*> view) override;
void elementShowSpoilerAnimation() override;
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);

View file

@ -68,6 +68,9 @@ Sticker::Sticker(
dataMediaCreated();
} else {
_data->loadThumbnail(parent->data()->fullId());
if (_data->isPremiumSticker()) {
_data->loadVideoThumbnail(parent->data()->fullId());
}
}
if (const auto media = replacing ? replacing->media() : nullptr) {
_lottie = media->stickerTakeLottie(_data, _replacements);
@ -123,7 +126,9 @@ bool Sticker::readyToDrawLottie() {
ensureDataMediaCreated();
_dataMedia->checkStickerLarge();
const auto loaded = _dataMedia->loaded();
if (sticker->isLottie() && !_lottie && loaded) {
const auto waitingForPremium = _data->isPremiumSticker()
&& _dataMedia->videoThumbnailContent().isEmpty();
if (sticker->isLottie() && !_lottie && loaded && !waitingForPremium) {
setupLottie();
}
return (_lottie && _lottie->ready());
@ -153,6 +158,19 @@ void Sticker::draw(
}
}
ClickHandlerPtr Sticker::link() {
return _link;
}
DocumentData *Sticker::document() {
return _data;
}
void Sticker::stickerClearLoopPlayed() {
_lottieOncePlayed = false;
_premiumEffectPlayed = false;
}
void Sticker::paintLottie(
Painter &p,
const PaintContext &context,
@ -162,6 +180,14 @@ void Sticker::paintLottie(
if (context.selected() && !_nextLastDiceFrame) {
request.colored = context.st->msgStickerOverlay()->c;
}
const auto premium = _data->isPremiumSticker();
if (premium) {
const auto rightAligned = _parent->hasOutLayout()
&& !_parent->delegate()->elementIsChatWide();
if (!rightAligned) {
request.mirrorHorizontal = true;
}
}
const auto frame = _lottie
? _lottie->frameInfo(request)
: Lottie::Animation::FrameInfo();
@ -201,10 +227,10 @@ void Sticker::paintLottie(
_framesCount = count;
_nextLastDiceFrame = !paused
&& (_diceIndex > 0)
&& (frame.index + 2 == count);
&& (_frameIndex + 2 == count);
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
const auto switchToNext = !playOnce
|| (!lastDiceFrame && (frame.index != 0 || !_lottieOncePlayed));
|| (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed));
if (!paused
&& switchToNext
&& _lottie->markFrameShown()
@ -338,6 +364,9 @@ void Sticker::dataMediaCreated() const {
if (_dataMedia->thumbnailPath().isEmpty()) {
_dataMedia->thumbnailWanted(_parent->data()->fullId());
}
if (_data->isPremiumSticker()) {
_data->loadVideoThumbnail(_parent->data()->fullId());
}
_parent->history()->owner().registerHeavyViewPart(_parent);
}
@ -355,6 +384,11 @@ void Sticker::setupLottie() {
ChatHelpers::StickerLottieSize::MessageHistory,
size() * cIntRetinaFactor(),
Lottie::Quality::High);
if (_data->isPremiumSticker()
&& !_premiumEffectPlayed) {
_premiumEffectPlayed = true;
_parent->delegate()->elementStartPremium(_parent);
}
lottieCreated();
}

View file

@ -43,16 +43,10 @@ public:
Painter &p,
const PaintContext &context,
const QRect &r) override;
ClickHandlerPtr link() override {
return _link;
}
ClickHandlerPtr link() override;
DocumentData *document() override {
return _data;
}
void stickerClearLoopPlayed() override {
_lottieOncePlayed = false;
}
DocumentData *document() override;
void stickerClearLoopPlayed() override;
std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
@ -111,6 +105,7 @@ private:
mutable int _frameIndex = -1;
mutable int _framesCount = -1;
mutable bool _lottieOncePlayed = false;
mutable bool _premiumEffectPlayed = false;
mutable bool _nextLastDiceFrame = false;
rpl::lifetime _lifetime;

View file

@ -45,7 +45,9 @@ void Document::writeToStream(QDataStream &stream, DocumentData *document) {
}
}
stream << qint32(document->getDuration());
if (document->type == StickerDocument) {
stream << qint32(document->isPremiumSticker() ? 1 : 0);
}
writeImageLocation(stream, document->thumbnailLocation());
stream << qint32(document->thumbnailByteSize());
writeImageLocation(stream, document->videoThumbnailLocation());