init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
431
Telegram/SourceFiles/media/stories/media_stories_sibling.cpp
Normal file
431
Telegram/SourceFiles/media/stories/media_stories_sibling.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
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 "media/stories/media_stories_sibling.h"
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/stories/media_stories_controller.h"
|
||||
#include "media/stories/media_stories_view.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_player.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
namespace {
|
||||
|
||||
constexpr auto kGoodFadeDuration = crl::time(200);
|
||||
constexpr auto kSiblingFade = 0.5;
|
||||
constexpr auto kSiblingFadeOver = 0.4;
|
||||
constexpr auto kSiblingNameOpacity = 0.8;
|
||||
constexpr auto kSiblingNameOpacityOver = 1.;
|
||||
constexpr auto kSiblingScaleOver = 0.05;
|
||||
|
||||
[[nodiscard]] StoryId LookupShownId(
|
||||
const Data::StoriesSource &source,
|
||||
StoryId suggestedId) {
|
||||
const auto i = suggestedId
|
||||
? source.ids.lower_bound(Data::StoryIdDates{ suggestedId })
|
||||
: end(source.ids);
|
||||
return (i != end(source.ids) && i->id == suggestedId)
|
||||
? suggestedId
|
||||
: source.toOpen().id;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Sibling::Loader {
|
||||
public:
|
||||
virtual ~Loader() = default;
|
||||
|
||||
virtual QImage blurred() = 0;
|
||||
virtual QImage good() = 0;
|
||||
};
|
||||
|
||||
class Sibling::LoaderPhoto final : public Sibling::Loader {
|
||||
public:
|
||||
LoaderPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
Data::FileOrigin origin,
|
||||
Fn<void()> update);
|
||||
|
||||
QImage blurred() override;
|
||||
QImage good() override;
|
||||
|
||||
private:
|
||||
const not_null<PhotoData*> _photo;
|
||||
const Fn<void()> _update;
|
||||
std::shared_ptr<Data::PhotoMedia> _media;
|
||||
rpl::lifetime _waitingLoading;
|
||||
|
||||
};
|
||||
|
||||
class Sibling::LoaderVideo final
|
||||
: public Sibling::Loader
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LoaderVideo(
|
||||
not_null<DocumentData*> video,
|
||||
Data::FileOrigin origin,
|
||||
Fn<void()> update);
|
||||
|
||||
QImage blurred() override;
|
||||
QImage good() override;
|
||||
|
||||
private:
|
||||
void waitForGoodThumbnail();
|
||||
bool updateAfterGoodCheck();
|
||||
void createStreamedPlayer();
|
||||
void streamedFailed();
|
||||
|
||||
const not_null<DocumentData*> _video;
|
||||
const Data::FileOrigin _origin;
|
||||
const Fn<void()> _update;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
std::unique_ptr<Streaming::Instance> _streamed;
|
||||
rpl::lifetime _waitingGoodGeneration;
|
||||
bool _checkingGoodInCache = false;
|
||||
bool _failed = false;
|
||||
|
||||
};
|
||||
|
||||
Sibling::LoaderPhoto::LoaderPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
Data::FileOrigin origin,
|
||||
Fn<void()> update)
|
||||
: _photo(photo)
|
||||
, _update(std::move(update))
|
||||
, _media(_photo->createMediaView()) {
|
||||
_photo->load(origin, LoadFromCloudOrLocal, true);
|
||||
}
|
||||
|
||||
QImage Sibling::LoaderPhoto::blurred() {
|
||||
if (const auto image = _media->thumbnailInline()) {
|
||||
return image->original();
|
||||
}
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::black);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage Sibling::LoaderPhoto::good() {
|
||||
if (const auto image = _media->image(Data::PhotoSize::Large)) {
|
||||
return image->original();
|
||||
} else if (!_waitingLoading) {
|
||||
_photo->session().downloaderTaskFinished(
|
||||
) | rpl::on_next([=] {
|
||||
if (_media->loaded()) {
|
||||
_update();
|
||||
}
|
||||
}, _waitingLoading);
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
Sibling::LoaderVideo::LoaderVideo(
|
||||
not_null<DocumentData*> video,
|
||||
Data::FileOrigin origin,
|
||||
Fn<void()> update)
|
||||
: _video(video)
|
||||
, _origin(origin)
|
||||
, _update(std::move(update))
|
||||
, _media(_video->createMediaView()) {
|
||||
_media->goodThumbnailWanted();
|
||||
}
|
||||
|
||||
QImage Sibling::LoaderVideo::blurred() {
|
||||
if (const auto image = _media->thumbnailInline()) {
|
||||
return image->original();
|
||||
}
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::black);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage Sibling::LoaderVideo::good() {
|
||||
if (const auto image = _media->goodThumbnail()) {
|
||||
return image->original();
|
||||
} else if (!_video->goodThumbnailChecked()
|
||||
&& !_video->goodThumbnailNoData()) {
|
||||
if (!_checkingGoodInCache) {
|
||||
waitForGoodThumbnail();
|
||||
}
|
||||
} else if (_failed) {
|
||||
return QImage();
|
||||
} else if (!_streamed) {
|
||||
createStreamedPlayer();
|
||||
} else if (_streamed->ready()) {
|
||||
return _streamed->info().video.cover;
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
void Sibling::LoaderVideo::createStreamedPlayer() {
|
||||
_streamed = std::make_unique<Streaming::Instance>(
|
||||
_video,
|
||||
_origin,
|
||||
[] {}); // waitingCallback
|
||||
_streamed->lockPlayer();
|
||||
_streamed->player().updates(
|
||||
) | rpl::on_next_error([=](Streaming::Update &&update) {
|
||||
v::match(update.data, [&](Streaming::Information &update) {
|
||||
_update();
|
||||
}, [](const auto &update) {
|
||||
});
|
||||
}, [=](Streaming::Error &&error) {
|
||||
streamedFailed();
|
||||
}, _streamed->lifetime());
|
||||
if (_streamed->ready()) {
|
||||
_update();
|
||||
} else if (!_streamed->valid()) {
|
||||
streamedFailed();
|
||||
} else if (!_streamed->player().active()
|
||||
&& !_streamed->player().finished()) {
|
||||
_streamed->play({
|
||||
.mode = Streaming::Mode::Video,
|
||||
});
|
||||
_streamed->pause();
|
||||
}
|
||||
}
|
||||
|
||||
void Sibling::LoaderVideo::streamedFailed() {
|
||||
_failed = true;
|
||||
_streamed = nullptr;
|
||||
_update();
|
||||
}
|
||||
|
||||
void Sibling::LoaderVideo::waitForGoodThumbnail() {
|
||||
_checkingGoodInCache = true;
|
||||
const auto weak = make_weak(this);
|
||||
_video->owner().cache().get({}, [=](const auto &) {
|
||||
crl::on_main([=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
if (!strong->updateAfterGoodCheck()) {
|
||||
strong->_video->session().downloaderTaskFinished(
|
||||
) | rpl::on_next([=] {
|
||||
strong->updateAfterGoodCheck();
|
||||
}, strong->_waitingGoodGeneration);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool Sibling::LoaderVideo::updateAfterGoodCheck() {
|
||||
if (!_video->goodThumbnailChecked()
|
||||
&& !_video->goodThumbnailNoData()) {
|
||||
return false;
|
||||
}
|
||||
_checkingGoodInCache = false;
|
||||
_waitingGoodGeneration.destroy();
|
||||
_update();
|
||||
return true;
|
||||
}
|
||||
|
||||
Sibling::Sibling(
|
||||
not_null<Controller*> controller,
|
||||
const Data::StoriesSource &source,
|
||||
StoryId suggestedId)
|
||||
: _controller(controller)
|
||||
, _id{ source.peer->id, LookupShownId(source, suggestedId) }
|
||||
, _peer(source.peer) {
|
||||
checkStory();
|
||||
_goodShown.stop();
|
||||
}
|
||||
|
||||
Sibling::~Sibling() = default;
|
||||
|
||||
void Sibling::checkStory() {
|
||||
const auto maybeStory = _peer->owner().stories().lookup(_id);
|
||||
if (!maybeStory) {
|
||||
if (_blurred.isNull()) {
|
||||
setBlackThumbnail();
|
||||
if (maybeStory.error() == Data::NoStory::Unknown) {
|
||||
_peer->owner().stories().resolve(_id, crl::guard(this, [=] {
|
||||
checkStory();
|
||||
}));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto story = *maybeStory;
|
||||
const auto origin = Data::FileOrigin();
|
||||
v::match(story->media().data, [&](not_null<PhotoData*> photo) {
|
||||
_loader = std::make_unique<LoaderPhoto>(photo, origin, [=] {
|
||||
check();
|
||||
});
|
||||
}, [&](not_null<DocumentData*> document) {
|
||||
_loader = std::make_unique<LoaderVideo>(document, origin, [=] {
|
||||
check();
|
||||
});
|
||||
}, [&](const std::shared_ptr<Data::GroupCall> &call) {
|
||||
_loader = nullptr;
|
||||
}, [&](v::null_t) {
|
||||
_loader = nullptr;
|
||||
});
|
||||
if (!_loader) {
|
||||
setBlackThumbnail();
|
||||
return;
|
||||
}
|
||||
_blurred = _loader->blurred();
|
||||
check();
|
||||
}
|
||||
|
||||
void Sibling::setBlackThumbnail() {
|
||||
_blurred = QImage(
|
||||
st::storiesMaxSize,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_blurred.fill(Qt::black);
|
||||
}
|
||||
|
||||
FullStoryId Sibling::shownId() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
not_null<PeerData*> Sibling::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
bool Sibling::shows(
|
||||
const Data::StoriesSource &source,
|
||||
StoryId suggestedId) const {
|
||||
const auto fullId = FullStoryId{
|
||||
source.peer->id,
|
||||
LookupShownId(source, suggestedId),
|
||||
};
|
||||
return (_id == fullId);
|
||||
}
|
||||
|
||||
SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {
|
||||
const auto name = nameImage(layout);
|
||||
return {
|
||||
.image = _good.isNull() ? _blurred : _good,
|
||||
.layout = {
|
||||
.geometry = layout.geometry,
|
||||
.fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over,
|
||||
.radius = st::storiesRadius,
|
||||
},
|
||||
.userpic = userpicImage(layout),
|
||||
.userpicPosition = layout.userpic.topLeft(),
|
||||
.name = name,
|
||||
.namePosition = namePosition(layout, name),
|
||||
.nameOpacity = (kSiblingNameOpacity * (1 - over)
|
||||
+ kSiblingNameOpacityOver * over),
|
||||
.scale = 1. + (over * kSiblingScaleOver),
|
||||
};
|
||||
}
|
||||
|
||||
QImage Sibling::userpicImage(const SiblingLayout &layout) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = layout.userpic.width() * ratio;
|
||||
const auto key = _peer->userpicUniqueKey(_userpicView);
|
||||
if (_userpicImage.width() != size || _userpicKey != key) {
|
||||
_userpicKey = key;
|
||||
_userpicImage = PeerData::GenerateUserpicImage(
|
||||
_peer,
|
||||
_userpicView,
|
||||
size);
|
||||
_userpicImage.setDevicePixelRatio(ratio);
|
||||
}
|
||||
return _userpicImage;
|
||||
}
|
||||
|
||||
QImage Sibling::nameImage(const SiblingLayout &layout) {
|
||||
if (_nameFontSize != layout.nameFontSize) {
|
||||
_nameFontSize = layout.nameFontSize;
|
||||
|
||||
const auto family = 0; // Default font family.
|
||||
const auto font = style::font(
|
||||
_nameFontSize,
|
||||
style::FontFlag::Semibold,
|
||||
family);
|
||||
_name.reset();
|
||||
_nameStyle = std::make_unique<style::TextStyle>(style::TextStyle{
|
||||
.font = font,
|
||||
});
|
||||
};
|
||||
const auto text = _peer->isSelf()
|
||||
? tr::lng_stories_my_name(tr::now)
|
||||
: _peer->shortName();
|
||||
if (_nameText != text) {
|
||||
_name.reset();
|
||||
_nameText = text;
|
||||
}
|
||||
if (!_name) {
|
||||
_nameAvailableWidth = 0;
|
||||
_name.emplace(*_nameStyle, _nameText);
|
||||
}
|
||||
const auto available = layout.nameBoundingRect.width();
|
||||
const auto wasCut = (_nameAvailableWidth < _name->maxWidth());
|
||||
const auto nowCut = (available < _name->maxWidth());
|
||||
if (_nameImage.isNull()
|
||||
|| _nameAvailableWidth != layout.nameBoundingRect.width()) {
|
||||
_nameAvailableWidth = layout.nameBoundingRect.width();
|
||||
if (_nameImage.isNull() || nowCut || wasCut) {
|
||||
const auto w = std::min(_nameAvailableWidth, _name->maxWidth());
|
||||
const auto h = _nameStyle->font->height;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
_nameImage = QImage(
|
||||
QSize(w, h) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_nameImage.setDevicePixelRatio(ratio);
|
||||
_nameImage.fill(Qt::transparent);
|
||||
auto p = Painter(&_nameImage);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setFont(_nameStyle->font);
|
||||
p.setPen(Qt::white);
|
||||
_name->drawLeftElided(p, 0, 0, w, w);
|
||||
}
|
||||
}
|
||||
return _nameImage;
|
||||
}
|
||||
|
||||
QPoint Sibling::namePosition(
|
||||
const SiblingLayout &layout,
|
||||
const QImage &image) const {
|
||||
const auto size = image.size() / image.devicePixelRatio();
|
||||
const auto width = size.width();
|
||||
const auto bounding = layout.nameBoundingRect;
|
||||
const auto left = layout.geometry.x()
|
||||
+ (layout.geometry.width() - width) / 2;
|
||||
const auto top = bounding.y() + bounding.height() - size.height();
|
||||
if (left < bounding.x()) {
|
||||
return { bounding.x(), top };
|
||||
} else if (left + width > bounding.x() + bounding.width()) {
|
||||
return { bounding.x() + bounding.width() - width, top };
|
||||
}
|
||||
return { left, top };
|
||||
}
|
||||
|
||||
void Sibling::check() {
|
||||
Expects(_loader != nullptr);
|
||||
|
||||
auto good = _loader->good();
|
||||
if (good.isNull()) {
|
||||
return;
|
||||
}
|
||||
_loader = nullptr;
|
||||
_good = std::move(good);
|
||||
_goodShown.start([=] {
|
||||
_controller->repaintSibling(this);
|
||||
}, 0., 1., kGoodFadeDuration, anim::linear);
|
||||
}
|
||||
|
||||
} // namespace Media::Stories
|
||||
Reference in New Issue
Block a user