mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Show video userpics in media viewer.
This commit is contained in:
parent
0126578dbd
commit
8c45b5e0f8
15 changed files with 402 additions and 131 deletions
|
@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_photo_media.h"
|
#include "data/data_photo_media.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "media/streaming/media_streaming_loader_local.h"
|
||||||
|
#include "media/streaming/media_streaming_loader_mtproto.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -296,7 +298,8 @@ void PhotoData::updateImages(
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video) {
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime) {
|
||||||
if (!inlineThumbnailBytes.isEmpty()
|
if (!inlineThumbnailBytes.isEmpty()
|
||||||
&& _inlineThumbnailBytes.isEmpty()) {
|
&& _inlineThumbnailBytes.isEmpty()) {
|
||||||
_inlineThumbnailBytes = inlineThumbnailBytes;
|
_inlineThumbnailBytes = inlineThumbnailBytes;
|
||||||
|
@ -318,6 +321,9 @@ void PhotoData::updateImages(
|
||||||
update(PhotoSize::Thumbnail, thumbnail);
|
update(PhotoSize::Thumbnail, thumbnail);
|
||||||
update(PhotoSize::Large, large);
|
update(PhotoSize::Large, large);
|
||||||
|
|
||||||
|
if (video.location.valid()) {
|
||||||
|
_videoStartTime = videoStartTime;
|
||||||
|
}
|
||||||
Data::UpdateCloudFile(
|
Data::UpdateCloudFile(
|
||||||
_video,
|
_video,
|
||||||
video,
|
video,
|
||||||
|
@ -378,6 +384,32 @@ int PhotoData::videoByteSize() const {
|
||||||
return _video.byteSize;
|
return _video.byteSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (!forceRemoteLoader) {
|
||||||
|
const auto media = activeMediaView();
|
||||||
|
if (media && !media->videoContent().isEmpty()) {
|
||||||
|
return Media::Streaming::MakeBytesLoader(media->videoContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return videoLocation().file().data.is<StorageFileLocation>()
|
||||||
|
? std::make_unique<Media::Streaming::LoaderMtproto>(
|
||||||
|
&session().downloader(),
|
||||||
|
videoLocation().file().data.get_unchecked<StorageFileLocation>(),
|
||||||
|
videoByteSize(),
|
||||||
|
origin)
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
PhotoClickHandler::PhotoClickHandler(
|
PhotoClickHandler::PhotoClickHandler(
|
||||||
not_null<PhotoData*> photo,
|
not_null<PhotoData*> photo,
|
||||||
FullMsgId context,
|
FullMsgId context,
|
||||||
|
|
|
@ -14,6 +14,12 @@ namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Media {
|
||||||
|
namespace Streaming {
|
||||||
|
class Loader;
|
||||||
|
} // namespace Streaming
|
||||||
|
} // namespace Media
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
|
@ -83,7 +89,8 @@ public:
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video);
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime);
|
||||||
[[nodiscard]] int validSizeIndex(Data::PhotoSize size) const;
|
[[nodiscard]] int validSizeIndex(Data::PhotoSize size) const;
|
||||||
|
|
||||||
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
|
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
|
||||||
|
@ -118,6 +125,20 @@ public:
|
||||||
void loadVideo(Data::FileOrigin origin);
|
void loadVideo(Data::FileOrigin origin);
|
||||||
[[nodiscard]] const ImageLocation &videoLocation() const;
|
[[nodiscard]] const ImageLocation &videoLocation() const;
|
||||||
[[nodiscard]] int videoByteSize() const;
|
[[nodiscard]] int videoByteSize() const;
|
||||||
|
[[nodiscard]] crl::time videoStartPosition() const {
|
||||||
|
return _videoStartTime;
|
||||||
|
}
|
||||||
|
void setVideoPlaybackFailed() {
|
||||||
|
_videoPlaybackFailed = true;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool videoPlaybackFailed() const {
|
||||||
|
return _videoPlaybackFailed;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool videoCanBePlayed() const;
|
||||||
|
[[nodiscard]] auto createStreamingLoader(
|
||||||
|
Data::FileOrigin origin,
|
||||||
|
bool forceRemoteLoader) const
|
||||||
|
-> std::unique_ptr<Media::Streaming::Loader>;
|
||||||
|
|
||||||
// For now they return size of the 'large' image.
|
// For now they return size of the 'large' image.
|
||||||
int width() const;
|
int width() const;
|
||||||
|
@ -136,6 +157,8 @@ private:
|
||||||
QByteArray _inlineThumbnailBytes;
|
QByteArray _inlineThumbnailBytes;
|
||||||
std::array<Data::CloudFile, Data::kPhotoSizeCount> _images;
|
std::array<Data::CloudFile, Data::kPhotoSizeCount> _images;
|
||||||
Data::CloudFile _video;
|
Data::CloudFile _video;
|
||||||
|
crl::time _videoStartTime = 0;
|
||||||
|
bool _videoPlaybackFailed = false;
|
||||||
|
|
||||||
int32 _dc = 0;
|
int32 _dc = 0;
|
||||||
uint64 _access = 0;
|
uint64 _access = 0;
|
||||||
|
|
|
@ -190,6 +190,14 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
|
||||||
return FindInlineThumbnail(data.vsizes().v);
|
return FindInlineThumbnail(data.vsizes().v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int VideoStartTime(const MTPDvideoSize &data) {
|
||||||
|
return int(
|
||||||
|
std::clamp(
|
||||||
|
std::floor(data.vvideo_start_ts().value_or_empty() * 1000),
|
||||||
|
0.,
|
||||||
|
double(std::numeric_limits<int>::max())));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Session::Session(not_null<Main::Session*> session)
|
Session::Session(not_null<Main::Session*> session)
|
||||||
|
@ -2158,7 +2166,8 @@ not_null<PhotoData*> Session::processPhoto(
|
||||||
small,
|
small,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
large,
|
large,
|
||||||
ImageWithLocation{});
|
ImageWithLocation{},
|
||||||
|
crl::time(0));
|
||||||
}, [&](const MTPDphotoEmpty &data) {
|
}, [&](const MTPDphotoEmpty &data) {
|
||||||
return photo(data.vid().v);
|
return photo(data.vid().v);
|
||||||
});
|
});
|
||||||
|
@ -2175,7 +2184,8 @@ not_null<PhotoData*> Session::photo(
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video) {
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime) {
|
||||||
const auto result = photo(id);
|
const auto result = photo(id);
|
||||||
photoApplyFields(
|
photoApplyFields(
|
||||||
result,
|
result,
|
||||||
|
@ -2188,7 +2198,8 @@ not_null<PhotoData*> Session::photo(
|
||||||
small,
|
small,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
large,
|
large,
|
||||||
video);
|
video,
|
||||||
|
videoStartTime);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2237,7 +2248,8 @@ PhotoData *Session::photoFromWeb(
|
||||||
ImageWithLocation{},
|
ImageWithLocation{},
|
||||||
ImageWithLocation{ .location = thumbnailLocation },
|
ImageWithLocation{ .location = thumbnailLocation },
|
||||||
ImageWithLocation{ .location = large },
|
ImageWithLocation{ .location = large },
|
||||||
ImageWithLocation{});
|
ImageWithLocation{},
|
||||||
|
crl::time(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::photoApplyFields(
|
void Session::photoApplyFields(
|
||||||
|
@ -2275,24 +2287,24 @@ void Session::photoApplyFields(
|
||||||
? ImageWithLocation()
|
? ImageWithLocation()
|
||||||
: Images::FromPhotoSize(_session, data, *i);
|
: Images::FromPhotoSize(_session, data, *i);
|
||||||
};
|
};
|
||||||
const auto video = [&] {
|
const auto findVideoSize = [&]() -> std::optional<MTPVideoSize> {
|
||||||
const auto sizes = data.vvideo_sizes();
|
const auto sizes = data.vvideo_sizes();
|
||||||
if (!sizes || sizes->v.isEmpty()) {
|
if (!sizes || sizes->v.isEmpty()) {
|
||||||
return ImageWithLocation();
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
const auto area = [](const MTPVideoSize &size) {
|
const auto area = [](const MTPVideoSize &size) {
|
||||||
return size.match([](const MTPDvideoSize &data) {
|
return size.match([](const MTPDvideoSize &data) {
|
||||||
return data.vw().v * data.vh().v;
|
return data.vw().v * data.vh().v;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const auto max = ranges::max_element(
|
return *ranges::max_element(
|
||||||
sizes->v,
|
sizes->v,
|
||||||
std::greater<>(),
|
std::greater<>(),
|
||||||
area);
|
area);
|
||||||
return Images::FromVideoSize(_session, data, *max);
|
|
||||||
};
|
};
|
||||||
const auto large = image(LargeLevels);
|
const auto large = image(LargeLevels);
|
||||||
if (large.location.valid()) {
|
if (large.location.valid()) {
|
||||||
|
const auto video = findVideoSize();
|
||||||
photoApplyFields(
|
photoApplyFields(
|
||||||
photo,
|
photo,
|
||||||
data.vaccess_hash().v,
|
data.vaccess_hash().v,
|
||||||
|
@ -2304,7 +2316,13 @@ void Session::photoApplyFields(
|
||||||
image(SmallLevels),
|
image(SmallLevels),
|
||||||
image(ThumbnailLevels),
|
image(ThumbnailLevels),
|
||||||
large,
|
large,
|
||||||
video());
|
(video
|
||||||
|
? Images::FromVideoSize(_session, data, *video)
|
||||||
|
: ImageWithLocation()),
|
||||||
|
(video
|
||||||
|
? VideoStartTime(
|
||||||
|
*video->match([](const auto &data) { return &data; }))
|
||||||
|
: 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2319,7 +2337,8 @@ void Session::photoApplyFields(
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video) {
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime) {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2331,7 +2350,8 @@ void Session::photoApplyFields(
|
||||||
small,
|
small,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
large,
|
large,
|
||||||
video);
|
video,
|
||||||
|
videoStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<DocumentData*> Session::document(DocumentId id) {
|
not_null<DocumentData*> Session::document(DocumentId id) {
|
||||||
|
|
|
@ -404,7 +404,8 @@ public:
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video);
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime);
|
||||||
void photoConvert(
|
void photoConvert(
|
||||||
not_null<PhotoData*> original,
|
not_null<PhotoData*> original,
|
||||||
const MTPPhoto &data);
|
const MTPPhoto &data);
|
||||||
|
@ -673,7 +674,8 @@ private:
|
||||||
const ImageWithLocation &small,
|
const ImageWithLocation &small,
|
||||||
const ImageWithLocation &thumbnail,
|
const ImageWithLocation &thumbnail,
|
||||||
const ImageWithLocation &large,
|
const ImageWithLocation &large,
|
||||||
const ImageWithLocation &video);
|
const ImageWithLocation &video,
|
||||||
|
crl::time videoStartTime);
|
||||||
|
|
||||||
void documentApplyFields(
|
void documentApplyFields(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_streaming.h"
|
#include "data/data_streaming.h"
|
||||||
|
|
||||||
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
|
@ -19,16 +20,16 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kKeepAliveTimeout = 5 * crl::time(1000);
|
constexpr auto kKeepAliveTimeout = 5 * crl::time(1000);
|
||||||
|
|
||||||
template <typename Object>
|
template <typename Object, typename Data>
|
||||||
bool PruneDestroyedAndSet(
|
bool PruneDestroyedAndSet(
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
not_null<DocumentData*>,
|
not_null<Data*>,
|
||||||
std::weak_ptr<Object>> &objects,
|
std::weak_ptr<Object>> &objects,
|
||||||
not_null<DocumentData*> document,
|
not_null<Data*> data,
|
||||||
const std::shared_ptr<Object> &object) {
|
const std::shared_ptr<Object> &object) {
|
||||||
auto result = false;
|
auto result = false;
|
||||||
for (auto i = begin(objects); i != end(objects);) {
|
for (auto i = begin(objects); i != end(objects);) {
|
||||||
if (i->first == document) {
|
if (i->first == data) {
|
||||||
(i++)->second = object;
|
(i++)->second = object;
|
||||||
result = true;
|
result = true;
|
||||||
} else if (i->second.lock() != nullptr) {
|
} else if (i->second.lock() != nullptr) {
|
||||||
|
@ -49,54 +50,87 @@ Streaming::Streaming(not_null<Session*> owner)
|
||||||
|
|
||||||
Streaming::~Streaming() = default;
|
Streaming::~Streaming() = default;
|
||||||
|
|
||||||
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
|
||||||
not_null<DocumentData*> document,
|
template <typename Data>
|
||||||
|
[[nodiscard]] std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||||
|
not_null<Data*> data,
|
||||||
FileOrigin origin,
|
FileOrigin origin,
|
||||||
bool forceRemoteLoader) {
|
bool forceRemoteLoader) {
|
||||||
const auto i = _readers.find(document);
|
const auto i = readers.find(data);
|
||||||
if (i != end(_readers)) {
|
if (i != end(readers)) {
|
||||||
if (auto result = i->second.lock()) {
|
if (auto result = i->second.lock()) {
|
||||||
if (!forceRemoteLoader || result->isRemoteLoader()) {
|
if (!forceRemoteLoader || result->isRemoteLoader()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto loader = document->createStreamingLoader(origin, forceRemoteLoader);
|
auto loader = data->createStreamingLoader(origin, forceRemoteLoader);
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto result = std::make_shared<Reader>(
|
auto result = std::make_shared<Reader>(
|
||||||
std::move(loader),
|
std::move(loader),
|
||||||
&_owner->cacheBigFile());
|
&_owner->cacheBigFile());
|
||||||
if (!PruneDestroyedAndSet(_readers, document, result)) {
|
if (!PruneDestroyedAndSet(readers, data, result)) {
|
||||||
_readers.emplace_or_assign(document, result);
|
readers.emplace_or_assign(data, result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Data>
|
||||||
|
[[nodiscard]] std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||||
|
not_null<Data*> data,
|
||||||
|
FileOrigin origin) {
|
||||||
|
const auto i = documents.find(data);
|
||||||
|
if (i != end(documents)) {
|
||||||
|
if (auto result = i->second.lock()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto reader = sharedReader(readers, data, origin);
|
||||||
|
if (!reader) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto result = std::make_shared<Document>(data, std::move(reader));
|
||||||
|
if (!PruneDestroyedAndSet(documents, data, result)) {
|
||||||
|
documents.emplace_or_assign(data, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
FileOrigin origin,
|
||||||
|
bool forceRemoteLoader) {
|
||||||
|
return sharedReader(_fileReaders, document, origin, forceRemoteLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
FileOrigin origin) {
|
FileOrigin origin) {
|
||||||
const auto i = _documents.find(document);
|
return sharedDocument(_fileDocuments, _fileReaders, document, origin);
|
||||||
if (i != end(_documents)) {
|
}
|
||||||
if (auto result = i->second.lock()) {
|
|
||||||
return result;
|
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||||
}
|
not_null<PhotoData*> photo,
|
||||||
}
|
FileOrigin origin,
|
||||||
auto reader = sharedReader(document, origin);
|
bool forceRemoteLoader) {
|
||||||
if (!reader) {
|
return sharedReader(_photoReaders, photo, origin, forceRemoteLoader);
|
||||||
return nullptr;
|
}
|
||||||
}
|
|
||||||
auto result = std::make_shared<Document>(document, std::move(reader));
|
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||||
if (!PruneDestroyedAndSet(_documents, document, result)) {
|
not_null<PhotoData*> photo,
|
||||||
_documents.emplace_or_assign(document, result);
|
FileOrigin origin) {
|
||||||
}
|
return sharedDocument(_photoDocuments, _photoReaders, photo, origin);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Streaming::keepAlive(not_null<DocumentData*> document) {
|
void Streaming::keepAlive(not_null<DocumentData*> document) {
|
||||||
const auto i = _documents.find(document);
|
const auto i = _fileDocuments.find(document);
|
||||||
if (i == end(_documents)) {
|
if (i == end(_fileDocuments)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto shared = i->second.lock();
|
auto shared = i->second.lock();
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
class PhotoData;
|
||||||
class DocumentData;
|
class DocumentData;
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
|
@ -41,17 +42,46 @@ public:
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
FileOrigin origin);
|
FileOrigin origin);
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<Reader> sharedReader(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
FileOrigin origin,
|
||||||
|
bool forceRemoteLoader = false);
|
||||||
|
[[nodiscard]] std::shared_ptr<Document> sharedDocument(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
FileOrigin origin);
|
||||||
|
|
||||||
void keepAlive(not_null<DocumentData*> document);
|
void keepAlive(not_null<DocumentData*> document);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void clearKeptAlive();
|
void clearKeptAlive();
|
||||||
|
|
||||||
|
template <typename Data>
|
||||||
|
[[nodiscard]] std::shared_ptr<Reader> sharedReader(
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||||
|
not_null<Data*> data,
|
||||||
|
FileOrigin origin,
|
||||||
|
bool forceRemoteLoader = false);
|
||||||
|
|
||||||
|
template <typename Data>
|
||||||
|
[[nodiscard]] std::shared_ptr<Document> sharedDocument(
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
|
||||||
|
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||||
|
not_null<Data*> data,
|
||||||
|
FileOrigin origin);
|
||||||
|
|
||||||
const not_null<Session*> _owner;
|
const not_null<Session*> _owner;
|
||||||
|
|
||||||
base::flat_map<not_null<DocumentData*>, std::weak_ptr<Reader>> _readers;
|
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
not_null<DocumentData*>,
|
not_null<DocumentData*>,
|
||||||
std::weak_ptr<Document>> _documents;
|
std::weak_ptr<Reader>> _fileReaders;
|
||||||
|
base::flat_map<
|
||||||
|
not_null<DocumentData*>,
|
||||||
|
std::weak_ptr<Document>> _fileDocuments;
|
||||||
|
|
||||||
|
base::flat_map<not_null<PhotoData*>, std::weak_ptr<Reader>> _photoReaders;
|
||||||
|
base::flat_map<
|
||||||
|
not_null<PhotoData*>,
|
||||||
|
std::weak_ptr<Document>> _photoDocuments;
|
||||||
|
|
||||||
base::flat_map<std::shared_ptr<Document>, crl::time> _keptAlive;
|
base::flat_map<std::shared_ptr<Document>, crl::time> _keptAlive;
|
||||||
base::Timer _keptAliveTimer;
|
base::Timer _keptAliveTimer;
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/streaming/media_streaming_reader.h"
|
#include "media/streaming/media_streaming_reader.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document_media.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 "main/main_session.h"
|
||||||
|
@ -34,19 +35,29 @@ constexpr auto kGoodThumbnailQuality = 87;
|
||||||
Document::Document(
|
Document::Document(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
std::shared_ptr<Reader> reader)
|
std::shared_ptr<Reader> reader)
|
||||||
: Document(std::move(reader), document) {
|
: Document(std::move(reader), document, nullptr) {
|
||||||
_player.fullInCache(
|
_player.fullInCache(
|
||||||
) | rpl::start_with_next([=](bool fullInCache) {
|
) | rpl::start_with_next([=](bool fullInCache) {
|
||||||
_document->setLoadedInMediaCache(fullInCache);
|
_document->setLoadedInMediaCache(fullInCache);
|
||||||
}, _player.lifetime());
|
}, _player.lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
Document::Document(std::unique_ptr<Loader> loader)
|
Document::Document(
|
||||||
: Document(std::make_shared<Reader>(std::move(loader)), nullptr) {
|
not_null<PhotoData*> photo,
|
||||||
|
std::shared_ptr<Reader> reader)
|
||||||
|
: Document(std::move(reader), nullptr, photo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Document::Document(std::shared_ptr<Reader> reader, DocumentData *document)
|
Document::Document(std::unique_ptr<Loader> loader)
|
||||||
|
: Document(std::make_shared<Reader>(std::move(loader)), nullptr, nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Document::Document(
|
||||||
|
std::shared_ptr<Reader> reader,
|
||||||
|
DocumentData *document,
|
||||||
|
PhotoData *photo)
|
||||||
: _document(document)
|
: _document(document)
|
||||||
|
, _photo(photo)
|
||||||
, _player(std::move(reader))
|
, _player(std::move(reader))
|
||||||
, _radial(
|
, _radial(
|
||||||
[=] { waitingCallback(); },
|
[=] { waitingCallback(); },
|
||||||
|
@ -71,10 +82,6 @@ const Information &Document::info() const {
|
||||||
return _info;
|
return _info;
|
||||||
}
|
}
|
||||||
|
|
||||||
//not_null<DocumentData*> Document::data() const {
|
|
||||||
// return _document;
|
|
||||||
//}
|
|
||||||
|
|
||||||
void Document::play(const PlaybackOptions &options) {
|
void Document::play(const PlaybackOptions &options) {
|
||||||
_player.play(options);
|
_player.play(options);
|
||||||
_info.audio.state.position
|
_info.audio.state.position
|
||||||
|
@ -160,6 +167,10 @@ void Document::handleError(Error &&error) {
|
||||||
} else if (error == Error::OpenFailed) {
|
} else if (error == Error::OpenFailed) {
|
||||||
_document->setInappPlaybackFailed();
|
_document->setInappPlaybackFailed();
|
||||||
}
|
}
|
||||||
|
} else if (_photo) {
|
||||||
|
if (error == Error::NotStreamable || error == Error::OpenFailed) {
|
||||||
|
_photo->setVideoPlaybackFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
waitingChange(false);
|
waitingChange(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ public:
|
||||||
Document(
|
Document(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
std::shared_ptr<Reader> reader);
|
std::shared_ptr<Reader> reader);
|
||||||
|
Document(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
std::shared_ptr<Reader> reader);
|
||||||
explicit Document(std::unique_ptr<Loader> loader);
|
explicit Document(std::unique_ptr<Loader> loader);
|
||||||
|
|
||||||
void play(const PlaybackOptions &options);
|
void play(const PlaybackOptions &options);
|
||||||
|
@ -33,14 +36,16 @@ public:
|
||||||
[[nodiscard]] Player &player();
|
[[nodiscard]] Player &player();
|
||||||
[[nodiscard]] const Player &player() const;
|
[[nodiscard]] const Player &player() const;
|
||||||
[[nodiscard]] const Information &info() const;
|
[[nodiscard]] const Information &info() const;
|
||||||
// [[nodiscard]] not_null<DocumentData*> data() const;
|
|
||||||
|
|
||||||
[[nodiscard]] bool waitingShown() const;
|
[[nodiscard]] bool waitingShown() const;
|
||||||
[[nodiscard]] float64 waitingOpacity() const;
|
[[nodiscard]] float64 waitingOpacity() const;
|
||||||
[[nodiscard]] Ui::RadialState waitingState() const;
|
[[nodiscard]] Ui::RadialState waitingState() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Document(std::shared_ptr<Reader> reader, DocumentData *document);
|
Document(
|
||||||
|
std::shared_ptr<Reader> reader,
|
||||||
|
DocumentData *document,
|
||||||
|
PhotoData *photo);
|
||||||
|
|
||||||
friend class Instance;
|
friend class Instance;
|
||||||
|
|
||||||
|
@ -59,6 +64,7 @@ private:
|
||||||
void validateGoodThumbnail();
|
void validateGoodThumbnail();
|
||||||
|
|
||||||
DocumentData *_document = nullptr;
|
DocumentData *_document = nullptr;
|
||||||
|
PhotoData *_photo = nullptr;
|
||||||
Player _player;
|
Player _player;
|
||||||
Information _info;
|
Information _info;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "media/streaming/media_streaming_document.h"
|
#include "media/streaming/media_streaming_document.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_streaming.h"
|
#include "data/data_streaming.h"
|
||||||
|
@ -35,6 +36,15 @@ Instance::Instance(
|
||||||
std::move(waitingCallback)) {
|
std::move(waitingCallback)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Instance::Instance(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
Data::FileOrigin origin,
|
||||||
|
Fn<void()> waitingCallback)
|
||||||
|
: Instance(
|
||||||
|
photo->owner().streaming().sharedDocument(photo, origin),
|
||||||
|
std::move(waitingCallback)) {
|
||||||
|
}
|
||||||
|
|
||||||
Instance::~Instance() {
|
Instance::~Instance() {
|
||||||
if (_shared) {
|
if (_shared) {
|
||||||
unlockPlayer();
|
unlockPlayer();
|
||||||
|
|
|
@ -34,6 +34,10 @@ public:
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
Data::FileOrigin origin,
|
Data::FileOrigin origin,
|
||||||
Fn<void()> waitingCallback);
|
Fn<void()> waitingCallback);
|
||||||
|
Instance(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
Data::FileOrigin origin,
|
||||||
|
Fn<void()> waitingCallback);
|
||||||
~Instance();
|
~Instance();
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const;
|
[[nodiscard]] bool valid() const;
|
||||||
|
|
|
@ -130,7 +130,7 @@ QWidget *PipDelegate::pipParentWidget() {
|
||||||
return _parent;
|
return _parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
Images::Options VideoThumbOptions(not_null<DocumentData*> document) {
|
Images::Options VideoThumbOptions(DocumentData *document) {
|
||||||
const auto result = Images::Option::Smooth | Images::Option::Blurred;
|
const auto result = Images::Option::Smooth | Images::Option::Blurred;
|
||||||
return (document && document->isVideoMessage())
|
return (document && document->isVideoMessage())
|
||||||
? (result | Images::Option::Circled)
|
? (result | Images::Option::Circled)
|
||||||
|
@ -240,6 +240,12 @@ struct OverlayWidget::Streamed {
|
||||||
QWidget *controlsParent,
|
QWidget *controlsParent,
|
||||||
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||||
Fn<void()> waitingCallback);
|
Fn<void()> waitingCallback);
|
||||||
|
Streamed(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
Data::FileOrigin origin,
|
||||||
|
QWidget *controlsParent,
|
||||||
|
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||||
|
Fn<void()> waitingCallback);
|
||||||
|
|
||||||
Streaming::Instance instance;
|
Streaming::Instance instance;
|
||||||
PlaybackControls controls;
|
PlaybackControls controls;
|
||||||
|
@ -277,6 +283,16 @@ OverlayWidget::Streamed::Streamed(
|
||||||
, controls(controlsParent, controlsDelegate) {
|
, controls(controlsParent, controlsDelegate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OverlayWidget::Streamed::Streamed(
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
Data::FileOrigin origin,
|
||||||
|
QWidget *controlsParent,
|
||||||
|
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||||
|
Fn<void()> waitingCallback)
|
||||||
|
: instance(photo, origin, std::move(waitingCallback))
|
||||||
|
, controls(controlsParent, controlsDelegate) {
|
||||||
|
}
|
||||||
|
|
||||||
OverlayWidget::PipWrap::PipWrap(
|
OverlayWidget::PipWrap::PipWrap(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
|
@ -356,7 +372,7 @@ OverlayWidget::OverlayWidget()
|
||||||
|
|
||||||
Core::App().calls().currentCallValue(
|
Core::App().calls().currentCallValue(
|
||||||
) | rpl::start_with_next([=](Calls::Call *call) {
|
) | rpl::start_with_next([=](Calls::Call *call) {
|
||||||
if (!_streamed) {
|
if (!_streamed || videoIsGifOrUserpic()) {
|
||||||
return;
|
return;
|
||||||
} else if (call) {
|
} else if (call) {
|
||||||
playbackPauseOnCall();
|
playbackPauseOnCall();
|
||||||
|
@ -441,8 +457,10 @@ QSize OverlayWidget::videoSize() const {
|
||||||
return flipSizeByRotation(_streamed->instance.info().video.size);
|
return flipSizeByRotation(_streamed->instance.info().video.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayWidget::videoIsGifv() const {
|
bool OverlayWidget::videoIsGifOrUserpic() const {
|
||||||
return _streamed && _document->isAnimation() && !_document->isVideoMessage();
|
return _streamed
|
||||||
|
&& (!_document
|
||||||
|
|| (_document->isAnimation() && !_document->isVideoMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage OverlayWidget::videoFrame() const {
|
QImage OverlayWidget::videoFrame() const {
|
||||||
|
@ -709,7 +727,7 @@ void OverlayWidget::refreshCaptionGeometry() {
|
||||||
_groupThumbs = nullptr;
|
_groupThumbs = nullptr;
|
||||||
_groupThumbsRect = QRect();
|
_groupThumbsRect = QRect();
|
||||||
}
|
}
|
||||||
const auto captionBottom = (_streamed && !videoIsGifv())
|
const auto captionBottom = (_streamed && !videoIsGifOrUserpic())
|
||||||
? (_streamed->controls.y() - st::mediaviewCaptionMargin.height())
|
? (_streamed->controls.y() - st::mediaviewCaptionMargin.height())
|
||||||
: _groupThumbs
|
: _groupThumbs
|
||||||
? _groupThumbsTop
|
? _groupThumbsTop
|
||||||
|
@ -884,7 +902,7 @@ void OverlayWidget::contentSizeChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::resizeContentByScreenSize() {
|
void OverlayWidget::resizeContentByScreenSize() {
|
||||||
const auto bottom = (!_streamed || videoIsGifv())
|
const auto bottom = (!_streamed || videoIsGifOrUserpic())
|
||||||
? height()
|
? height()
|
||||||
: (_streamed->controls.y()
|
: (_streamed->controls.y()
|
||||||
- st::mediaviewCaptionPadding.bottom()
|
- st::mediaviewCaptionPadding.bottom()
|
||||||
|
@ -942,8 +960,10 @@ float64 OverlayWidget::radialProgress() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayWidget::radialLoading() const {
|
bool OverlayWidget::radialLoading() const {
|
||||||
if (_document) {
|
if (_streamed) {
|
||||||
return _document->loading() && !_streamed;
|
return false;
|
||||||
|
} else if (_document) {
|
||||||
|
return _document->loading();
|
||||||
} else if (_photo) {
|
} else if (_photo) {
|
||||||
return _photo->displayLoading();
|
return _photo->displayLoading();
|
||||||
}
|
}
|
||||||
|
@ -1133,7 +1153,9 @@ void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
|
||||||
_photo = photo;
|
_photo = photo;
|
||||||
_photoMedia = _photo->createMediaView();
|
_photoMedia = _photo->createMediaView();
|
||||||
_photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
|
_photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
|
||||||
_photo->load(fileOrigin(), LoadFromCloudOrLocal, true);
|
if (!_photo->hasVideo() || _photo->videoPlaybackFailed()) {
|
||||||
|
_photo->load(fileOrigin(), LoadFromCloudOrLocal, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1186,7 +1208,7 @@ void OverlayWidget::onHideControls(bool force) {
|
||||||
|| _menu
|
|| _menu
|
||||||
|| _mousePressed
|
|| _mousePressed
|
||||||
|| (_fullScreenVideo
|
|| (_fullScreenVideo
|
||||||
&& !videoIsGifv()
|
&& !videoIsGifOrUserpic()
|
||||||
&& _streamed->controls.geometry().contains(_lastMouseMovePos))) {
|
&& _streamed->controls.geometry().contains(_lastMouseMovePos))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1307,7 +1329,9 @@ void OverlayWidget::onSaveAs() {
|
||||||
updateOver(_lastMouseMovePos);
|
updateOver(_lastMouseMovePos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!_photo || !_photoMedia->loaded()) return;
|
if (!_photo || !_photoMedia->loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto image = _photoMedia->image(Data::PhotoSize::Large)->original();
|
const auto image = _photoMedia->image(Data::PhotoSize::Large)->original();
|
||||||
auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
|
auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
|
||||||
|
@ -1858,7 +1882,7 @@ void OverlayWidget::refreshCaption(HistoryItem *item) {
|
||||||
|
|
||||||
using namespace HistoryView;
|
using namespace HistoryView;
|
||||||
_caption = Ui::Text::String(st::msgMinWidth);
|
_caption = Ui::Text::String(st::msgMinWidth);
|
||||||
const auto duration = (_streamed && !videoIsGifv())
|
const auto duration = (_streamed && _document && !videoIsGifOrUserpic())
|
||||||
? _document->getDuration()
|
? _document->getDuration()
|
||||||
: 0;
|
: 0;
|
||||||
const auto base = duration
|
const auto base = duration
|
||||||
|
@ -2045,18 +2069,29 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item)
|
||||||
_radial.stop();
|
_radial.stop();
|
||||||
|
|
||||||
refreshMediaViewer();
|
refreshMediaViewer();
|
||||||
|
|
||||||
|
_staticContent = QPixmap();
|
||||||
|
if (_photo->videoCanBePlayed()) {
|
||||||
|
initStreaming();
|
||||||
|
}
|
||||||
|
|
||||||
refreshCaption(item);
|
refreshCaption(item);
|
||||||
|
|
||||||
_zoom = 0;
|
|
||||||
_zoomToScreen = _zoomToDefault = 0;
|
|
||||||
_blurred = true;
|
_blurred = true;
|
||||||
_staticContent = QPixmap();
|
|
||||||
_down = OverNone;
|
_down = OverNone;
|
||||||
const auto size = style::ConvertScale(flipSizeByRotation(QSize(
|
if (!_staticContent.isNull()) {
|
||||||
photo->width(),
|
// Video thumbnail.
|
||||||
photo->height())));
|
const auto size = style::ConvertScale(
|
||||||
_w = size.width();
|
flipSizeByRotation(_staticContent.size()));
|
||||||
_h = size.height();
|
_w = size.width();
|
||||||
|
_h = size.height();
|
||||||
|
} else {
|
||||||
|
const auto size = style::ConvertScale(flipSizeByRotation(QSize(
|
||||||
|
photo->width(),
|
||||||
|
photo->height())));
|
||||||
|
_w = size.width();
|
||||||
|
_h = size.height();
|
||||||
|
}
|
||||||
contentSizeChanged();
|
contentSizeChanged();
|
||||||
refreshFromLabel(item);
|
refreshFromLabel(item);
|
||||||
displayFinished();
|
displayFinished();
|
||||||
|
@ -2252,16 +2287,24 @@ void OverlayWidget::displayFinished() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OverlayWidget::canInitStreaming() const {
|
||||||
|
return (_document && _documentMedia->canBePlayed())
|
||||||
|
|| (_photo && _photo->videoCanBePlayed());
|
||||||
|
}
|
||||||
|
|
||||||
bool OverlayWidget::initStreaming(bool continueStreaming) {
|
bool OverlayWidget::initStreaming(bool continueStreaming) {
|
||||||
Expects(_document != nullptr);
|
Expects(canInitStreaming());
|
||||||
Expects(_documentMedia->canBePlayed());
|
|
||||||
|
|
||||||
if (_streamed) {
|
if (_streamed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
initStreamingThumbnail();
|
initStreamingThumbnail();
|
||||||
if (!createStreamingObjects()) {
|
if (!createStreamingObjects()) {
|
||||||
_document->setInappPlaybackFailed();
|
if (_document) {
|
||||||
|
_document->setInappPlaybackFailed();
|
||||||
|
} else {
|
||||||
|
_photo->setVideoPlaybackFailed();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2301,22 +2344,47 @@ void OverlayWidget::startStreamingPlayer() {
|
||||||
|
|
||||||
const auto position = _document
|
const auto position = _document
|
||||||
? _document->session().settings().mediaLastPlaybackPosition(_document->id)
|
? _document->session().settings().mediaLastPlaybackPosition(_document->id)
|
||||||
|
: _photo
|
||||||
|
? _photo->videoStartPosition()
|
||||||
: 0;
|
: 0;
|
||||||
restartAtSeekPosition(position);
|
restartAtSeekPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::initStreamingThumbnail() {
|
void OverlayWidget::initStreamingThumbnail() {
|
||||||
Expects(_document != nullptr);
|
Expects(_photo || _document);
|
||||||
|
|
||||||
_touchbarDisplay.fire(TouchBarItemType::Video);
|
_touchbarDisplay.fire(TouchBarItemType::Video);
|
||||||
|
|
||||||
const auto good = _documentMedia->goodThumbnail();
|
const auto computePhotoThumbnail = [&] {
|
||||||
const auto useGood = (good != nullptr);
|
const auto thumbnail = _photoMedia->image(Data::PhotoSize::Thumbnail);
|
||||||
const auto thumbnail = _documentMedia->thumbnail();
|
if (thumbnail) {
|
||||||
const auto useThumb = (thumbnail != nullptr);
|
return thumbnail;
|
||||||
const auto blurred = _documentMedia->thumbnailInline();
|
} else if (_peer && _peer->userpicPhotoId() == _photo->id) {
|
||||||
const auto size = useGood ? good->size() : _document->dimensions;
|
if (const auto view = _peer->activeUserpicView()) {
|
||||||
if (!useGood && !thumbnail && !blurred) {
|
if (const auto image = view->image()) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return thumbnail;
|
||||||
|
};
|
||||||
|
const auto good = _document
|
||||||
|
? _documentMedia->goodThumbnail()
|
||||||
|
: _photoMedia->image(Data::PhotoSize::Large);
|
||||||
|
const auto thumbnail = _document
|
||||||
|
? _documentMedia->thumbnail()
|
||||||
|
: computePhotoThumbnail();
|
||||||
|
const auto blurred = _document
|
||||||
|
? _documentMedia->thumbnailInline()
|
||||||
|
: _photoMedia->thumbnailInline();
|
||||||
|
const auto size = _photo
|
||||||
|
? QSize(
|
||||||
|
_photo->videoLocation().width(),
|
||||||
|
_photo->videoLocation().height())
|
||||||
|
: good
|
||||||
|
? good->size()
|
||||||
|
: _document->dimensions;
|
||||||
|
if (!good && !thumbnail && !blurred) {
|
||||||
return;
|
return;
|
||||||
} else if (size.isEmpty()) {
|
} else if (size.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -2325,16 +2393,16 @@ void OverlayWidget::initStreamingThumbnail() {
|
||||||
const auto h = size.height();
|
const auto h = size.height();
|
||||||
const auto options = VideoThumbOptions(_document);
|
const auto options = VideoThumbOptions(_document);
|
||||||
const auto goodOptions = (options & ~Images::Option::Blurred);
|
const auto goodOptions = (options & ~Images::Option::Blurred);
|
||||||
_staticContent = (useGood
|
_staticContent = (good
|
||||||
? good
|
? good
|
||||||
: useThumb
|
: thumbnail
|
||||||
? thumbnail
|
? thumbnail
|
||||||
: blurred
|
: blurred
|
||||||
? blurred
|
? blurred
|
||||||
: Image::BlankMedia().get())->pixNoCache(
|
: Image::BlankMedia().get())->pixNoCache(
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
useGood ? goodOptions : options,
|
good ? goodOptions : options,
|
||||||
w / cIntRetinaFactor(),
|
w / cIntRetinaFactor(),
|
||||||
h / cIntRetinaFactor());
|
h / cIntRetinaFactor());
|
||||||
_staticContent.setDevicePixelRatio(cRetinaFactor());
|
_staticContent.setDevicePixelRatio(cRetinaFactor());
|
||||||
|
@ -2358,24 +2426,36 @@ void OverlayWidget::applyVideoSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayWidget::createStreamingObjects() {
|
bool OverlayWidget::createStreamingObjects() {
|
||||||
_streamed = std::make_unique<Streamed>(
|
Expects(_photo || _document);
|
||||||
_document,
|
|
||||||
fileOrigin(),
|
if (_document) {
|
||||||
this,
|
_streamed = std::make_unique<Streamed>(
|
||||||
static_cast<PlaybackControls::Delegate*>(this),
|
_document,
|
||||||
[=] { waitingAnimationCallback(); });
|
fileOrigin(),
|
||||||
|
this,
|
||||||
|
static_cast<PlaybackControls::Delegate*>(this),
|
||||||
|
[=] { waitingAnimationCallback(); });
|
||||||
|
} else {
|
||||||
|
_streamed = std::make_unique<Streamed>(
|
||||||
|
_photo,
|
||||||
|
fileOrigin(),
|
||||||
|
this,
|
||||||
|
static_cast<PlaybackControls::Delegate*>(this),
|
||||||
|
[=] { waitingAnimationCallback(); });
|
||||||
|
}
|
||||||
if (!_streamed->instance.valid()) {
|
if (!_streamed->instance.valid()) {
|
||||||
_streamed = nullptr;
|
_streamed = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_streamed->instance.setPriority(kOverlayLoaderPriority);
|
_streamed->instance.setPriority(kOverlayLoaderPriority);
|
||||||
_streamed->instance.lockPlayer();
|
_streamed->instance.lockPlayer();
|
||||||
_streamed->withSound = _document->isAudioFile()
|
_streamed->withSound = _document
|
||||||
|| _document->isVideoFile()
|
&& (_document->isAudioFile()
|
||||||
|| _document->isVoiceMessage()
|
|| _document->isVideoFile()
|
||||||
|| _document->isVideoMessage();
|
|| _document->isVoiceMessage()
|
||||||
|
|| _document->isVideoMessage());
|
||||||
|
|
||||||
if (videoIsGifv()) {
|
if (videoIsGifOrUserpic()) {
|
||||||
_streamed->controls.hide();
|
_streamed->controls.hide();
|
||||||
} else {
|
} else {
|
||||||
refreshClipControllerGeometry();
|
refreshClipControllerGeometry();
|
||||||
|
@ -2430,17 +2510,25 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
|
void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
|
||||||
Expects(_document != nullptr);
|
Expects(_document || _photo);
|
||||||
|
|
||||||
if (error == Streaming::Error::NotStreamable) {
|
if (error == Streaming::Error::NotStreamable) {
|
||||||
_document->setNotSupportsStreaming();
|
if (_document) {
|
||||||
|
_document->setNotSupportsStreaming();
|
||||||
|
} else {
|
||||||
|
_photo->setVideoPlaybackFailed();
|
||||||
|
}
|
||||||
} else if (error == Streaming::Error::OpenFailed) {
|
} else if (error == Streaming::Error::OpenFailed) {
|
||||||
_document->setInappPlaybackFailed();
|
if (_document) {
|
||||||
|
_document->setInappPlaybackFailed();
|
||||||
|
} else {
|
||||||
|
_photo->setVideoPlaybackFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!_documentMedia->canBePlayed()) {
|
if (canInitStreaming()) {
|
||||||
redisplayContent();
|
|
||||||
} else {
|
|
||||||
updatePlaybackState();
|
updatePlaybackState();
|
||||||
|
} else {
|
||||||
|
redisplayContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2543,7 +2631,7 @@ void OverlayWidget::initThemePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::refreshClipControllerGeometry() {
|
void OverlayWidget::refreshClipControllerGeometry() {
|
||||||
if (!_streamed || videoIsGifv()) {
|
if (!_streamed || videoIsGifOrUserpic()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2578,7 +2666,7 @@ void OverlayWidget::playbackControlsFromFullScreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::playbackControlsToPictureInPicture() {
|
void OverlayWidget::playbackControlsToPictureInPicture() {
|
||||||
if (!videoIsGifv()) {
|
if (!videoIsGifOrUserpic()) {
|
||||||
switchToPip();
|
switchToPip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2608,7 +2696,7 @@ void OverlayWidget::playbackPauseResume() {
|
||||||
_streamed->resumeOnCallEnd = false;
|
_streamed->resumeOnCallEnd = false;
|
||||||
if (_streamed->instance.player().failed()) {
|
if (_streamed->instance.player().failed()) {
|
||||||
clearStreaming();
|
clearStreaming();
|
||||||
if (!_documentMedia->canBePlayed() || !initStreaming()) {
|
if (!canInitStreaming() || !initStreaming()) {
|
||||||
redisplayContent();
|
redisplayContent();
|
||||||
}
|
}
|
||||||
} else if (_streamed->instance.player().finished()
|
} else if (_streamed->instance.player().finished()
|
||||||
|
@ -2627,7 +2715,6 @@ void OverlayWidget::playbackPauseResume() {
|
||||||
|
|
||||||
void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
||||||
Expects(_streamed != nullptr);
|
Expects(_streamed != nullptr);
|
||||||
Expects(_document != nullptr);
|
|
||||||
|
|
||||||
if (videoShown()) {
|
if (videoShown()) {
|
||||||
_streamed->instance.saveFrameToCover();
|
_streamed->instance.saveFrameToCover();
|
||||||
|
@ -2638,11 +2725,12 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
||||||
}
|
}
|
||||||
auto options = Streaming::PlaybackOptions();
|
auto options = Streaming::PlaybackOptions();
|
||||||
options.position = position;
|
options.position = position;
|
||||||
options.audioId = AudioMsgId(_document, _msgid);
|
|
||||||
if (!_streamed->withSound) {
|
if (!_streamed->withSound) {
|
||||||
options.mode = Streaming::Mode::Video;
|
options.mode = Streaming::Mode::Video;
|
||||||
options.loop = true;
|
options.loop = true;
|
||||||
} else {
|
} else {
|
||||||
|
Assert(_document != nullptr);
|
||||||
|
options.audioId = AudioMsgId(_document, _msgid);
|
||||||
options.speed = Core::App().settings().videoPlaybackSpeed();
|
options.speed = Core::App().settings().videoPlaybackSpeed();
|
||||||
if (_pip) {
|
if (_pip) {
|
||||||
_pip = nullptr;
|
_pip = nullptr;
|
||||||
|
@ -2706,7 +2794,7 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
|
||||||
Core::App().settings().setVideoPlaybackSpeed(speed);
|
Core::App().settings().setVideoPlaybackSpeed(speed);
|
||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
}
|
}
|
||||||
if (_streamed && !videoIsGifv()) {
|
if (_streamed && !videoIsGifOrUserpic()) {
|
||||||
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
|
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
|
||||||
_streamed->instance.setSpeed(speed);
|
_streamed->instance.setSpeed(speed);
|
||||||
}
|
}
|
||||||
|
@ -2743,7 +2831,7 @@ void OverlayWidget::switchToPip() {
|
||||||
void OverlayWidget::playbackToggleFullScreen() {
|
void OverlayWidget::playbackToggleFullScreen() {
|
||||||
Expects(_streamed != nullptr);
|
Expects(_streamed != nullptr);
|
||||||
|
|
||||||
if (!videoShown() || (videoIsGifv() && !_fullScreenVideo)) {
|
if (!videoShown() || (videoIsGifOrUserpic() && !_fullScreenVideo)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_fullScreenVideo = !_fullScreenVideo;
|
_fullScreenVideo = !_fullScreenVideo;
|
||||||
|
@ -2797,7 +2885,7 @@ void OverlayWidget::playbackPauseMusic() {
|
||||||
void OverlayWidget::updatePlaybackState() {
|
void OverlayWidget::updatePlaybackState() {
|
||||||
Expects(_streamed != nullptr);
|
Expects(_streamed != nullptr);
|
||||||
|
|
||||||
if (videoIsGifv()) {
|
if (videoIsGifOrUserpic()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto state = _streamed->instance.player().prepareLegacyState();
|
const auto state = _streamed->instance.player().prepareLegacyState();
|
||||||
|
|
|
@ -283,7 +283,7 @@ private:
|
||||||
void refreshClipControllerGeometry();
|
void refreshClipControllerGeometry();
|
||||||
void refreshCaptionGeometry();
|
void refreshCaptionGeometry();
|
||||||
|
|
||||||
[[nodiscard]] bool initStreaming(bool continueStreaming = false);
|
bool initStreaming(bool continueStreaming = false);
|
||||||
void startStreamingPlayer();
|
void startStreamingPlayer();
|
||||||
void initStreamingThumbnail();
|
void initStreamingThumbnail();
|
||||||
void streamingReady(Streaming::Information &&info);
|
void streamingReady(Streaming::Information &&info);
|
||||||
|
@ -346,7 +346,7 @@ private:
|
||||||
void applyVideoSize();
|
void applyVideoSize();
|
||||||
[[nodiscard]] bool videoShown() const;
|
[[nodiscard]] bool videoShown() const;
|
||||||
[[nodiscard]] QSize videoSize() const;
|
[[nodiscard]] QSize videoSize() const;
|
||||||
[[nodiscard]] bool videoIsGifv() const;
|
[[nodiscard]] bool videoIsGifOrUserpic() const;
|
||||||
[[nodiscard]] QImage videoFrame() const;
|
[[nodiscard]] QImage videoFrame() const;
|
||||||
[[nodiscard]] QImage videoFrameForDirectPaint() const;
|
[[nodiscard]] QImage videoFrameForDirectPaint() const;
|
||||||
[[nodiscard]] QImage transformVideoFrame(QImage frame) const;
|
[[nodiscard]] QImage transformVideoFrame(QImage frame) const;
|
||||||
|
@ -356,6 +356,7 @@ private:
|
||||||
void paintTransformedVideoFrame(Painter &p);
|
void paintTransformedVideoFrame(Painter &p);
|
||||||
void paintTransformedStaticContent(Painter &p);
|
void paintTransformedStaticContent(Painter &p);
|
||||||
void clearStreaming(bool savePosition = true);
|
void clearStreaming(bool savePosition = true);
|
||||||
|
bool canInitStreaming() const;
|
||||||
|
|
||||||
QBrush _transparentBrush;
|
QBrush _transparentBrush;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kDocumentBaseCacheTag = 0x0000000000010000ULL;
|
constexpr auto kDocumentBaseCacheTag = 0x0000000000010000ULL;
|
||||||
constexpr auto kDocumentBaseCacheMask = 0x000000000000FF00ULL;
|
constexpr auto kDocumentBaseCacheMask = 0x000000000000FF00ULL;
|
||||||
|
constexpr auto kPhotoBaseCacheTag = 0x0000000000020000ULL;
|
||||||
|
constexpr auto kPhotoBaseCacheMask = 0x000000000000FF00ULL;
|
||||||
constexpr auto kSerializeTypeShift = quint8(0x08);
|
constexpr auto kSerializeTypeShift = quint8(0x08);
|
||||||
constexpr auto kNonStorageLocationToken = quint8(0x10);
|
constexpr auto kNonStorageLocationToken = quint8(0x10);
|
||||||
const auto kInMediaCacheLocation = QString("*media_cache*");
|
const auto kInMediaCacheLocation = QString("*media_cache*");
|
||||||
|
@ -405,9 +407,6 @@ Storage::Cache::Key StorageFileLocation::cacheKey() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
|
Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
|
||||||
// Skip '1' and '2' for legacy document cache keys.
|
|
||||||
const auto shifted = ((uint64(_type) + 3) << 8);
|
|
||||||
const auto sliced = uint64(_dcId) & 0xFFULL;
|
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Type::Document: {
|
case Type::Document: {
|
||||||
const auto high = kDocumentBaseCacheTag
|
const auto high = kDocumentBaseCacheTag
|
||||||
|
@ -425,6 +424,18 @@ Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
|
||||||
| ((uint64(_dcId) & 0xFFULL) << 8)
|
| ((uint64(_dcId) & 0xFFULL) << 8)
|
||||||
| (_volumeId >> 56);
|
| (_volumeId >> 56);
|
||||||
const auto low = (_volumeId << 8);
|
const auto low = (_volumeId << 8);
|
||||||
|
|
||||||
|
Ensures((low & 0xFFULL) == 0);
|
||||||
|
return Storage::Cache::Key{ high, low };
|
||||||
|
}
|
||||||
|
|
||||||
|
case Type::Photo: {
|
||||||
|
const auto high = kPhotoBaseCacheTag
|
||||||
|
| ((uint64(_dcId) << 16) & kPhotoBaseCacheMask)
|
||||||
|
| (_id >> 48);
|
||||||
|
const auto low = (_id << 16);
|
||||||
|
|
||||||
|
Ensures((low & 0xFFULL) == 0);
|
||||||
return Storage::Cache::Key{ high, low };
|
return Storage::Cache::Key{ high, low };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +443,6 @@ Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
|
||||||
case Type::PeerPhoto:
|
case Type::PeerPhoto:
|
||||||
case Type::Encrypted:
|
case Type::Encrypted:
|
||||||
case Type::Secure:
|
case Type::Secure:
|
||||||
case Type::Photo:
|
|
||||||
case Type::Takeout:
|
case Type::Takeout:
|
||||||
Unexpected("Not implemented file location type.");
|
Unexpected("Not implemented file location type.");
|
||||||
|
|
||||||
|
|
|
@ -611,9 +611,9 @@ inline bool operator>=(
|
||||||
|
|
||||||
struct ImageWithLocation {
|
struct ImageWithLocation {
|
||||||
ImageLocation location;
|
ImageLocation location;
|
||||||
int bytesCount = 0;
|
|
||||||
QByteArray bytes;
|
QByteArray bytes;
|
||||||
QImage preloaded;
|
QImage preloaded;
|
||||||
|
int bytesCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
InMemoryKey inMemoryKey(const StorageFileLocation &location);
|
InMemoryKey inMemoryKey(const StorageFileLocation &location);
|
||||||
|
|
|
@ -51,8 +51,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
data.vtype())) },
|
data.vtype())) },
|
||||||
data.vw().v,
|
data.vw().v,
|
||||||
data.vh().v),
|
data.vh().v),
|
||||||
|
.bytes = bytes,
|
||||||
.bytesCount = bytes.size(),
|
.bytesCount = bytes.size(),
|
||||||
.bytes = bytes
|
|
||||||
};
|
};
|
||||||
}, [&](const MTPDphotoStrippedSize &data) {
|
}, [&](const MTPDphotoStrippedSize &data) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -69,8 +69,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
// data.vtype())) },
|
// data.vtype())) },
|
||||||
// width, // ???
|
// width, // ???
|
||||||
// height), // ???
|
// height), // ???
|
||||||
|
// .bytes = bytes,
|
||||||
// .bytesCount = bytes.size(),
|
// .bytesCount = bytes.size(),
|
||||||
// .bytes = bytes
|
|
||||||
//};
|
//};
|
||||||
}, [&](const MTPDphotoSizeEmpty &) {
|
}, [&](const MTPDphotoSizeEmpty &) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -110,8 +110,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
data.vtype())) },
|
data.vtype())) },
|
||||||
data.vw().v,
|
data.vw().v,
|
||||||
data.vh().v),
|
data.vh().v),
|
||||||
|
.bytes = bytes,
|
||||||
.bytesCount = bytes.size(),
|
.bytesCount = bytes.size(),
|
||||||
.bytes = bytes
|
|
||||||
};
|
};
|
||||||
}, [&](const MTPDphotoStrippedSize &data) {
|
}, [&](const MTPDphotoStrippedSize &data) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -128,8 +128,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
// data.vtype())) },
|
// data.vtype())) },
|
||||||
// width, // ???
|
// width, // ???
|
||||||
// height), // ???
|
// height), // ???
|
||||||
|
// .bytes = bytes,
|
||||||
// .bytesCount = bytes.size(),
|
// .bytesCount = bytes.size(),
|
||||||
// .bytes = bytes
|
|
||||||
//};
|
//};
|
||||||
}, [&](const MTPDphotoSizeEmpty &) {
|
}, [&](const MTPDphotoSizeEmpty &) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -172,8 +172,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
location.vlocal_id())) },
|
location.vlocal_id())) },
|
||||||
data.vw().v,
|
data.vw().v,
|
||||||
data.vh().v),
|
data.vh().v),
|
||||||
|
.bytes = bytes,
|
||||||
.bytesCount = bytes.size(),
|
.bytesCount = bytes.size(),
|
||||||
.bytes = bytes
|
|
||||||
};
|
};
|
||||||
}, [&](const MTPDphotoStrippedSize &data) {
|
}, [&](const MTPDphotoStrippedSize &data) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -190,8 +190,8 @@ ImageWithLocation FromPhotoSize(
|
||||||
// data.vtype())) },
|
// data.vtype())) },
|
||||||
// width, // ???
|
// width, // ???
|
||||||
// height), // ???
|
// height), // ???
|
||||||
|
// .bytes = bytes,
|
||||||
// .bytesCount = bytes.size(),
|
// .bytesCount = bytes.size(),
|
||||||
// .bytes = bytes
|
|
||||||
//};
|
//};
|
||||||
}, [&](const MTPDphotoSizeEmpty &) {
|
}, [&](const MTPDphotoSizeEmpty &) {
|
||||||
return ImageWithLocation();
|
return ImageWithLocation();
|
||||||
|
@ -212,9 +212,9 @@ ImageWithLocation FromImageInMemory(
|
||||||
DownloadLocation{ InMemoryLocation{ bytes } },
|
DownloadLocation{ InMemoryLocation{ bytes } },
|
||||||
image.width(),
|
image.width(),
|
||||||
image.height()),
|
image.height()),
|
||||||
.bytesCount = bytes.size(),
|
|
||||||
.bytes = bytes,
|
.bytes = bytes,
|
||||||
.preloaded = image
|
.preloaded = image,
|
||||||
|
.bytesCount = bytes.size(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ ImageWithLocation FromVideoSize(
|
||||||
data.vtype())) },
|
data.vtype())) },
|
||||||
data.vw().v,
|
data.vw().v,
|
||||||
data.vh().v),
|
data.vh().v),
|
||||||
.bytesCount = data.vsize().v
|
.bytesCount = data.vsize().v,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -285,7 +285,7 @@ ImageWithLocation FromVideoSize(
|
||||||
data.vtype())) },
|
data.vtype())) },
|
||||||
data.vw().v,
|
data.vw().v,
|
||||||
data.vh().v),
|
data.vh().v),
|
||||||
.bytesCount = data.vsize().v
|
.bytesCount = data.vsize().v,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue