mirror of https://github.com/procxx/kepka.git
				
				
				
			Use Data::DocumentMedia to store good thumbnails.
This commit is contained in:
		
							parent
							
								
									61647275e8
								
							
						
					
					
						commit
						7db53599e8
					
				|  | @ -360,6 +360,8 @@ PRIVATE | ||||||
|     data/data_document.h |     data/data_document.h | ||||||
|     data/data_document_good_thumbnail.cpp |     data/data_document_good_thumbnail.cpp | ||||||
|     data/data_document_good_thumbnail.h |     data/data_document_good_thumbnail.h | ||||||
|  |     data/data_document_media.cpp | ||||||
|  |     data/data_document_media.h | ||||||
|     data/data_drafts.cpp |     data/data_drafts.cpp | ||||||
|     data/data_drafts.h |     data/data_drafts.h | ||||||
|     data/data_folder.cpp |     data/data_folder.cpp | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "data/data_session.h" | #include "data/data_session.h" | ||||||
| #include "data/data_streaming.h" | #include "data/data_streaming.h" | ||||||
| #include "data/data_document_good_thumbnail.h" | #include "data/data_document_good_thumbnail.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "lang/lang_keys.h" | #include "lang/lang_keys.h" | ||||||
| #include "inline_bots/inline_bot_layout_item.h" | #include "inline_bots/inline_bot_layout_item.h" | ||||||
| #include "main/main_session.h" | #include "main/main_session.h" | ||||||
|  | @ -569,7 +570,6 @@ void DocumentData::setattributes( | ||||||
| 			_additional = nullptr; | 			_additional = nullptr; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	validateGoodThumbnail(); |  | ||||||
| 	if (isAudioFile() || isAnimation() || isVoiceMessage()) { | 	if (isAudioFile() || isAnimation() || isVoiceMessage()) { | ||||||
| 		setMaybeSupportsStreaming(true); | 		setMaybeSupportsStreaming(true); | ||||||
| 	} | 	} | ||||||
|  | @ -612,7 +612,6 @@ bool DocumentData::checkWallPaperProperties() { | ||||||
| 		return false; // #TODO themes support svg patterns
 | 		return false; // #TODO themes support svg patterns
 | ||||||
| 	} | 	} | ||||||
| 	type = WallPaperDocument; | 	type = WallPaperDocument; | ||||||
| 	validateGoodThumbnail(); |  | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -661,48 +660,65 @@ Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const { | ||||||
| 	return Data::DocumentThumbCacheKey(_dc, id); | 	return Data::DocumentThumbCacheKey(_dc, id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Image *DocumentData::goodThumbnail() const { | bool DocumentData::goodThumbnailChecked() const { | ||||||
| 	return _goodThumbnail.get(); | 	return (_goodThumbnailState & GoodThumbnailFlag::Mask) | ||||||
|  | 		== GoodThumbnailFlag::Checked; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DocumentData::validateGoodThumbnail() { | bool DocumentData::goodThumbnailGenerating() const { | ||||||
| 	if (!isVideoFile() | 	return (_goodThumbnailState & GoodThumbnailFlag::Mask) | ||||||
| 		&& !isAnimation() | 		== GoodThumbnailFlag::Generating; | ||||||
| 		&& !isWallPaper() |  | ||||||
| 		&& !isTheme() |  | ||||||
| 		&& (!sticker() || !sticker()->animated)) { |  | ||||||
| 		_goodThumbnail = nullptr; |  | ||||||
| 	} else if (!_goodThumbnail && hasRemoteLocation()) { |  | ||||||
| 		_goodThumbnail = std::make_unique<Image>( |  | ||||||
| 			std::make_unique<Data::GoodThumbSource>(this)); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DocumentData::refreshGoodThumbnail() { | bool DocumentData::goodThumbnailNoData() const { | ||||||
| 	if (_goodThumbnail && hasRemoteLocation()) { | 	return (_goodThumbnailState & GoodThumbnailFlag::Mask) | ||||||
| 		replaceGoodThumbnail(std::make_unique<Data::GoodThumbSource>(this)); | 		== GoodThumbnailFlag::NoData; | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DocumentData::replaceGoodThumbnail( | void DocumentData::setGoodThumbnailGenerating() { | ||||||
| 		std::unique_ptr<Images::Source> &&source) { | 	_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask) | ||||||
| 	_goodThumbnail->replaceSource(std::move(source)); | 		| GoodThumbnailFlag::Generating; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DocumentData::setGoodThumbnailOnUpload( | void DocumentData::setGoodThumbnailDataReady() { | ||||||
| 		QImage &&image, | 	_goodThumbnailState = GoodThumbnailFlag::DataReady | ||||||
| 		QByteArray &&bytes) { | 		| (goodThumbnailNoData() | ||||||
| 	Expects(uploadingData != nullptr); | 			? GoodThumbnailFlag(0) | ||||||
|  | 			: (_goodThumbnailState & GoodThumbnailFlag::Mask)); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	if (image.isNull()) { | void DocumentData::setGoodThumbnailChecked(bool hasData) { | ||||||
|  | 	if (!hasData && (_goodThumbnailState & GoodThumbnailFlag::DataReady)) { | ||||||
|  | 		_goodThumbnailState &= ~GoodThumbnailFlag::DataReady; | ||||||
|  | 		_goodThumbnailState &= ~GoodThumbnailFlag::Mask; | ||||||
|  | 		Data::DocumentMedia::CheckGoodThumbnail(this); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	_goodThumbnail = std::make_unique<Image>( | 	_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask) | ||||||
| 		std::make_unique<Images::LocalFileSource>( | 		| (hasData | ||||||
| 			QString(), | 			? GoodThumbnailFlag::Checked | ||||||
| 			std::move(bytes), | 			: GoodThumbnailFlag::NoData); | ||||||
| 			sticker() ? "WEBP" : "JPG", | } | ||||||
| 			std::move(image))); | 
 | ||||||
|  | std::shared_ptr<Data::DocumentMedia> DocumentData::createMediaView() { | ||||||
|  | 	if (auto result = activeMediaView()) { | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  | 	auto result = std::make_shared<Data::DocumentMedia>(this); | ||||||
|  | 	_media = result; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<Data::DocumentMedia> DocumentData::activeMediaView() { | ||||||
|  | 	return _media.lock(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DocumentData::setGoodThumbnailPhoto(not_null<PhotoData*> photo) { | ||||||
|  | 	_goodThumbnailPhoto = photo; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PhotoData *DocumentData::goodThumbnailPhoto() const { | ||||||
|  | 	return _goodThumbnailPhoto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| auto DocumentData::bigFileBaseCacheKey() const | auto DocumentData::bigFileBaseCacheKey() const | ||||||
|  | @ -817,7 +833,8 @@ bool DocumentData::loaded(FilePathResolve resolve) const { | ||||||
| 				ActiveCache().increment(ComputeUsage(that->sticker())); | 				ActiveCache().increment(ComputeUsage(that->sticker())); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			that->refreshGoodThumbnail(); | 			that->setGoodThumbnailDataReady(); | ||||||
|  | 			Data::DocumentMedia::CheckGoodThumbnail(that); | ||||||
| 			destroyLoader(); | 			destroyLoader(); | ||||||
| 
 | 
 | ||||||
| 			if (!that->_data.isEmpty() || that->getStickerLarge()) { | 			if (!that->_data.isEmpty() || that->getStickerLarge()) { | ||||||
|  | @ -1606,7 +1623,6 @@ void DocumentData::setRemoteLocation( | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	validateGoodThumbnail(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DocumentData::setContentUrl(const QString &url) { | void DocumentData::setContentUrl(const QString &url) { | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ class Loader; | ||||||
| 
 | 
 | ||||||
| namespace Data { | namespace Data { | ||||||
| class Session; | class Session; | ||||||
|  | class DocumentMedia; | ||||||
| } // namespace Data
 | } // namespace Data
 | ||||||
| 
 | 
 | ||||||
| namespace Main { | namespace Main { | ||||||
|  | @ -180,11 +181,18 @@ public: | ||||||
| 		ImagePtr thumbnailInline, | 		ImagePtr thumbnailInline, | ||||||
| 		ImagePtr thumbnail); | 		ImagePtr thumbnail); | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] Image *goodThumbnail() const; |  | ||||||
| 	[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const; | 	[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const; | ||||||
| 	void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes); | 	[[nodiscard]] bool goodThumbnailChecked() const; | ||||||
| 	void refreshGoodThumbnail(); | 	[[nodiscard]] bool goodThumbnailGenerating() const; | ||||||
| 	void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source); | 	[[nodiscard]] bool goodThumbnailNoData() const; | ||||||
|  | 	void setGoodThumbnailGenerating(); | ||||||
|  | 	void setGoodThumbnailDataReady(); | ||||||
|  | 	void setGoodThumbnailChecked(bool hasData); | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] std::shared_ptr<Data::DocumentMedia> createMediaView(); | ||||||
|  | 	[[nodiscard]] std::shared_ptr<Data::DocumentMedia> activeMediaView(); | ||||||
|  | 	void setGoodThumbnailPhoto(not_null<PhotoData*> photo); | ||||||
|  | 	[[nodiscard]] PhotoData *goodThumbnailPhoto() const; | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] auto bigFileBaseCacheKey() const | 	[[nodiscard]] auto bigFileBaseCacheKey() const | ||||||
| 	-> std::optional<Storage::Cache::Key>; | 	-> std::optional<Storage::Cache::Key>; | ||||||
|  | @ -258,6 +266,17 @@ private: | ||||||
| 	using Flags = base::flags<Flag>; | 	using Flags = base::flags<Flag>; | ||||||
| 	friend constexpr bool is_flag_type(Flag) { return true; }; | 	friend constexpr bool is_flag_type(Flag) { return true; }; | ||||||
| 
 | 
 | ||||||
|  | 	enum class GoodThumbnailFlag : uchar { | ||||||
|  | 		Checked = 0x01, | ||||||
|  | 		Generating = 0x02, | ||||||
|  | 		NoData = 0x03, | ||||||
|  | 		Mask = 0x03, | ||||||
|  | 
 | ||||||
|  | 		DataReady = 0x04, | ||||||
|  | 	}; | ||||||
|  | 	using GoodThumbnailState = base::flags<GoodThumbnailFlag>; | ||||||
|  | 	friend constexpr bool is_flag_type(GoodThumbnailFlag) { return true; }; | ||||||
|  | 
 | ||||||
| 	static constexpr Flags kStreamingSupportedMask = Flags() | 	static constexpr Flags kStreamingSupportedMask = Flags() | ||||||
| 		| Flag::StreamingMaybeYes | 		| Flag::StreamingMaybeYes | ||||||
| 		| Flag::StreamingMaybeNo; | 		| Flag::StreamingMaybeNo; | ||||||
|  | @ -272,9 +291,8 @@ private: | ||||||
| 
 | 
 | ||||||
| 	friend class Serialize::Document; | 	friend class Serialize::Document; | ||||||
| 
 | 
 | ||||||
| 	LocationType locationType() const; | 	[[nodiscard]] LocationType locationType() const; | ||||||
| 	void validateLottieSticker(); | 	void validateLottieSticker(); | ||||||
| 	void validateGoodThumbnail(); |  | ||||||
| 	void setMaybeSupportsStreaming(bool supports); | 	void setMaybeSupportsStreaming(bool supports); | ||||||
| 	void setLoadedInMediaCacheLocation(); | 	void setLoadedInMediaCacheLocation(); | ||||||
| 
 | 
 | ||||||
|  | @ -294,8 +312,9 @@ private: | ||||||
| 
 | 
 | ||||||
| 	ImagePtr _thumbnailInline; | 	ImagePtr _thumbnailInline; | ||||||
| 	ImagePtr _thumbnail; | 	ImagePtr _thumbnail; | ||||||
| 	std::unique_ptr<Image> _goodThumbnail; |  | ||||||
| 	Data::ReplyPreview _replyPreview; | 	Data::ReplyPreview _replyPreview; | ||||||
|  | 	std::weak_ptr<Data::DocumentMedia> _media; | ||||||
|  | 	PhotoData *_goodThumbnailPhoto = nullptr; | ||||||
| 
 | 
 | ||||||
| 	not_null<Data::Session*> _owner; | 	not_null<Data::Session*> _owner; | ||||||
| 
 | 
 | ||||||
|  | @ -304,6 +323,7 @@ private: | ||||||
| 	std::unique_ptr<DocumentAdditionalData> _additional; | 	std::unique_ptr<DocumentAdditionalData> _additional; | ||||||
| 	int32 _duration = -1; | 	int32 _duration = -1; | ||||||
| 	mutable Flags _flags = kStreamingSupportedUnknown; | 	mutable Flags _flags = kStreamingSupportedUnknown; | ||||||
|  | 	GoodThumbnailState _goodThumbnailState = GoodThumbnailState(); | ||||||
| 	mutable std::unique_ptr<FileLoader> _loader; | 	mutable std::unique_ptr<FileLoader> _loader; | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,202 @@ | ||||||
|  | /*
 | ||||||
|  | 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 "data/data_document_media.h" | ||||||
|  | 
 | ||||||
|  | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_good_thumbnail.h" | ||||||
|  | #include "data/data_session.h" | ||||||
|  | #include "data/data_cloud_themes.h" | ||||||
|  | #include "media/clip/media_clip_reader.h" | ||||||
|  | #include "main/main_session.h" | ||||||
|  | #include "lottie/lottie_animation.h" | ||||||
|  | #include "window/themes/window_theme_preview.h" | ||||||
|  | #include "ui/image/image.h" | ||||||
|  | #include "app.h" | ||||||
|  | 
 | ||||||
|  | #include <QtCore/QBuffer> | ||||||
|  | #include <QtGui/QImageReader> | ||||||
|  | 
 | ||||||
|  | namespace Data { | ||||||
|  | namespace { | ||||||
|  | 
 | ||||||
|  | constexpr auto kReadAreaLimit = 12'032 * 9'024; | ||||||
|  | constexpr auto kWallPaperThumbnailLimit = 960; | ||||||
|  | constexpr auto kMaxVideoFrameArea = 7'680 * 4'320; | ||||||
|  | constexpr auto kGoodThumbQuality = 87; | ||||||
|  | 
 | ||||||
|  | enum class FileType { | ||||||
|  | 	Video, | ||||||
|  | 	AnimatedSticker, | ||||||
|  | 	WallPaper, | ||||||
|  | 	Theme, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | [[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) { | ||||||
|  | 	return owner->isVideoFile() | ||||||
|  | 		|| owner->isAnimation() | ||||||
|  | 		|| owner->isWallPaper() | ||||||
|  | 		|| owner->isTheme() | ||||||
|  | 		|| (owner->sticker() && owner->sticker()->animated); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | [[nodiscard]] QImage PrepareGoodThumbnail( | ||||||
|  | 		const QString &path, | ||||||
|  | 		QByteArray data, | ||||||
|  | 		FileType type) { | ||||||
|  | 	if (type == FileType::Video) { | ||||||
|  | 		return ::Media::Clip::PrepareForSending(path, data).thumbnail; | ||||||
|  | 	} else if (type == FileType::AnimatedSticker) { | ||||||
|  | 		return Lottie::ReadThumbnail(Lottie::ReadContent(data, path)); | ||||||
|  | 	} else if (type == FileType::Theme) { | ||||||
|  | 		return Window::Theme::GeneratePreview(data, path); | ||||||
|  | 	} | ||||||
|  | 	auto buffer = QBuffer(&data); | ||||||
|  | 	auto file = QFile(path); | ||||||
|  | 	auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer; | ||||||
|  | 	auto reader = QImageReader(device); | ||||||
|  | 	const auto size = reader.size(); | ||||||
|  | 	if (!reader.canRead() | ||||||
|  | 		|| (size.width() * size.height() > kReadAreaLimit)) { | ||||||
|  | 		return QImage(); | ||||||
|  | 	} | ||||||
|  | 	auto result = reader.read(); | ||||||
|  | 	if (!result.width() || !result.height()) { | ||||||
|  | 		return QImage(); | ||||||
|  | 	} | ||||||
|  | 	return (result.width() > kWallPaperThumbnailLimit | ||||||
|  | 		|| result.height() > kWallPaperThumbnailLimit) | ||||||
|  | 		? result.scaled( | ||||||
|  | 			kWallPaperThumbnailLimit, | ||||||
|  | 			kWallPaperThumbnailLimit, | ||||||
|  | 			Qt::KeepAspectRatio, | ||||||
|  | 			Qt::SmoothTransformation) | ||||||
|  | 		: result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace
 | ||||||
|  | 
 | ||||||
|  | DocumentMedia::DocumentMedia(not_null<DocumentData*> owner) | ||||||
|  | : _owner(owner) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DocumentMedia::~DocumentMedia() = default; | ||||||
|  | 
 | ||||||
|  | void DocumentMedia::goodThumbnailWanted() { | ||||||
|  | 	_flags |= Flag::GoodThumbnailWanted; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Image *DocumentMedia::goodThumbnail() const { | ||||||
|  | 	Expects((_flags & Flag::GoodThumbnailWanted) != 0); | ||||||
|  | 
 | ||||||
|  | 	if (!_goodThumbnail) { | ||||||
|  | 		ReadOrGenerateThumbnail(_owner); | ||||||
|  | 	} | ||||||
|  | 	return _goodThumbnail.get(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DocumentMedia::setGoodThumbnail(QImage thumbnail) { | ||||||
|  | 	if (!(_flags & Flag::GoodThumbnailWanted)) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	_goodThumbnail = std::make_unique<Image>( | ||||||
|  | 		std::make_unique<Images::ImageSource>(std::move(thumbnail), "PNG")); | ||||||
|  | 	_owner->session().downloaderTaskFinished().notify(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DocumentMedia::GenerateGoodThumbnail(not_null<DocumentData*> document) { | ||||||
|  | 	const auto data = document->data(); | ||||||
|  | 	const auto type = document->isWallPaper() | ||||||
|  | 		? FileType::WallPaper | ||||||
|  | 		: document->isTheme() | ||||||
|  | 		? FileType::Theme | ||||||
|  | 		: document->sticker() | ||||||
|  | 		? FileType::AnimatedSticker | ||||||
|  | 		: FileType::Video; | ||||||
|  | 	auto location = document->location().isEmpty() | ||||||
|  | 		? nullptr | ||||||
|  | 		: std::make_unique<FileLocation>(document->location()); | ||||||
|  | 	if (data.isEmpty() && !location) { | ||||||
|  | 		document->setGoodThumbnailChecked(false); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	const auto guard = base::make_weak(&document->owner().session()); | ||||||
|  | 	crl::async([=, location = std::move(location)] { | ||||||
|  | 		const auto filepath = (location && location->accessEnable()) | ||||||
|  | 			? location->name() | ||||||
|  | 			: QString(); | ||||||
|  | 		auto result = PrepareGoodThumbnail(filepath, data, type); | ||||||
|  | 		auto bytes = QByteArray(); | ||||||
|  | 		if (!result.isNull()) { | ||||||
|  | 			auto buffer = QBuffer(&bytes); | ||||||
|  | 			const auto format = (type == FileType::AnimatedSticker) | ||||||
|  | 				? "WEBP" | ||||||
|  | 				: (type == FileType::WallPaper && result.hasAlphaChannel()) | ||||||
|  | 				? "PNG" | ||||||
|  | 				: "JPG"; | ||||||
|  | 			result.save(&buffer, format, kGoodThumbQuality); | ||||||
|  | 		} | ||||||
|  | 		if (!filepath.isEmpty()) { | ||||||
|  | 			location->accessDisable(); | ||||||
|  | 		} | ||||||
|  | 		const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes; | ||||||
|  | 		crl::on_main(guard, [=] { | ||||||
|  | 			document->setGoodThumbnailChecked(true); | ||||||
|  | 			if (const auto active = document->activeMediaView()) { | ||||||
|  | 				active->setGoodThumbnail(result); | ||||||
|  | 			} | ||||||
|  | 			document->owner().cache().put( | ||||||
|  | 				document->goodThumbnailCacheKey(), | ||||||
|  | 				Storage::Cache::Database::TaggedValue{ | ||||||
|  | 					base::duplicate(cache), | ||||||
|  | 					kImageCacheTag }); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) { | ||||||
|  | 	if (!document->goodThumbnailChecked()) { | ||||||
|  | 		ReadOrGenerateThumbnail(document); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DocumentMedia::ReadOrGenerateThumbnail( | ||||||
|  | 		not_null<DocumentData*> document) { | ||||||
|  | 	if (document->goodThumbnailGenerating() | ||||||
|  | 		|| document->goodThumbnailNoData() | ||||||
|  | 		|| !MayHaveGoodThumbnail(document)) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	document->setGoodThumbnailGenerating(); | ||||||
|  | 
 | ||||||
|  | 	const auto guard = base::make_weak(&document->session()); | ||||||
|  | 	const auto active = document->activeMediaView(); | ||||||
|  | 	const auto got = [=](QByteArray value) { | ||||||
|  | 		if (value.isEmpty()) { | ||||||
|  | 			crl::on_main(guard, [=] { | ||||||
|  | 				GenerateGoodThumbnail(document); | ||||||
|  | 			}); | ||||||
|  | 		} else if (active) { | ||||||
|  | 			crl::async([=] { | ||||||
|  | 				const auto image = App::readImage(value, nullptr, false); | ||||||
|  | 				crl::on_main(guard, [=] { | ||||||
|  | 					document->setGoodThumbnailChecked(true); | ||||||
|  | 					if (const auto active = document->activeMediaView()) { | ||||||
|  | 						active->setGoodThumbnail(image); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			crl::on_main(guard, [=] { | ||||||
|  | 				document->setGoodThumbnailChecked(true); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	document->owner().cache().get(document->goodThumbnailCacheKey(), got); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Data
 | ||||||
|  | @ -0,0 +1,43 @@ | ||||||
|  | /*
 | ||||||
|  | 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 "base/flags.h" | ||||||
|  | 
 | ||||||
|  | namespace Data { | ||||||
|  | 
 | ||||||
|  | class DocumentMedia final { | ||||||
|  | public: | ||||||
|  | 	explicit DocumentMedia(not_null<DocumentData*> owner); | ||||||
|  | 	~DocumentMedia(); | ||||||
|  | 
 | ||||||
|  | 	void goodThumbnailWanted(); | ||||||
|  | 	[[nodiscard]] Image *goodThumbnail() const; | ||||||
|  | 	void setGoodThumbnail(QImage thumbnail); | ||||||
|  | 
 | ||||||
|  | 	// For DocumentData.
 | ||||||
|  | 	void validateGoodThumbnail(); | ||||||
|  | 	static void CheckGoodThumbnail(not_null<DocumentData*> document); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	enum class Flag : uchar { | ||||||
|  | 		GoodThumbnailWanted = 0x01, | ||||||
|  | 	}; | ||||||
|  | 	inline constexpr bool is_flag_type(Flag) { return true; }; | ||||||
|  | 	using Flags = base::flags<Flag>; | ||||||
|  | 
 | ||||||
|  | 	static void ReadOrGenerateThumbnail(not_null<DocumentData*> document); | ||||||
|  | 	static void GenerateGoodThumbnail(not_null<DocumentData*> document); | ||||||
|  | 
 | ||||||
|  | 	const not_null<DocumentData*> _owner; | ||||||
|  | 	std::unique_ptr<Image> _goodThumbnail; | ||||||
|  | 	Flags _flags; | ||||||
|  | 
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Data
 | ||||||
|  | @ -750,23 +750,6 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) { | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	parent()->history()->owner().documentConvert(_document, *content); | 	parent()->history()->owner().documentConvert(_document, *content); | ||||||
| 
 |  | ||||||
| 	if (const auto good = _document->goodThumbnail()) { |  | ||||||
| 		auto bytes = good->bytesForCache(); |  | ||||||
| 		if (const auto length = bytes.size()) { |  | ||||||
| 			if (length > Storage::kMaxFileInMemory) { |  | ||||||
| 				LOG(("App Error: Bad thumbnail data for saving to cache.")); |  | ||||||
| 			} else { |  | ||||||
| 				parent()->history()->owner().cache().putIfEmpty( |  | ||||||
| 					_document->goodThumbnailCacheKey(), |  | ||||||
| 					Storage::Cache::Database::TaggedValue( |  | ||||||
| 						std::move(bytes), |  | ||||||
| 						Data::kImageCacheTag)); |  | ||||||
| 				_document->refreshGoodThumbnail(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2421,6 +2421,7 @@ void Session::documentConvert( | ||||||
| 	}(); | 	}(); | ||||||
| 	const auto oldKey = original->mediaKey(); | 	const auto oldKey = original->mediaKey(); | ||||||
| 	const auto oldCacheKey = original->cacheKey(); | 	const auto oldCacheKey = original->cacheKey(); | ||||||
|  | 	const auto oldGoodKey = original->goodThumbnailCacheKey(); | ||||||
| 	const auto idChanged = (original->id != id); | 	const auto idChanged = (original->id != id); | ||||||
| 	const auto sentSticker = idChanged && (original->sticker() != nullptr); | 	const auto sentSticker = idChanged && (original->sticker() != nullptr); | ||||||
| 	if (idChanged) { | 	if (idChanged) { | ||||||
|  | @ -2444,6 +2445,7 @@ void Session::documentConvert( | ||||||
| 	documentApplyFields(original, data); | 	documentApplyFields(original, data); | ||||||
| 	if (idChanged) { | 	if (idChanged) { | ||||||
| 		cache().moveIfEmpty(oldCacheKey, original->cacheKey()); | 		cache().moveIfEmpty(oldCacheKey, original->cacheKey()); | ||||||
|  | 		cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey()); | ||||||
| 		if (savedGifs().indexOf(original) >= 0) { | 		if (savedGifs().indexOf(original) >= 0) { | ||||||
| 			Local::writeSavedGifs(); | 			Local::writeSavedGifs(); | ||||||
| 		} | 		} | ||||||
|  | @ -3180,10 +3182,10 @@ void Session::unregisterPlayingVideoFile(not_null<ViewElement*> view) { | ||||||
| 	if (i != _playingVideoFiles.end()) { | 	if (i != _playingVideoFiles.end()) { | ||||||
| 		if (!--i->second) { | 		if (!--i->second) { | ||||||
| 			_playingVideoFiles.erase(i); | 			_playingVideoFiles.erase(i); | ||||||
| 			unregisterHeavyViewPart(view); | 			view->checkHeavyPart(); | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		unregisterHeavyViewPart(view); | 		view->checkHeavyPart(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -3205,7 +3207,7 @@ void Session::checkPlayingVideoFiles() { | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		unregisterHeavyViewPart(view); | 		view->checkHeavyPart(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -237,15 +237,7 @@ bool WebPageData::applyChanges( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void WebPageData::replaceDocumentGoodThumbnail() { | void WebPageData::replaceDocumentGoodThumbnail() { | ||||||
| 	if (!document || !photo || !document->goodThumbnail()) { | 	if (document && photo) { | ||||||
| 		return; | 		document->setGoodThumbnailPhoto(photo); | ||||||
| 	} | 	} | ||||||
| 	const auto &location = photo->large()->location(); |  | ||||||
| 	if (location.valid()) { |  | ||||||
| 		document->replaceGoodThumbnail( |  | ||||||
| 			std::make_unique<Images::StorageSource>( |  | ||||||
| 				location, |  | ||||||
| 				photo->large()->bytesSize())); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| namespace { | namespace { | ||||||
| 
 | 
 | ||||||
| constexpr auto kScrollDateHideTimeout = 1000; | constexpr auto kScrollDateHideTimeout = 1000; | ||||||
| constexpr auto kUnloadHeavyPartsPages = 1; | constexpr auto kUnloadHeavyPartsPages = 2; | ||||||
| 
 | 
 | ||||||
| // Helper binary search for an item in a list that is not completely
 | // Helper binary search for an item in a list that is not completely
 | ||||||
| // above the given top of the visible area or below the given bottom of the visible area
 | // above the given top of the visible area or below the given bottom of the visible area
 | ||||||
|  |  | ||||||
|  | @ -606,6 +606,12 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange { | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Element::checkHeavyPart() { | ||||||
|  | 	if (_media) { | ||||||
|  | 		_media->checkHeavyPart(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Element::unloadHeavyPart() { | void Element::unloadHeavyPart() { | ||||||
| 	if (_media) { | 	if (_media) { | ||||||
| 		_media->unloadHeavyPart(); | 		_media->unloadHeavyPart(); | ||||||
|  |  | ||||||
|  | @ -261,7 +261,8 @@ public: | ||||||
| 	}; | 	}; | ||||||
| 	[[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const; | 	[[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const; | ||||||
| 
 | 
 | ||||||
| 	virtual void unloadHeavyPart(); | 	void checkHeavyPart(); | ||||||
|  | 	void unloadHeavyPart(); | ||||||
| 
 | 
 | ||||||
| 	// Legacy blocks structure.
 | 	// Legacy blocks structure.
 | ||||||
| 	HistoryBlock *block(); | 	HistoryBlock *block(); | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "data/data_streaming.h" | #include "data/data_streaming.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "app.h" | #include "app.h" | ||||||
| #include "styles/style_history.h" | #include "styles/style_history.h" | ||||||
| 
 | 
 | ||||||
|  | @ -92,6 +93,8 @@ Gif::~Gif() { | ||||||
| 		_data->owner().streaming().keepAlive(_data); | 		_data->owner().streaming().keepAlive(_data); | ||||||
| 		setStreamed(nullptr); | 		setStreamed(nullptr); | ||||||
| 	} | 	} | ||||||
|  | 	_dataMedia = nullptr; | ||||||
|  | 	checkHeavyPart(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QSize Gif::sizeForAspectRatio() const { | QSize Gif::sizeForAspectRatio() const { | ||||||
|  | @ -404,7 +407,8 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		const auto good = _data->goodThumbnail(); | 		ensureDataMediaCreated(); | ||||||
|  | 		const auto good = _dataMedia->goodThumbnail(); | ||||||
| 		if (good && good->loaded()) { | 		if (good && good->loaded()) { | ||||||
| 			p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); | 			p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); | ||||||
| 		} else { | 		} else { | ||||||
|  | @ -1071,6 +1075,15 @@ TextState Gif::getStateGrouped( | ||||||
| 		: _savel); | 		: _savel); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Gif::ensureDataMediaCreated() const { | ||||||
|  | 	if (_dataMedia) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	_dataMedia = _data->createMediaView(); | ||||||
|  | 	_dataMedia->goodThumbnailWanted(); | ||||||
|  | 	history()->owner().registerHeavyViewPart(_parent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool Gif::uploading() const { | bool Gif::uploading() const { | ||||||
| 	return _data->uploading(); | 	return _data->uploading(); | ||||||
| } | } | ||||||
|  | @ -1116,7 +1129,10 @@ void Gif::validateGroupedCache( | ||||||
| 		not_null<uint64*> cacheKey, | 		not_null<uint64*> cacheKey, | ||||||
| 		not_null<QPixmap*> cache) const { | 		not_null<QPixmap*> cache) const { | ||||||
| 	using Option = Images::Option; | 	using Option = Images::Option; | ||||||
| 	const auto good = _data->goodThumbnail(); | 
 | ||||||
|  | 	ensureDataMediaCreated(); | ||||||
|  | 
 | ||||||
|  | 	const auto good = _dataMedia->goodThumbnail(); | ||||||
| 	const auto useGood = (good && good->loaded()); | 	const auto useGood = (good && good->loaded()); | ||||||
| 	const auto thumb = _data->thumbnail(); | 	const auto thumb = _data->thumbnail(); | ||||||
| 	const auto useThumb = (thumb && thumb->loaded()); | 	const auto useThumb = (thumb && thumb->loaded()); | ||||||
|  | @ -1245,6 +1261,17 @@ void Gif::parentTextUpdated() { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Gif::checkHeavyPart() { | ||||||
|  | 	if (!_dataMedia && !_streamed) { | ||||||
|  | 		history()->owner().unregisterHeavyViewPart(_parent); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Gif::unloadHeavyPart() { | ||||||
|  | 	stopAnimation(); | ||||||
|  | 	_dataMedia = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Gif::refreshParentId(not_null<HistoryItem*> realParent) { | void Gif::refreshParentId(not_null<HistoryItem*> realParent) { | ||||||
| 	File::refreshParentId(realParent); | 	File::refreshParentId(realParent); | ||||||
| 	if (_parent->media() == this) { | 	if (_parent->media() == this) { | ||||||
|  |  | ||||||
|  | @ -15,6 +15,10 @@ struct HistoryMessageReply; | ||||||
| struct HistoryMessageForwarded; | struct HistoryMessageForwarded; | ||||||
| class Painter; | class Painter; | ||||||
| 
 | 
 | ||||||
|  | namespace Data { | ||||||
|  | class DocumentMedia; | ||||||
|  | } // namespace Data
 | ||||||
|  | 
 | ||||||
| namespace Media { | namespace Media { | ||||||
| namespace View { | namespace View { | ||||||
| class PlaybackProgress; | class PlaybackProgress; | ||||||
|  | @ -100,9 +104,8 @@ public: | ||||||
| 
 | 
 | ||||||
| 	void parentTextUpdated() override; | 	void parentTextUpdated() override; | ||||||
| 
 | 
 | ||||||
| 	void unloadHeavyPart() override { | 	void checkHeavyPart() override; | ||||||
| 		stopAnimation(); | 	void unloadHeavyPart() override; | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	void refreshParentId(not_null<HistoryItem*> realParent) override; | 	void refreshParentId(not_null<HistoryItem*> realParent) override; | ||||||
| 
 | 
 | ||||||
|  | @ -113,6 +116,7 @@ private: | ||||||
| 	bool dataFinished() const override; | 	bool dataFinished() const override; | ||||||
| 	bool dataLoaded() const override; | 	bool dataLoaded() const override; | ||||||
| 
 | 
 | ||||||
|  | 	void ensureDataMediaCreated() const; | ||||||
| 	void refreshCaption(); | 	void refreshCaption(); | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] bool autoplayEnabled() const; | 	[[nodiscard]] bool autoplayEnabled() const; | ||||||
|  | @ -161,11 +165,12 @@ private: | ||||||
| 		StateRequest request, | 		StateRequest request, | ||||||
| 		QPoint position) const; | 		QPoint position) const; | ||||||
| 
 | 
 | ||||||
| 	not_null<DocumentData*> _data; | 	const not_null<DocumentData*> _data; | ||||||
| 	int _thumbw = 1; | 	int _thumbw = 1; | ||||||
| 	int _thumbh = 1; | 	int _thumbh = 1; | ||||||
| 	Ui::Text::String _caption; | 	Ui::Text::String _caption; | ||||||
| 	std::unique_ptr<Streamed> _streamed; | 	std::unique_ptr<Streamed> _streamed; | ||||||
|  | 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia; | ||||||
| 
 | 
 | ||||||
| 	QString _downloadSize; | 	QString _downloadSize; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -253,6 +253,8 @@ public: | ||||||
| 		crl::time ms) const { | 		crl::time ms) const { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	virtual void checkHeavyPart() { | ||||||
|  | 	} | ||||||
| 	virtual void unloadHeavyPart() { | 	virtual void unloadHeavyPart() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -422,6 +422,12 @@ int GroupedMedia::checkAnimationCount() { | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GroupedMedia::checkHeavyPart() { | ||||||
|  | 	for (auto &part : _parts) { | ||||||
|  | 		part.content->checkHeavyPart(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void GroupedMedia::unloadHeavyPart() { | void GroupedMedia::unloadHeavyPart() { | ||||||
| 	for (auto &part : _parts) { | 	for (auto &part : _parts) { | ||||||
| 		part.content->unloadHeavyPart(); | 		part.content->unloadHeavyPart(); | ||||||
|  |  | ||||||
|  | @ -88,6 +88,7 @@ public: | ||||||
| 
 | 
 | ||||||
| 	void stopAnimation() override; | 	void stopAnimation() override; | ||||||
| 	int checkAnimationCount() override; | 	int checkAnimationCount() override; | ||||||
|  | 	void checkHeavyPart() override; | ||||||
| 	void unloadHeavyPart() override; | 	void unloadHeavyPart() override; | ||||||
| 
 | 
 | ||||||
| 	void parentTextUpdated() override; | 	void parentTextUpdated() override; | ||||||
|  |  | ||||||
|  | @ -34,6 +34,8 @@ public: | ||||||
| 		} | 		} | ||||||
| 		virtual void clearStickerLoopPlayed() { | 		virtual void clearStickerLoopPlayed() { | ||||||
| 		} | 		} | ||||||
|  | 		virtual void checkHeavyPart() { | ||||||
|  | 		} | ||||||
| 		virtual void unloadHeavyPart() { | 		virtual void unloadHeavyPart() { | ||||||
| 		} | 		} | ||||||
| 		virtual void refreshLink() { | 		virtual void refreshLink() { | ||||||
|  | @ -82,6 +84,9 @@ public: | ||||||
| 		_content->clearStickerLoopPlayed(); | 		_content->clearStickerLoopPlayed(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	void checkHeavyPart() override { | ||||||
|  | 		_content->checkHeavyPart(); | ||||||
|  | 	} | ||||||
| 	void unloadHeavyPart() override { | 	void unloadHeavyPart() override { | ||||||
| 		_content->unloadHeavyPart(); | 		_content->unloadHeavyPart(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "window/window_session_controller.h" // isGifPausedAtLeastFor.
 | #include "window/window_session_controller.h" // isGifPausedAtLeastFor.
 | ||||||
| #include "data/data_session.h" | #include "data/data_session.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
| #include "lottie/lottie_single_player.h" | #include "lottie/lottie_single_player.h" | ||||||
| #include "styles/style_history.h" | #include "styles/style_history.h" | ||||||
|  | @ -55,12 +56,12 @@ namespace { | ||||||
| 
 | 
 | ||||||
| Sticker::Sticker( | Sticker::Sticker( | ||||||
| 	not_null<Element*> parent, | 	not_null<Element*> parent, | ||||||
| 	not_null<DocumentData*> document, | 	not_null<DocumentData*> data, | ||||||
| 	const Lottie::ColorReplacements *replacements) | 	const Lottie::ColorReplacements *replacements) | ||||||
| : _parent(parent) | : _parent(parent) | ||||||
| , _document(document) | , _data(data) | ||||||
| , _replacements(replacements) { | , _replacements(replacements) { | ||||||
| 	_document->loadThumbnail(parent->data()->fullId()); | 	_data->loadThumbnail(parent->data()->fullId()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Sticker::~Sticker() { | Sticker::~Sticker() { | ||||||
|  | @ -72,9 +73,9 @@ bool Sticker::isEmojiSticker() const { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Sticker::initSize() { | void Sticker::initSize() { | ||||||
| 	_size = _document->dimensions; | 	_size = _data->dimensions; | ||||||
| 	if (isEmojiSticker() || _diceIndex >= 0) { | 	if (isEmojiSticker() || _diceIndex >= 0) { | ||||||
| 		_size = GetAnimatedEmojiSize(&_document->session(), _size); | 		_size = GetAnimatedEmojiSize(&_data->session(), _size); | ||||||
| 		[[maybe_unused]] bool result = readyToDrawLottie(); | 		[[maybe_unused]] bool result = readyToDrawLottie(); | ||||||
| 	} else { | 	} else { | ||||||
| 		_size = DownscaledSize( | 		_size = DownscaledSize( | ||||||
|  | @ -92,13 +93,13 @@ bool Sticker::readyToDrawLottie() { | ||||||
| 	if (!_lastDiceFrame.isNull()) { | 	if (!_lastDiceFrame.isNull()) { | ||||||
| 		return true; | 		return true; | ||||||
| 	} | 	} | ||||||
| 	const auto sticker = _document->sticker(); | 	const auto sticker = _data->sticker(); | ||||||
| 	if (!sticker) { | 	if (!sticker) { | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_document->checkStickerLarge(); | 	_data->checkStickerLarge(); | ||||||
| 	const auto loaded = _document->loaded(); | 	const auto loaded = _data->loaded(); | ||||||
| 	if (sticker->animated && !_lottie && loaded) { | 	if (sticker->animated && !_lottie && loaded) { | ||||||
| 		setupLottie(); | 		setupLottie(); | ||||||
| 	} | 	} | ||||||
|  | @ -123,8 +124,8 @@ QSize Sticker::GetAnimatedEmojiSize( | ||||||
| void Sticker::draw(Painter &p, const QRect &r, bool selected) { | void Sticker::draw(Painter &p, const QRect &r, bool selected) { | ||||||
| 	if (readyToDrawLottie()) { | 	if (readyToDrawLottie()) { | ||||||
| 		paintLottie(p, r, selected); | 		paintLottie(p, r, selected); | ||||||
| 	} else if (_document->sticker() | 	} else if (_data->sticker() | ||||||
| 		&& (!_document->sticker()->animated || !_replacements)) { | 		&& (!_data->sticker()->animated || !_replacements)) { | ||||||
| 		paintPixmap(p, r, selected); | 		paintPixmap(p, r, selected); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -166,7 +167,7 @@ void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) { | ||||||
| 		: (_diceIndex == 0) | 		: (_diceIndex == 0) | ||||||
| 		? false | 		? false | ||||||
| 		: (isEmojiSticker() | 		: (isEmojiSticker() | ||||||
| 			|| !_document->session().settings().loopAnimatedStickers()); | 			|| !_data->session().settings().loopAnimatedStickers()); | ||||||
| 	const auto count = _lottie->information().framesCount; | 	const auto count = _lottie->information().framesCount; | ||||||
| 	_atTheEnd = (frame.index + 1 == count); | 	_atTheEnd = (frame.index + 1 == count); | ||||||
| 	_nextLastDiceFrame = !paused | 	_nextLastDiceFrame = !paused | ||||||
|  | @ -197,22 +198,24 @@ void Sticker::paintPixmap(Painter &p, const QRect &r, bool selected) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QPixmap Sticker::paintedPixmap(bool selected) const { | QPixmap Sticker::paintedPixmap(bool selected) const { | ||||||
|  | 	ensureDataMediaCreated(); | ||||||
|  | 
 | ||||||
| 	const auto o = _parent->data()->fullId(); | 	const auto o = _parent->data()->fullId(); | ||||||
| 	const auto w = _size.width(); | 	const auto w = _size.width(); | ||||||
| 	const auto h = _size.height(); | 	const auto h = _size.height(); | ||||||
| 	const auto &c = st::msgStickerOverlay; | 	const auto &c = st::msgStickerOverlay; | ||||||
| 	const auto good = _document->goodThumbnail(); | 	const auto good = _dataMedia->goodThumbnail(); | ||||||
| 	if (good && !good->loaded()) { | 	if (good && !good->loaded()) { | ||||||
| 		good->load({}); | 		good->load({}); | ||||||
| 	} | 	} | ||||||
| 	if (const auto image = _document->getStickerLarge()) { | 	if (const auto image = _data->getStickerLarge()) { | ||||||
| 		return selected | 		return selected | ||||||
| 			? image->pixColored(o, c, w, h) | 			? image->pixColored(o, c, w, h) | ||||||
| 			: image->pix(o, w, h); | 			: image->pix(o, w, h); | ||||||
| 	//
 | 	//
 | ||||||
| 	// Inline thumbnails can't have alpha channel.
 | 	// Inline thumbnails can't have alpha channel.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	//} else if (const auto blurred = _document->thumbnailInline()) {
 | 	//} else if (const auto blurred = _data->thumbnailInline()) {
 | ||||||
| 	//	return selected
 | 	//	return selected
 | ||||||
| 	//		? blurred->pixBlurredColored(o, c, w, h)
 | 	//		? blurred->pixBlurredColored(o, c, w, h)
 | ||||||
| 	//		: blurred->pixBlurred(o, w, h);
 | 	//		: blurred->pixBlurred(o, w, h);
 | ||||||
|  | @ -220,7 +223,7 @@ QPixmap Sticker::paintedPixmap(bool selected) const { | ||||||
| 		return selected | 		return selected | ||||||
| 			? good->pixColored(o, c, w, h) | 			? good->pixColored(o, c, w, h) | ||||||
| 			: good->pix(o, w, h); | 			: good->pix(o, w, h); | ||||||
| 	} else if (const auto thumbnail = _document->thumbnail()) { | 	} else if (const auto thumbnail = _data->thumbnail()) { | ||||||
| 		return selected | 		return selected | ||||||
| 			? thumbnail->pixBlurredColored(o, c, w, h) | 			? thumbnail->pixBlurredColored(o, c, w, h) | ||||||
| 			: thumbnail->pixBlurred(o, w, h); | 			: thumbnail->pixBlurred(o, w, h); | ||||||
|  | @ -232,7 +235,7 @@ void Sticker::refreshLink() { | ||||||
| 	if (_link) { | 	if (_link) { | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	const auto sticker = _document->sticker(); | 	const auto sticker = _data->sticker(); | ||||||
| 	if (isEmojiSticker()) { | 	if (isEmojiSticker()) { | ||||||
| 		const auto weak = base::make_weak(this); | 		const auto weak = base::make_weak(this); | ||||||
| 		_link = std::make_shared<LambdaClickHandler>([weak] { | 		_link = std::make_shared<LambdaClickHandler>([weak] { | ||||||
|  | @ -245,12 +248,20 @@ void Sticker::refreshLink() { | ||||||
| 				that->_parent); | 				that->_parent); | ||||||
| 		}); | 		}); | ||||||
| 	} else if (sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) { | 	} else if (sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) { | ||||||
| 		_link = std::make_shared<LambdaClickHandler>([document = _document] { | 		_link = std::make_shared<LambdaClickHandler>([document = _data] { | ||||||
| 			StickerSetBox::Show(App::wnd()->sessionController(), document); | 			StickerSetBox::Show(App::wnd()->sessionController(), document); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Sticker::ensureDataMediaCreated() const { | ||||||
|  | 	if (_dataMedia) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	_dataMedia = _data->createMediaView(); | ||||||
|  | 	_dataMedia->goodThumbnailWanted(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Sticker::setDiceIndex(const QString &emoji, int index) { | void Sticker::setDiceIndex(const QString &emoji, int index) { | ||||||
| 	_diceEmoji = emoji; | 	_diceEmoji = emoji; | ||||||
| 	_diceIndex = index; | 	_diceIndex = index; | ||||||
|  | @ -258,7 +269,7 @@ void Sticker::setDiceIndex(const QString &emoji, int index) { | ||||||
| 
 | 
 | ||||||
| void Sticker::setupLottie() { | void Sticker::setupLottie() { | ||||||
| 	_lottie = Stickers::LottiePlayerFromDocument( | 	_lottie = Stickers::LottiePlayerFromDocument( | ||||||
| 		_document, | 		_data, | ||||||
| 		_replacements, | 		_replacements, | ||||||
| 		Stickers::LottieSize::MessageHistory, | 		Stickers::LottieSize::MessageHistory, | ||||||
| 		_size * cIntRetinaFactor(), | 		_size * cIntRetinaFactor(), | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ class Session; | ||||||
| 
 | 
 | ||||||
| namespace Data { | namespace Data { | ||||||
| struct FileOrigin; | struct FileOrigin; | ||||||
|  | class DocumentMedia; | ||||||
| } // namespace Data
 | } // namespace Data
 | ||||||
| 
 | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
|  | @ -31,7 +32,7 @@ class Sticker final | ||||||
| public: | public: | ||||||
| 	Sticker( | 	Sticker( | ||||||
| 		not_null<Element*> parent, | 		not_null<Element*> parent, | ||||||
| 		not_null<DocumentData*> document, | 		not_null<DocumentData*> data, | ||||||
| 		const Lottie::ColorReplacements *replacements = nullptr); | 		const Lottie::ColorReplacements *replacements = nullptr); | ||||||
| 	~Sticker(); | 	~Sticker(); | ||||||
| 
 | 
 | ||||||
|  | @ -43,7 +44,7 @@ public: | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	DocumentData *document() override { | 	DocumentData *document() override { | ||||||
| 		return _document; | 		return _data; | ||||||
| 	} | 	} | ||||||
| 	void clearStickerLoopPlayed() override { | 	void clearStickerLoopPlayed() override { | ||||||
| 		_lottieOncePlayed = false; | 		_lottieOncePlayed = false; | ||||||
|  | @ -71,13 +72,16 @@ private: | ||||||
| 	void paintPixmap(Painter &p, const QRect &r, bool selected); | 	void paintPixmap(Painter &p, const QRect &r, bool selected); | ||||||
| 	[[nodiscard]] QPixmap paintedPixmap(bool selected) const; | 	[[nodiscard]] QPixmap paintedPixmap(bool selected) const; | ||||||
| 
 | 
 | ||||||
|  | 	void ensureDataMediaCreated() const; | ||||||
|  | 
 | ||||||
| 	void setupLottie(); | 	void setupLottie(); | ||||||
| 	void unloadLottie(); | 	void unloadLottie(); | ||||||
| 
 | 
 | ||||||
| 	const not_null<Element*> _parent; | 	const not_null<Element*> _parent; | ||||||
| 	const not_null<DocumentData*> _document; | 	const not_null<DocumentData*> _data; | ||||||
| 	const Lottie::ColorReplacements *_replacements = nullptr; | 	const Lottie::ColorReplacements *_replacements = nullptr; | ||||||
| 	std::unique_ptr<Lottie::SinglePlayer> _lottie; | 	std::unique_ptr<Lottie::SinglePlayer> _lottie; | ||||||
|  | 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia; | ||||||
| 	ClickHandlerPtr _link; | 	ClickHandlerPtr _link; | ||||||
| 	QSize _size; | 	QSize _size; | ||||||
| 	QImage _lastDiceFrame; | 	QImage _lastDiceFrame; | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "history/view/history_view_element.h" | #include "history/view/history_view_element.h" | ||||||
| #include "history/view/history_view_cursor_state.h" | #include "history/view/history_view_cursor_state.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
| #include "base/qthelp_url.h" | #include "base/qthelp_url.h" | ||||||
| #include "window/themes/window_theme.h" | #include "window/themes/window_theme.h" | ||||||
|  | @ -181,8 +182,11 @@ void ThemeDocument::validateThumbnail() const { | ||||||
| 	if (_thumbnailGood > 0) { | 	if (_thumbnailGood > 0) { | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	const auto good = _data->goodThumbnail(); | 	if (!_dataMedia) { | ||||||
| 	if (good) { | 		_dataMedia = _data->createMediaView(); | ||||||
|  | 		_dataMedia->goodThumbnailWanted(); | ||||||
|  | 	} | ||||||
|  | 	if (const auto good = _dataMedia->goodThumbnail()) { | ||||||
| 		if (good->loaded()) { | 		if (good->loaded()) { | ||||||
| 			prepareThumbnailFrom(good, 1); | 			prepareThumbnailFrom(good, 1); | ||||||
| 			return; | 			return; | ||||||
|  |  | ||||||
|  | @ -9,9 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| 
 | 
 | ||||||
| #include "history/view/media/history_view_file.h" | #include "history/view/media/history_view_file.h" | ||||||
| 
 | 
 | ||||||
|  | namespace Data { | ||||||
|  | class DocumentMedia; | ||||||
|  | } // namespace Data
 | ||||||
|  | 
 | ||||||
| namespace HistoryView { | namespace HistoryView { | ||||||
| 
 | 
 | ||||||
| class ThemeDocument : public File { | class ThemeDocument final : public File { | ||||||
| public: | public: | ||||||
| 	ThemeDocument( | 	ThemeDocument( | ||||||
| 		not_null<Element*> parent, | 		not_null<Element*> parent, | ||||||
|  | @ -54,11 +58,12 @@ private: | ||||||
| 	void validateThumbnail() const; | 	void validateThumbnail() const; | ||||||
| 	void prepareThumbnailFrom(not_null<Image*> image, int good) const; | 	void prepareThumbnailFrom(not_null<Image*> image, int good) const; | ||||||
| 
 | 
 | ||||||
| 	not_null<DocumentData*> _data; | 	const not_null<DocumentData*> _data; | ||||||
| 	int _pixw = 1; | 	int _pixw = 1; | ||||||
| 	int _pixh = 1; | 	int _pixh = 1; | ||||||
| 	mutable QPixmap _thumbnail; | 	mutable QPixmap _thumbnail; | ||||||
| 	mutable int _thumbnailGood = -1; // -1 inline, 0 thumbnail, 1 good
 | 	mutable int _thumbnailGood = -1; // -1 inline, 0 thumbnail, 1 good
 | ||||||
|  | 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia; | ||||||
| 
 | 
 | ||||||
| 	// For wallpaper documents.
 | 	// For wallpaper documents.
 | ||||||
| 	QColor _background; | 	QColor _background; | ||||||
|  |  | ||||||
|  | @ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "media/streaming/media_streaming_instance.h" | #include "media/streaming/media_streaming_instance.h" | ||||||
| #include "data/data_session.h" | #include "data/data_session.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
|  | #include "main/main_session.h" | ||||||
| #include "storage/file_download.h" // Storage::kMaxFileInMemory.
 | #include "storage/file_download.h" // Storage::kMaxFileInMemory.
 | ||||||
| #include "styles/style_widgets.h" | #include "styles/style_widgets.h" | ||||||
| 
 | 
 | ||||||
|  | @ -192,48 +194,53 @@ void Document::waitingChange(bool waiting) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Document::validateGoodThumbnail() { | void Document::validateGoodThumbnail() { | ||||||
| 	const auto good = _document->goodThumbnail(); | 	if (_info.video.cover.isNull() || _document->goodThumbnailChecked()) { | ||||||
| 	if (_info.video.cover.isNull() |  | ||||||
| 		|| (good && good->loaded()) |  | ||||||
| 		|| _document->uploading()) { |  | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	auto image = [&] { | 	const auto document = _document; | ||||||
| 		auto result = _info.video.cover; | 	const auto information = _info.video; | ||||||
| 		if (_info.video.rotation != 0) { | 	const auto key = document->goodThumbnailCacheKey(); | ||||||
| 			auto transform = QTransform(); | 	const auto guard = base::make_weak(&document->session()); | ||||||
| 			transform.rotate(_info.video.rotation); | 	document->owner().cache().get(key, [=](QByteArray value) { | ||||||
| 			result = result.transformed(transform); | 		if (!value.isEmpty()) { | ||||||
|  | 			return; | ||||||
| 		} | 		} | ||||||
| 		if (result.size() != _info.video.size) { | 		const auto image = [&] { | ||||||
| 			result = result.scaled( | 			auto result = information.cover; | ||||||
| 				_info.video.size, | 			if (information.rotation != 0) { | ||||||
| 				Qt::IgnoreAspectRatio, | 				auto transform = QTransform(); | ||||||
| 				Qt::SmoothTransformation); | 				transform.rotate(information.rotation); | ||||||
|  | 				result = result.transformed(transform); | ||||||
|  | 			} | ||||||
|  | 			if (result.size() != information.size) { | ||||||
|  | 				result = result.scaled( | ||||||
|  | 					information.size, | ||||||
|  | 					Qt::IgnoreAspectRatio, | ||||||
|  | 					Qt::SmoothTransformation); | ||||||
|  | 			} | ||||||
|  | 			return result; | ||||||
|  | 		}(); | ||||||
|  | 		auto bytes = QByteArray(); | ||||||
|  | 		{ | ||||||
|  | 			auto buffer = QBuffer(&bytes); | ||||||
|  | 			image.save(&buffer, "JPG", kGoodThumbnailQuality); | ||||||
| 		} | 		} | ||||||
| 		return result; | 		const auto length = bytes.size(); | ||||||
| 	}(); | 		if (!length || length > Storage::kMaxFileInMemory) { | ||||||
| 
 | 			LOG(("App Error: Bad thumbnail data for saving to cache.")); | ||||||
| 	auto bytes = QByteArray(); | 			bytes = "(failed)"; | ||||||
| 	{ | 		} | ||||||
| 		auto buffer = QBuffer(&bytes); | 		crl::on_main(guard, [=] { | ||||||
| 		image.save(&buffer, "JPG", kGoodThumbnailQuality); | 			if (const auto active = document->activeMediaView()) { | ||||||
| 	} | 				active->setGoodThumbnail(image); | ||||||
| 	const auto length = bytes.size(); | 			} | ||||||
| 	if (!length || length > Storage::kMaxFileInMemory) { | 			document->owner().cache().putIfEmpty( | ||||||
| 		LOG(("App Error: Bad thumbnail data for saving to cache.")); | 				document->goodThumbnailCacheKey(), | ||||||
| 	} else if (_document->uploading()) { | 				Storage::Cache::Database::TaggedValue( | ||||||
| 		_document->setGoodThumbnailOnUpload( | 					base::duplicate(bytes), | ||||||
| 			std::move(image), | 					Data::kImageCacheTag)); | ||||||
| 			std::move(bytes)); | 		}); | ||||||
| 	} else { | 	}); | ||||||
| 		_document->owner().cache().putIfEmpty( |  | ||||||
| 			_document->goodThumbnailCacheKey(), |  | ||||||
| 			Storage::Cache::Database::TaggedValue( |  | ||||||
| 				std::move(bytes), |  | ||||||
| 				Data::kImageCacheTag)); |  | ||||||
| 		_document->refreshGoodThumbnail(); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Document::waitingCallback() { | void Document::waitingCallback() { | ||||||
|  |  | ||||||
|  | @ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "data/data_user.h" | #include "data/data_user.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
| #include "data/data_media_rotation.h" | #include "data/data_media_rotation.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "window/themes/window_theme_preview.h" | #include "window/themes/window_theme_preview.h" | ||||||
| #include "window/window_peer_menu.h" | #include "window/window_peer_menu.h" | ||||||
| #include "window/window_session_controller.h" | #include "window/window_session_controller.h" | ||||||
|  | @ -2177,7 +2178,8 @@ void OverlayWidget::startStreamingPlayer() { | ||||||
| void OverlayWidget::initStreamingThumbnail() { | void OverlayWidget::initStreamingThumbnail() { | ||||||
| 	Expects(_doc != nullptr); | 	Expects(_doc != nullptr); | ||||||
| 
 | 
 | ||||||
| 	const auto good = _doc->goodThumbnail(); | 	const auto media = _doc->activeMediaView(); | ||||||
|  | 	const auto good = media ? media->goodThumbnail() : nullptr; | ||||||
| 	const auto useGood = (good && good->loaded()); | 	const auto useGood = (good && good->loaded()); | ||||||
| 	const auto thumb = _doc->thumbnail(); | 	const auto thumb = _doc->thumbnail(); | ||||||
| 	const auto useThumb = (thumb && thumb->loaded()); | 	const auto useThumb = (thumb && thumb->loaded()); | ||||||
|  |  | ||||||
|  | @ -348,6 +348,7 @@ private: | ||||||
| 
 | 
 | ||||||
| 	PhotoData *_photo = nullptr; | 	PhotoData *_photo = nullptr; | ||||||
| 	DocumentData *_doc = nullptr; | 	DocumentData *_doc = nullptr; | ||||||
|  | 	std::shared_ptr<Data::DocumentMedia> _docMedia; | ||||||
| 	int _rotation = 0; | 	int _rotation = 0; | ||||||
| 	std::unique_ptr<SharedMedia> _sharedMedia; | 	std::unique_ptr<SharedMedia> _sharedMedia; | ||||||
| 	std::optional<SharedMediaWithLastSlice> _sharedMediaData; | 	std::optional<SharedMediaWithLastSlice> _sharedMediaData; | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "media/view/media_view_playback_progress.h" | #include "media/view/media_view_playback_progress.h" | ||||||
| #include "media/audio/media_audio.h" | #include "media/audio/media_audio.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
| #include "data/data_session.h" | #include "data/data_session.h" | ||||||
| #include "data/data_media_rotation.h" | #include "data/data_media_rotation.h" | ||||||
|  | @ -806,20 +807,20 @@ void PipPanel::updateDecorations() { | ||||||
| 
 | 
 | ||||||
| Pip::Pip( | Pip::Pip( | ||||||
| 	not_null<Delegate*> delegate, | 	not_null<Delegate*> delegate, | ||||||
| 	not_null<DocumentData*> document, | 	not_null<DocumentData*> data, | ||||||
| 	FullMsgId contextId, | 	FullMsgId contextId, | ||||||
| 	std::shared_ptr<Streaming::Document> shared, | 	std::shared_ptr<Streaming::Document> shared, | ||||||
| 	FnMut<void()> closeAndContinue, | 	FnMut<void()> closeAndContinue, | ||||||
| 	FnMut<void()> destroy) | 	FnMut<void()> destroy) | ||||||
| : _delegate(delegate) | : _delegate(delegate) | ||||||
| , _document(document) | , _data(data) | ||||||
| , _contextId(contextId) | , _contextId(contextId) | ||||||
| , _instance(std::move(shared), [=] { waitingAnimationCallback(); }) | , _instance(std::move(shared), [=] { waitingAnimationCallback(); }) | ||||||
| , _panel( | , _panel( | ||||||
| 	_delegate->pipParentWidget(), | 	_delegate->pipParentWidget(), | ||||||
| 	[=](QPainter &p, const FrameRequest &request) { paint(p, request); }) | 	[=](QPainter &p, const FrameRequest &request) { paint(p, request); }) | ||||||
| , _playbackProgress(std::make_unique<PlaybackProgress>()) | , _playbackProgress(std::make_unique<PlaybackProgress>()) | ||||||
| , _rotation(document->owner().mediaRotation().get(document)) | , _rotation(data->owner().mediaRotation().get(data)) | ||||||
| , _roundRect(ImageRoundRadius::Large, st::radialBg) | , _roundRect(ImageRoundRadius::Large, st::radialBg) | ||||||
| , _closeAndContinue(std::move(closeAndContinue)) | , _closeAndContinue(std::move(closeAndContinue)) | ||||||
| , _destroy(std::move(destroy)) { | , _destroy(std::move(destroy)) { | ||||||
|  | @ -835,9 +836,10 @@ void Pip::setupPanel() { | ||||||
| 		if (!_instance.info().video.size.isEmpty()) { | 		if (!_instance.info().video.size.isEmpty()) { | ||||||
| 			return _instance.info().video.size; | 			return _instance.info().video.size; | ||||||
| 		} | 		} | ||||||
| 		const auto good = _document->goodThumbnail(); | 		const auto media = _data->activeMediaView(); | ||||||
|  | 		const auto good = media ? media->goodThumbnail() : nullptr; | ||||||
| 		const auto useGood = (good && good->loaded()); | 		const auto useGood = (good && good->loaded()); | ||||||
| 		const auto original = useGood ? good->size() : _document->dimensions; | 		const auto original = useGood ? good->size() : _data->dimensions; | ||||||
| 		return original.isEmpty() ? QSize(1, 1) : original; | 		return original.isEmpty() ? QSize(1, 1) : original; | ||||||
| 	}(); | 	}(); | ||||||
| 	_panel.setAspectRatio(FlipSizeByRotation(size, _rotation)); | 	_panel.setAspectRatio(FlipSizeByRotation(size, _rotation)); | ||||||
|  | @ -1369,11 +1371,12 @@ QImage Pip::videoFrame(const FrameRequest &request) const { | ||||||
| 		return _instance.frame(request); | 		return _instance.frame(request); | ||||||
| 	} | 	} | ||||||
| 	const auto &cover = _instance.info().video.cover; | 	const auto &cover = _instance.info().video.cover; | ||||||
| 	const auto good = _document->goodThumbnail(); | 	const auto media = _data->activeMediaView(); | ||||||
|  | 	const auto good = media ? media->goodThumbnail() : nullptr; | ||||||
| 	const auto useGood = (good && good->loaded()); | 	const auto useGood = (good && good->loaded()); | ||||||
| 	const auto thumb = _document->thumbnail(); | 	const auto thumb = _data->thumbnail(); | ||||||
| 	const auto useThumb = (thumb && thumb->loaded()); | 	const auto useThumb = (thumb && thumb->loaded()); | ||||||
| 	const auto blurred = _document->thumbnailInline(); | 	const auto blurred = _data->thumbnailInline(); | ||||||
| 	const auto state = !cover.isNull() | 	const auto state = !cover.isNull() | ||||||
| 		? ThumbState::Cover | 		? ThumbState::Cover | ||||||
| 		: useGood | 		: useGood | ||||||
|  |  | ||||||
|  | @ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| 
 | 
 | ||||||
| #include <QtCore/QPointer> | #include <QtCore/QPointer> | ||||||
| 
 | 
 | ||||||
|  | namespace Data { | ||||||
|  | class DocumentMedia; | ||||||
|  | } // namespace Data
 | ||||||
|  | 
 | ||||||
| namespace Ui { | namespace Ui { | ||||||
| class IconButton; | class IconButton; | ||||||
| template <typename Widget> | template <typename Widget> | ||||||
|  | @ -121,7 +125,7 @@ public: | ||||||
| 
 | 
 | ||||||
| 	Pip( | 	Pip( | ||||||
| 		not_null<Delegate*> delegate, | 		not_null<Delegate*> delegate, | ||||||
| 		not_null<DocumentData*> document, | 		not_null<DocumentData*> data, | ||||||
| 		FullMsgId contextId, | 		FullMsgId contextId, | ||||||
| 		std::shared_ptr<Streaming::Document> shared, | 		std::shared_ptr<Streaming::Document> shared, | ||||||
| 		FnMut<void()> closeAndContinue, | 		FnMut<void()> closeAndContinue, | ||||||
|  | @ -197,12 +201,13 @@ private: | ||||||
| 	void seekFinish(float64 value); | 	void seekFinish(float64 value); | ||||||
| 
 | 
 | ||||||
| 	const not_null<Delegate*> _delegate; | 	const not_null<Delegate*> _delegate; | ||||||
| 	not_null<DocumentData*> _document; | 	not_null<DocumentData*> _data; | ||||||
| 	FullMsgId _contextId; | 	FullMsgId _contextId; | ||||||
| 	Streaming::Instance _instance; | 	Streaming::Instance _instance; | ||||||
| 	PipPanel _panel; | 	PipPanel _panel; | ||||||
| 	QSize _size; | 	QSize _size; | ||||||
| 	std::unique_ptr<PlaybackProgress> _playbackProgress; | 	std::unique_ptr<PlaybackProgress> _playbackProgress; | ||||||
|  | 	std::shared_ptr<Data::DocumentMedia> _dataMedia; | ||||||
| 
 | 
 | ||||||
| 	bool _showPause = false; | 	bool _showPause = false; | ||||||
| 	bool _startPaused = false; | 	bool _startPaused = false; | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "data/data_media_types.h" | #include "data/data_media_types.h" | ||||||
| #include "data/data_peer.h" | #include "data/data_peer.h" | ||||||
| #include "data/data_file_origin.h" | #include "data/data_file_origin.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "styles/style_overview.h" | #include "styles/style_overview.h" | ||||||
| #include "styles/style_history.h" | #include "styles/style_history.h" | ||||||
| #include "core/file_utilities.h" | #include "core/file_utilities.h" | ||||||
|  | @ -417,13 +418,10 @@ Video::Video( | ||||||
| , _duration(formatDurationText(_data->getDuration())) { | , _duration(formatDurationText(_data->getDuration())) { | ||||||
| 	setDocumentLinks(_data); | 	setDocumentLinks(_data); | ||||||
| 	_data->loadThumbnail(parent->fullId()); | 	_data->loadThumbnail(parent->fullId()); | ||||||
| 	if (_data->hasThumbnail() && !_data->thumbnail()->loaded()) { |  | ||||||
| 		if (const auto good = _data->goodThumbnail()) { |  | ||||||
| 			good->load({}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | Video::~Video() = default; | ||||||
|  | 
 | ||||||
| void Video::initDimensions() { | void Video::initDimensions() { | ||||||
| 	_maxw = 2 * st::overviewPhotoMinSize; | 	_maxw = 2 * st::overviewPhotoMinSize; | ||||||
| 	_minh = _maxw; | 	_minh = _maxw; | ||||||
|  | @ -436,12 +434,14 @@ int32 Video::resizeGetHeight(int32 width) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { | void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) { | ||||||
|  | 	ensureDataMediaCreated(); | ||||||
|  | 
 | ||||||
| 	const auto selected = (selection == FullSelection); | 	const auto selected = (selection == FullSelection); | ||||||
| 	const auto blurred = _data->thumbnailInline(); | 	const auto blurred = _data->thumbnailInline(); | ||||||
| 	const auto goodLoaded = _data->goodThumbnail() |  | ||||||
| 		&& _data->goodThumbnail()->loaded(); |  | ||||||
| 	const auto thumbLoaded = _data->hasThumbnail() | 	const auto thumbLoaded = _data->hasThumbnail() | ||||||
| 		&& _data->thumbnail()->loaded(); | 		&& _data->thumbnail()->loaded(); | ||||||
|  | 	const auto goodLoaded = _dataMedia->goodThumbnail() | ||||||
|  | 		&& _dataMedia->goodThumbnail()->loaded(); | ||||||
| 
 | 
 | ||||||
| 	bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); | 	bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); | ||||||
| 	if (displayLoading) { | 	if (displayLoading) { | ||||||
|  | @ -459,7 +459,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const | ||||||
| 			|| (_pixBlurred && (thumbLoaded || goodLoaded)))) { | 			|| (_pixBlurred && (thumbLoaded || goodLoaded)))) { | ||||||
| 		auto size = _width * cIntRetinaFactor(); | 		auto size = _width * cIntRetinaFactor(); | ||||||
| 		auto img = goodLoaded | 		auto img = goodLoaded | ||||||
| 			? _data->goodThumbnail()->original() | 			? _dataMedia->goodThumbnail()->original() | ||||||
| 			: thumbLoaded | 			: thumbLoaded | ||||||
| 			? _data->thumbnail()->original() | 			? _data->thumbnail()->original() | ||||||
| 			: Images::prepareBlur(blurred->original()); | 			: Images::prepareBlur(blurred->original()); | ||||||
|  | @ -543,6 +543,14 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const | ||||||
| 	paintCheckbox(p, { checkLeft, checkTop }, selected, context); | 	paintCheckbox(p, { checkLeft, checkTop }, selected, context); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Video::ensureDataMediaCreated() const { | ||||||
|  | 	if (_dataMedia) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	_dataMedia = _data->createMediaView(); | ||||||
|  | 	_dataMedia->goodThumbnailWanted(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| float64 Video::dataProgress() const { | float64 Video::dataProgress() const { | ||||||
| 	return _data->progress(); | 	return _data->progress(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ struct RoundCheckbox; | ||||||
| 
 | 
 | ||||||
| namespace Data { | namespace Data { | ||||||
| class Media; | class Media; | ||||||
|  | class DocumentMedia; | ||||||
| } // namespace Data
 | } // namespace Data
 | ||||||
| 
 | 
 | ||||||
| namespace Overview { | namespace Overview { | ||||||
|  | @ -220,11 +221,12 @@ private: | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Video : public RadialProgressItem { | class Video final : public RadialProgressItem { | ||||||
| public: | public: | ||||||
| 	Video( | 	Video( | ||||||
| 		not_null<HistoryItem*> parent, | 		not_null<HistoryItem*> parent, | ||||||
| 		not_null<DocumentData*> video); | 		not_null<DocumentData*> video); | ||||||
|  | 	~Video(); | ||||||
| 
 | 
 | ||||||
| 	void initDimensions() override; | 	void initDimensions() override; | ||||||
| 	int32 resizeGetHeight(int32 width) override; | 	int32 resizeGetHeight(int32 width) override; | ||||||
|  | @ -240,15 +242,17 @@ protected: | ||||||
| 	bool iconAnimated() const override; | 	bool iconAnimated() const override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | 	void ensureDataMediaCreated() const; | ||||||
|  | 	void updateStatusText(); | ||||||
|  | 
 | ||||||
| 	not_null<DocumentData*> _data; | 	not_null<DocumentData*> _data; | ||||||
|  | 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia; | ||||||
| 	StatusText _status; | 	StatusText _status; | ||||||
| 
 | 
 | ||||||
| 	QString _duration; | 	QString _duration; | ||||||
| 	QPixmap _pix; | 	QPixmap _pix; | ||||||
| 	bool _pixBlurred = true; | 	bool _pixBlurred = true; | ||||||
| 
 | 
 | ||||||
| 	void updateStatusText(); |  | ||||||
| 
 |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Voice : public RadialProgressItem { | class Voice : public RadialProgressItem { | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "storage/localimageloader.h" | #include "storage/localimageloader.h" | ||||||
| #include "storage/file_download.h" | #include "storage/file_download.h" | ||||||
| #include "data/data_document.h" | #include "data/data_document.h" | ||||||
|  | #include "data/data_document_media.h" | ||||||
| #include "data/data_photo.h" | #include "data/data_photo.h" | ||||||
| #include "data/data_session.h" | #include "data/data_session.h" | ||||||
| #include "main/main_session.h" | #include "main/main_session.h" | ||||||
|  | @ -193,9 +194,18 @@ void Uploader::upload( | ||||||
| 				std::move(file->thumb)); | 				std::move(file->thumb)); | ||||||
| 		document->uploadingData = std::make_unique<Data::UploadState>( | 		document->uploadingData = std::make_unique<Data::UploadState>( | ||||||
| 			document->size); | 			document->size); | ||||||
| 		document->setGoodThumbnailOnUpload( | 		if (!file->goodThumbnail.isNull()) { | ||||||
| 			std::move(file->goodThumbnail), | 			if (const auto active = document->activeMediaView()) { | ||||||
| 			std::move(file->goodThumbnailBytes)); | 				active->setGoodThumbnail(std::move(file->goodThumbnail)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if (!file->goodThumbnailBytes.isEmpty()) { | ||||||
|  | 			document->owner().cache().putIfEmpty( | ||||||
|  | 				document->goodThumbnailCacheKey(), | ||||||
|  | 				Storage::Cache::Database::TaggedValue( | ||||||
|  | 					std::move(file->goodThumbnailBytes), | ||||||
|  | 					Data::kImageCacheTag)); | ||||||
|  | 		} | ||||||
| 		if (!file->content.isEmpty()) { | 		if (!file->content.isEmpty()) { | ||||||
| 			document->setDataAndCache(file->content); | 			document->setDataAndCache(file->content); | ||||||
| 			if (file->type == SendMediaType::ThemeFile) { | 			if (file->type == SendMediaType::ThemeFile) { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue