552 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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_photo.h"
 | 
						|
 | 
						|
#include "data/data_session.h"
 | 
						|
#include "data/data_file_origin.h"
 | 
						|
#include "data/data_reply_preview.h"
 | 
						|
#include "data/data_photo_media.h"
 | 
						|
#include "ui/image/image.h"
 | 
						|
#include "main/main_session.h"
 | 
						|
#include "history/history.h"
 | 
						|
#include "history/history_item.h"
 | 
						|
#include "media/streaming/media_streaming_loader_local.h"
 | 
						|
#include "media/streaming/media_streaming_loader_mtproto.h"
 | 
						|
#include "mainwidget.h"
 | 
						|
#include "storage/file_download.h"
 | 
						|
#include "core/application.h"
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kPhotoSideLimit = 1280;
 | 
						|
 | 
						|
using Data::PhotoMedia;
 | 
						|
using Data::PhotoSize;
 | 
						|
using Data::PhotoSizeIndex;
 | 
						|
using Data::kPhotoSizeCount;
 | 
						|
 | 
						|
[[nodiscard]] QImage ValidatePhotoImage(
 | 
						|
		QImage image,
 | 
						|
		const Data::CloudFile &file) {
 | 
						|
	return (v::is<WebFileLocation>(file.location.file().data)
 | 
						|
		&& image.format() == QImage::Format_ARGB32)
 | 
						|
		? Images::Opaque(std::move(image))
 | 
						|
		: image;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
PhotoData::PhotoData(not_null<Data::Session*> owner, PhotoId id)
 | 
						|
: id(id)
 | 
						|
, _owner(owner) {
 | 
						|
}
 | 
						|
 | 
						|
PhotoData::~PhotoData() {
 | 
						|
	for (auto &image : _images) {
 | 
						|
		base::take(image.loader).reset();
 | 
						|
	}
 | 
						|
	base::take(_videoSizes);
 | 
						|
}
 | 
						|
 | 
						|
Data::Session &PhotoData::owner() const {
 | 
						|
	return *_owner;
 | 
						|
}
 | 
						|
 | 
						|
Main::Session &PhotoData::session() const {
 | 
						|
	return _owner->session();
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::automaticLoadSettingsChanged() {
 | 
						|
	const auto index = PhotoSizeIndex(PhotoSize::Large);
 | 
						|
	if (!(_images[index].flags & Data::CloudFile::Flag::Cancelled)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_images[index].loader = nullptr;
 | 
						|
	_images[index].flags &= ~Data::CloudFile::Flag::Cancelled;
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::load(
 | 
						|
		Data::FileOrigin origin,
 | 
						|
		LoadFromCloudSetting fromCloud,
 | 
						|
		bool autoLoading) {
 | 
						|
	load(PhotoSize::Large, origin, fromCloud, autoLoading);
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::loading() const {
 | 
						|
	return loading(PhotoSize::Large);
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::validSizeIndex(PhotoSize size) const {
 | 
						|
	const auto index = PhotoSizeIndex(size);
 | 
						|
	for (auto i = index; i != kPhotoSizeCount; ++i) {
 | 
						|
		if (_images[i].location.valid()) {
 | 
						|
			return i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return PhotoSizeIndex(PhotoSize::Large);
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::existingSizeIndex(PhotoSize size) const {
 | 
						|
	const auto index = PhotoSizeIndex(size);
 | 
						|
	for (auto i = index; i != kPhotoSizeCount; ++i) {
 | 
						|
		if (_images[i].location.valid() || _images[i].progressivePartSize) {
 | 
						|
			return i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return PhotoSizeIndex(PhotoSize::Large);
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::hasExact(PhotoSize size) const {
 | 
						|
	return _images[PhotoSizeIndex(size)].location.valid();
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::loading(PhotoSize size) const {
 | 
						|
	const auto valid = validSizeIndex(size);
 | 
						|
	const auto existing = existingSizeIndex(size);
 | 
						|
	if (!_images[valid].loader) {
 | 
						|
		return false;
 | 
						|
	} else if (valid == existing) {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return (_images[valid].loader->loadSize()
 | 
						|
		>= _images[existing].progressivePartSize);
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::failed(PhotoSize size) const {
 | 
						|
	const auto flags = _images[validSizeIndex(size)].flags;
 | 
						|
	return (flags & Data::CloudFile::Flag::Failed);
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::clearFailed(PhotoSize size) {
 | 
						|
	_images[validSizeIndex(size)].flags &= ~Data::CloudFile::Flag::Failed;
 | 
						|
}
 | 
						|
 | 
						|
const ImageLocation &PhotoData::location(PhotoSize size) const {
 | 
						|
	return _images[validSizeIndex(size)].location;
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::SideLimit() {
 | 
						|
	return kPhotoSideLimit;
 | 
						|
}
 | 
						|
 | 
						|
std::optional<QSize> PhotoData::size(PhotoSize size) const {
 | 
						|
	const auto &provided = location(size);
 | 
						|
	const auto result = QSize{ provided.width(), provided.height() };
 | 
						|
	const auto limit = SideLimit();
 | 
						|
	if (result.isEmpty()) {
 | 
						|
		return std::nullopt;
 | 
						|
	} else if (result.width() <= limit && result.height() <= limit) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	const auto scaled = result.scaled(limit, limit, Qt::KeepAspectRatio);
 | 
						|
	return QSize(std::max(scaled.width(), 1), std::max(scaled.height(), 1));
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::imageByteSize(PhotoSize size) const {
 | 
						|
	const auto existing = existingSizeIndex(size);
 | 
						|
	if (const auto result = _images[existing].progressivePartSize) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	return _images[validSizeIndex(size)].byteSize;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::displayLoading() const {
 | 
						|
	const auto index = PhotoSizeIndex(PhotoSize::Large);
 | 
						|
	if (const auto loader = _images[index].loader.get()) {
 | 
						|
		return !loader->finished()
 | 
						|
			&& (!loader->loadingLocal() || !loader->autoLoading());
 | 
						|
	}
 | 
						|
	return (uploading() && !waitingForAlbum());
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::cancel() {
 | 
						|
	if (loading()) {
 | 
						|
		_images[PhotoSizeIndex(PhotoSize::Large)].loader->cancel();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
float64 PhotoData::progress() const {
 | 
						|
	if (uploading()) {
 | 
						|
		if (uploadingData->size > 0) {
 | 
						|
			const auto result = float64(uploadingData->offset)
 | 
						|
				/ uploadingData->size;
 | 
						|
			return std::clamp(result, 0., 1.);
 | 
						|
		}
 | 
						|
		return 0.;
 | 
						|
	}
 | 
						|
	const auto index = PhotoSizeIndex(PhotoSize::Large);
 | 
						|
	return loading() ? _images[index].loader->currentProgress() : 0.;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::cancelled() const {
 | 
						|
	const auto index = PhotoSizeIndex(PhotoSize::Large);
 | 
						|
	return (_images[index].flags & Data::CloudFile::Flag::Cancelled);
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::setWaitingForAlbum() {
 | 
						|
	if (uploading()) {
 | 
						|
		uploadingData->waitingForAlbum = true;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::waitingForAlbum() const {
 | 
						|
	return uploading() && uploadingData->waitingForAlbum;
 | 
						|
}
 | 
						|
 | 
						|
int32 PhotoData::loadOffset() const {
 | 
						|
	const auto index = PhotoSizeIndex(PhotoSize::Large);
 | 
						|
	return loading() ? _images[index].loader->currentOffset() : 0;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::uploading() const {
 | 
						|
	return (uploadingData != nullptr);
 | 
						|
}
 | 
						|
 | 
						|
Image *PhotoData::getReplyPreview(
 | 
						|
		Data::FileOrigin origin,
 | 
						|
		not_null<PeerData*> context) {
 | 
						|
	if (!_replyPreview) {
 | 
						|
		_replyPreview = std::make_unique<Data::ReplyPreview>(this);
 | 
						|
	}
 | 
						|
	return _replyPreview->image(origin, context);
 | 
						|
}
 | 
						|
 | 
						|
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
 | 
						|
	return getReplyPreview(item->fullId(), item->history()->peer);
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::replyPreviewLoaded() const {
 | 
						|
	if (!_replyPreview) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return _replyPreview->loaded();
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::setRemoteLocation(
 | 
						|
		int32 dc,
 | 
						|
		uint64 access,
 | 
						|
		const QByteArray &fileReference) {
 | 
						|
	_fileReference = fileReference;
 | 
						|
	if (_dc != dc || _access != access) {
 | 
						|
		_dc = dc;
 | 
						|
		_access = access;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
MTPInputPhoto PhotoData::mtpInput() const {
 | 
						|
	return MTP_inputPhoto(
 | 
						|
		MTP_long(id),
 | 
						|
		MTP_long(_access),
 | 
						|
		MTP_bytes(_fileReference));
 | 
						|
}
 | 
						|
 | 
						|
QByteArray PhotoData::fileReference() const {
 | 
						|
	return _fileReference;
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::refreshFileReference(const QByteArray &value) {
 | 
						|
	_fileReference = value;
 | 
						|
	for (auto &image : _images) {
 | 
						|
		image.location.refreshFileReference(value);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::collectLocalData(not_null<PhotoData*> local) {
 | 
						|
	if (local == this) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (auto i = 0; i != kPhotoSizeCount; ++i) {
 | 
						|
		if (const auto from = local->_images[i].location.file().cacheKey()) {
 | 
						|
			if (const auto to = _images[i].location.file().cacheKey()) {
 | 
						|
				_owner->cache().copyIfEmpty(from, to);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (const auto localMedia = local->activeMediaView()) {
 | 
						|
		auto media = createMediaView();
 | 
						|
		media->collectLocalData(localMedia.get());
 | 
						|
		_owner->keepAlive(std::move(media));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::isNull() const {
 | 
						|
	return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid();
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::load(
 | 
						|
		PhotoSize size,
 | 
						|
		Data::FileOrigin origin,
 | 
						|
		LoadFromCloudSetting fromCloud,
 | 
						|
		bool autoLoading) {
 | 
						|
	const auto valid = validSizeIndex(size);
 | 
						|
	const auto existing = existingSizeIndex(size);
 | 
						|
 | 
						|
	// Could've changed, if the requested size didn't have a location.
 | 
						|
	const auto validSize = static_cast<PhotoSize>(valid);
 | 
						|
	const auto finalCheck = [=] {
 | 
						|
		if (const auto active = activeMediaView()) {
 | 
						|
			return !active->image(size);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	};
 | 
						|
	const auto done = [=](QImage result, QByteArray bytes) {
 | 
						|
		Expects(_images[valid].loader != nullptr);
 | 
						|
 | 
						|
		// Find out what progressive photo size have we loaded exactly.
 | 
						|
		auto goodFor = validSize;
 | 
						|
		const auto loadSize = _images[valid].loader->loadSize();
 | 
						|
		if (valid > 0 && _images[valid].byteSize > loadSize) {
 | 
						|
			for (auto i = valid; i != 0;) {
 | 
						|
				--i;
 | 
						|
				const auto required = _images[i].progressivePartSize;
 | 
						|
				if (required > 0 && required <= loadSize) {
 | 
						|
					goodFor = static_cast<PhotoSize>(i);
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (const auto active = activeMediaView()) {
 | 
						|
			active->set(
 | 
						|
				validSize,
 | 
						|
				goodFor,
 | 
						|
				ValidatePhotoImage(std::move(result), _images[valid]),
 | 
						|
				std::move(bytes));
 | 
						|
		}
 | 
						|
		if (validSize == PhotoSize::Large && goodFor == validSize) {
 | 
						|
			_owner->photoLoadDone(this);
 | 
						|
		}
 | 
						|
	};
 | 
						|
	const auto fail = [=](bool started) {
 | 
						|
		if (validSize == PhotoSize::Large) {
 | 
						|
			_owner->photoLoadFail(this, started);
 | 
						|
		}
 | 
						|
	};
 | 
						|
	const auto progress = [=] {
 | 
						|
		if (validSize == PhotoSize::Large) {
 | 
						|
			_owner->photoLoadProgress(this);
 | 
						|
		}
 | 
						|
	};
 | 
						|
	Data::LoadCloudFile(
 | 
						|
		&session(),
 | 
						|
		_images[valid],
 | 
						|
		origin,
 | 
						|
		fromCloud,
 | 
						|
		autoLoading,
 | 
						|
		Data::kImageCacheTag,
 | 
						|
		finalCheck,
 | 
						|
		done,
 | 
						|
		fail,
 | 
						|
		progress,
 | 
						|
		_images[existing].progressivePartSize);
 | 
						|
 | 
						|
	if (size == PhotoSize::Large) {
 | 
						|
		_owner->notifyPhotoLayoutChanged(this);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<PhotoMedia> PhotoData::createMediaView() {
 | 
						|
	if (auto result = activeMediaView()) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	auto result = std::make_shared<PhotoMedia>(this);
 | 
						|
	_media = result;
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<PhotoMedia> PhotoData::activeMediaView() const {
 | 
						|
	return _media.lock();
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::updateImages(
 | 
						|
		const QByteArray &inlineThumbnailBytes,
 | 
						|
		const ImageWithLocation &small,
 | 
						|
		const ImageWithLocation &thumbnail,
 | 
						|
		const ImageWithLocation &large,
 | 
						|
		const ImageWithLocation &videoSmall,
 | 
						|
		const ImageWithLocation &videoLarge,
 | 
						|
		crl::time videoStartTime) {
 | 
						|
	if (!inlineThumbnailBytes.isEmpty()
 | 
						|
		&& _inlineThumbnailBytes.isEmpty()) {
 | 
						|
		_inlineThumbnailBytes = inlineThumbnailBytes;
 | 
						|
	}
 | 
						|
	const auto update = [&](PhotoSize size, const ImageWithLocation &data) {
 | 
						|
		const auto index = PhotoSizeIndex(size);
 | 
						|
		Data::UpdateCloudFile(
 | 
						|
			_images[index],
 | 
						|
			data,
 | 
						|
			owner().cache(),
 | 
						|
			Data::kImageCacheTag,
 | 
						|
			[=](Data::FileOrigin origin) { load(size, origin); },
 | 
						|
			[=](QImage preloaded, QByteArray bytes) {
 | 
						|
				if (const auto media = activeMediaView()) {
 | 
						|
					media->set(
 | 
						|
						size,
 | 
						|
						size,
 | 
						|
						ValidatePhotoImage(
 | 
						|
							std::move(preloaded),
 | 
						|
							_images[index]),
 | 
						|
						std::move(bytes));
 | 
						|
				}
 | 
						|
			});
 | 
						|
	};
 | 
						|
	update(PhotoSize::Small, small);
 | 
						|
	update(PhotoSize::Thumbnail, thumbnail);
 | 
						|
	update(PhotoSize::Large, large);
 | 
						|
 | 
						|
	if (!videoLarge.location.valid()) {
 | 
						|
		_videoSizes = nullptr;
 | 
						|
	} else {
 | 
						|
		if (!_videoSizes) {
 | 
						|
			_videoSizes = std::make_unique<VideoSizes>();
 | 
						|
		}
 | 
						|
		_videoSizes->startTime = videoStartTime;
 | 
						|
		constexpr auto large = PhotoSize::Large;
 | 
						|
		constexpr auto small = PhotoSize::Small;
 | 
						|
		Data::UpdateCloudFile(
 | 
						|
			_videoSizes->large,
 | 
						|
			videoLarge,
 | 
						|
			owner().cache(),
 | 
						|
			Data::kAnimationCacheTag,
 | 
						|
			[&](Data::FileOrigin origin) { loadVideo(large, origin); });
 | 
						|
		Data::UpdateCloudFile(
 | 
						|
			_videoSizes->small,
 | 
						|
			videoSmall,
 | 
						|
			owner().cache(),
 | 
						|
			Data::kAnimationCacheTag,
 | 
						|
			[&](Data::FileOrigin origin) { loadVideo(small, origin); });
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] bool PhotoData::hasAttachedStickers() const {
 | 
						|
	return _hasStickers;
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::setHasAttachedStickers(bool value) {
 | 
						|
	_hasStickers = value;
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::width() const {
 | 
						|
	return _images[PhotoSizeIndex(PhotoSize::Large)].location.width();
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::height() const {
 | 
						|
	return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
 | 
						|
}
 | 
						|
 | 
						|
Data::CloudFile &PhotoData::videoFile(PhotoSize size) {
 | 
						|
	Expects(_videoSizes != nullptr);
 | 
						|
 | 
						|
	return (size == PhotoSize::Small && hasVideoSmall())
 | 
						|
		? _videoSizes->small
 | 
						|
		: _videoSizes->large;
 | 
						|
}
 | 
						|
 | 
						|
const Data::CloudFile &PhotoData::videoFile(PhotoSize size) const {
 | 
						|
	Expects(_videoSizes != nullptr);
 | 
						|
 | 
						|
	return (size == PhotoSize::Small && hasVideoSmall())
 | 
						|
		? _videoSizes->small
 | 
						|
		: _videoSizes->large;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
bool PhotoData::hasVideo() const {
 | 
						|
	return _videoSizes != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::hasVideoSmall() const {
 | 
						|
	return hasVideo() && _videoSizes->small.location.valid();
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::videoLoading(Data::PhotoSize size) const {
 | 
						|
	return _videoSizes && videoFile(size).loader != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::videoFailed(Data::PhotoSize size) const {
 | 
						|
	return _videoSizes
 | 
						|
		&& (videoFile(size).flags & Data::CloudFile::Flag::Failed);
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::loadVideo(Data::PhotoSize size, Data::FileOrigin origin) {
 | 
						|
	if (!_videoSizes) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto autoLoading = false;
 | 
						|
	const auto finalCheck = [=] {
 | 
						|
		if (const auto active = activeMediaView()) {
 | 
						|
			return active->videoContent(size).isEmpty();
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	};
 | 
						|
	const auto done = [=](QByteArray result) {
 | 
						|
		if (const auto active = activeMediaView()) {
 | 
						|
			active->setVideo(size, std::move(result));
 | 
						|
		}
 | 
						|
	};
 | 
						|
	Data::LoadCloudFile(
 | 
						|
		&session(),
 | 
						|
		videoFile(size),
 | 
						|
		origin,
 | 
						|
		LoadFromCloudOrLocal,
 | 
						|
		autoLoading,
 | 
						|
		Data::kAnimationCacheTag,
 | 
						|
		finalCheck,
 | 
						|
		done);
 | 
						|
}
 | 
						|
 | 
						|
const ImageLocation &PhotoData::videoLocation(Data::PhotoSize size) const {
 | 
						|
	static const auto empty = ImageLocation();
 | 
						|
	return _videoSizes ? videoFile(size).location : empty;
 | 
						|
}
 | 
						|
 | 
						|
int PhotoData::videoByteSize(Data::PhotoSize size) const {
 | 
						|
	return _videoSizes ? videoFile(size).byteSize : 0;
 | 
						|
}
 | 
						|
 | 
						|
crl::time PhotoData::videoStartPosition() const {
 | 
						|
	return _videoSizes ? _videoSizes->startTime : crl::time(0);
 | 
						|
}
 | 
						|
 | 
						|
void PhotoData::setVideoPlaybackFailed() {
 | 
						|
	if (_videoSizes) {
 | 
						|
		_videoSizes->playbackFailed = true;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::videoPlaybackFailed() const {
 | 
						|
	return _videoSizes && _videoSizes->playbackFailed;
 | 
						|
}
 | 
						|
 | 
						|
bool PhotoData::videoCanBePlayed() const {
 | 
						|
	return hasVideo() && !videoPlaybackFailed();
 | 
						|
}
 | 
						|
 | 
						|
auto PhotoData::createStreamingLoader(
 | 
						|
	Data::FileOrigin origin,
 | 
						|
	bool forceRemoteLoader) const
 | 
						|
-> std::unique_ptr<Media::Streaming::Loader> {
 | 
						|
	if (!hasVideo()) {
 | 
						|
		return nullptr;
 | 
						|
	}
 | 
						|
	constexpr auto large = PhotoSize::Large;
 | 
						|
	if (!forceRemoteLoader) {
 | 
						|
		const auto media = activeMediaView();
 | 
						|
		const auto bytes = media ? media->videoContent(large) : QByteArray();
 | 
						|
		if (media && !bytes.isEmpty()) {
 | 
						|
			return Media::Streaming::MakeBytesLoader(bytes);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return v::is<StorageFileLocation>(videoLocation(large).file().data)
 | 
						|
		? std::make_unique<Media::Streaming::LoaderMtproto>(
 | 
						|
			&session().downloader(),
 | 
						|
			v::get<StorageFileLocation>(videoLocation(large).file().data),
 | 
						|
			videoByteSize(large),
 | 
						|
			origin)
 | 
						|
		: nullptr;
 | 
						|
}
 |