/* 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_photo.h" #include "data/data_photo_media.h" #include "data/data_session.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" namespace Media::Stories { namespace { constexpr auto kGoodFadeDuration = crl::time(200); } // 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 photo, Data::FileOrigin origin, Fn update); QImage blurred() override; QImage good() override; private: const not_null _photo; const Fn _update; std::shared_ptr _media; rpl::lifetime _waitingLoading; }; class Sibling::LoaderVideo final : public Sibling::Loader , public base::has_weak_ptr { public: LoaderVideo( not_null video, Data::FileOrigin origin, Fn update); QImage blurred() override; QImage good() override; private: void waitForGoodThumbnail(); bool updateAfterGoodCheck(); void streamedFailed(); const not_null _video; const Data::FileOrigin _origin; const Fn _update; std::shared_ptr _media; std::unique_ptr _streamed; rpl::lifetime _waitingGoodGeneration; bool _checkingGoodInCache = false; bool _failed = false; }; Sibling::LoaderPhoto::LoaderPhoto( not_null photo, Data::FileOrigin origin, Fn 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::start_with_next([=] { if (_media->loaded()) { _update(); } }, _waitingLoading); } return QImage(); } Sibling::LoaderVideo::LoaderVideo( not_null video, Data::FileOrigin origin, Fn 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()) { if (!_checkingGoodInCache) { waitForGoodThumbnail(); } } else if (_failed) { return QImage(); } else if (!_streamed) { _streamed = std::make_unique( _video, _origin, [] {}); // waitingCallback _streamed->lockPlayer(); _streamed->player().updates( ) | rpl::start_with_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->ready()) { return _streamed->info().video.cover; } return QImage(); } 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::start_with_next([=] { strong->updateAfterGoodCheck(); }, strong->_waitingGoodGeneration); } } }); }); } bool Sibling::LoaderVideo::updateAfterGoodCheck() { if (!_video->goodThumbnailChecked()) { return false; } _checkingGoodInCache = false; _waitingGoodGeneration.destroy(); _update(); return true; } Sibling::Sibling( not_null controller, const Data::StoriesList &list) : _controller(controller) , _id{ list.user, list.items.front().id } { const auto &item = list.items.front(); const auto &data = item.media.data; const auto origin = Data::FileOrigin(); if (const auto video = std::get_if>(&data)) { _loader = std::make_unique((*video), origin, [=] { check(); }); } else if (const auto photo = std::get_if>(&data)) { _loader = std::make_unique((*photo), origin, [=] { check(); }); } else { Unexpected("Media type in stories list."); } _blurred = _loader->blurred(); check(); _goodShown.stop(); } Sibling::~Sibling() = default; Data::FullStoryId Sibling::shownId() const { return _id; } bool Sibling::shows(const Data::StoriesList &list) const { Expects(!list.items.empty()); return _id == Data::FullStoryId{ list.user, list.items.front().id }; } QImage Sibling::image() const { return _good.isNull() ? _blurred : _good; } 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