Show video userpics in media viewer.

This commit is contained in:
John Preston 2020-07-03 14:48:55 +04:00
parent 0126578dbd
commit 8c45b5e0f8
15 changed files with 402 additions and 131 deletions

View File

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "ui/image/image.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 "storage/file_download.h"
#include "core/application.h"
@ -296,7 +298,8 @@ void PhotoData::updateImages(
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video) {
const ImageWithLocation &video,
crl::time videoStartTime) {
if (!inlineThumbnailBytes.isEmpty()
&& _inlineThumbnailBytes.isEmpty()) {
_inlineThumbnailBytes = inlineThumbnailBytes;
@ -318,6 +321,9 @@ void PhotoData::updateImages(
update(PhotoSize::Thumbnail, thumbnail);
update(PhotoSize::Large, large);
if (video.location.valid()) {
_videoStartTime = videoStartTime;
}
Data::UpdateCloudFile(
_video,
video,
@ -378,6 +384,32 @@ int PhotoData::videoByteSize() const {
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(
not_null<PhotoData*> photo,
FullMsgId context,

View File

@ -14,6 +14,12 @@ namespace Main {
class Session;
} // namespace Main
namespace Media {
namespace Streaming {
class Loader;
} // namespace Streaming
} // namespace Media
namespace Data {
class Session;
@ -83,7 +89,8 @@ public:
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video);
const ImageWithLocation &video,
crl::time videoStartTime);
[[nodiscard]] int validSizeIndex(Data::PhotoSize size) const;
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
@ -118,6 +125,20 @@ public:
void loadVideo(Data::FileOrigin origin);
[[nodiscard]] const ImageLocation &videoLocation() 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.
int width() const;
@ -136,6 +157,8 @@ private:
QByteArray _inlineThumbnailBytes;
std::array<Data::CloudFile, Data::kPhotoSizeCount> _images;
Data::CloudFile _video;
crl::time _videoStartTime = 0;
bool _videoPlaybackFailed = false;
int32 _dc = 0;
uint64 _access = 0;

View File

@ -190,6 +190,14 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
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
Session::Session(not_null<Main::Session*> session)
@ -2158,7 +2166,8 @@ not_null<PhotoData*> Session::processPhoto(
small,
thumbnail,
large,
ImageWithLocation{});
ImageWithLocation{},
crl::time(0));
}, [&](const MTPDphotoEmpty &data) {
return photo(data.vid().v);
});
@ -2175,7 +2184,8 @@ not_null<PhotoData*> Session::photo(
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video) {
const ImageWithLocation &video,
crl::time videoStartTime) {
const auto result = photo(id);
photoApplyFields(
result,
@ -2188,7 +2198,8 @@ not_null<PhotoData*> Session::photo(
small,
thumbnail,
large,
video);
video,
videoStartTime);
return result;
}
@ -2237,7 +2248,8 @@ PhotoData *Session::photoFromWeb(
ImageWithLocation{},
ImageWithLocation{ .location = thumbnailLocation },
ImageWithLocation{ .location = large },
ImageWithLocation{});
ImageWithLocation{},
crl::time(0));
}
void Session::photoApplyFields(
@ -2275,24 +2287,24 @@ void Session::photoApplyFields(
? ImageWithLocation()
: Images::FromPhotoSize(_session, data, *i);
};
const auto video = [&] {
const auto findVideoSize = [&]() -> std::optional<MTPVideoSize> {
const auto sizes = data.vvideo_sizes();
if (!sizes || sizes->v.isEmpty()) {
return ImageWithLocation();
return std::nullopt;
}
const auto area = [](const MTPVideoSize &size) {
return size.match([](const MTPDvideoSize &data) {
return data.vw().v * data.vh().v;
});
};
const auto max = ranges::max_element(
return *ranges::max_element(
sizes->v,
std::greater<>(),
area);
return Images::FromVideoSize(_session, data, *max);
};
const auto large = image(LargeLevels);
if (large.location.valid()) {
const auto video = findVideoSize();
photoApplyFields(
photo,
data.vaccess_hash().v,
@ -2304,7 +2316,13 @@ void Session::photoApplyFields(
image(SmallLevels),
image(ThumbnailLevels),
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 &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video) {
const ImageWithLocation &video,
crl::time videoStartTime) {
if (!date) {
return;
}
@ -2331,7 +2350,8 @@ void Session::photoApplyFields(
small,
thumbnail,
large,
video);
video,
videoStartTime);
}
not_null<DocumentData*> Session::document(DocumentId id) {

View File

@ -404,7 +404,8 @@ public:
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video);
const ImageWithLocation &video,
crl::time videoStartTime);
void photoConvert(
not_null<PhotoData*> original,
const MTPPhoto &data);
@ -673,7 +674,8 @@ private:
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &video);
const ImageWithLocation &video,
crl::time videoStartTime);
void documentApplyFields(
not_null<DocumentData*> document,

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_streaming.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
@ -19,16 +20,16 @@ namespace {
constexpr auto kKeepAliveTimeout = 5 * crl::time(1000);
template <typename Object>
template <typename Object, typename Data>
bool PruneDestroyedAndSet(
base::flat_map<
not_null<DocumentData*>,
not_null<Data*>,
std::weak_ptr<Object>> &objects,
not_null<DocumentData*> document,
not_null<Data*> data,
const std::shared_ptr<Object> &object) {
auto result = false;
for (auto i = begin(objects); i != end(objects);) {
if (i->first == document) {
if (i->first == data) {
(i++)->second = object;
result = true;
} else if (i->second.lock() != nullptr) {
@ -49,54 +50,87 @@ Streaming::Streaming(not_null<Session*> owner)
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,
bool forceRemoteLoader) {
const auto i = _readers.find(document);
if (i != end(_readers)) {
const auto i = readers.find(data);
if (i != end(readers)) {
if (auto result = i->second.lock()) {
if (!forceRemoteLoader || result->isRemoteLoader()) {
return result;
}
}
}
auto loader = document->createStreamingLoader(origin, forceRemoteLoader);
auto loader = data->createStreamingLoader(origin, forceRemoteLoader);
if (!loader) {
return nullptr;
}
auto result = std::make_shared<Reader>(
std::move(loader),
&_owner->cacheBigFile());
if (!PruneDestroyedAndSet(_readers, document, result)) {
_readers.emplace_or_assign(document, result);
if (!PruneDestroyedAndSet(readers, data, result)) {
readers.emplace_or_assign(data, 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(
not_null<DocumentData*> document,
FileOrigin origin) {
const auto i = _documents.find(document);
if (i != end(_documents)) {
if (auto result = i->second.lock()) {
return result;
}
}
auto reader = sharedReader(document, origin);
if (!reader) {
return nullptr;
}
auto result = std::make_shared<Document>(document, std::move(reader));
if (!PruneDestroyedAndSet(_documents, document, result)) {
_documents.emplace_or_assign(document, result);
}
return result;
return sharedDocument(_fileDocuments, _fileReaders, document, origin);
}
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
not_null<PhotoData*> photo,
FileOrigin origin,
bool forceRemoteLoader) {
return sharedReader(_photoReaders, photo, origin, forceRemoteLoader);
}
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
not_null<PhotoData*> photo,
FileOrigin origin) {
return sharedDocument(_photoDocuments, _photoReaders, photo, origin);
}
void Streaming::keepAlive(not_null<DocumentData*> document) {
const auto i = _documents.find(document);
if (i == end(_documents)) {
const auto i = _fileDocuments.find(document);
if (i == end(_fileDocuments)) {
return;
}
auto shared = i->second.lock();

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
class PhotoData;
class DocumentData;
namespace Media {
@ -41,17 +42,46 @@ public:
not_null<DocumentData*> document,
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);
private:
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;
base::flat_map<not_null<DocumentData*>, std::weak_ptr<Reader>> _readers;
base::flat_map<
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::Timer _keptAliveTimer;

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_reader.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "main/main_session.h"
@ -34,19 +35,29 @@ constexpr auto kGoodThumbnailQuality = 87;
Document::Document(
not_null<DocumentData*> document,
std::shared_ptr<Reader> reader)
: Document(std::move(reader), document) {
: Document(std::move(reader), document, nullptr) {
_player.fullInCache(
) | rpl::start_with_next([=](bool fullInCache) {
_document->setLoadedInMediaCache(fullInCache);
}, _player.lifetime());
}
Document::Document(std::unique_ptr<Loader> loader)
: Document(std::make_shared<Reader>(std::move(loader)), nullptr) {
Document::Document(
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)
, _photo(photo)
, _player(std::move(reader))
, _radial(
[=] { waitingCallback(); },
@ -71,10 +82,6 @@ const Information &Document::info() const {
return _info;
}
//not_null<DocumentData*> Document::data() const {
// return _document;
//}
void Document::play(const PlaybackOptions &options) {
_player.play(options);
_info.audio.state.position
@ -160,6 +167,10 @@ void Document::handleError(Error &&error) {
} else if (error == Error::OpenFailed) {
_document->setInappPlaybackFailed();
}
} else if (_photo) {
if (error == Error::NotStreamable || error == Error::OpenFailed) {
_photo->setVideoPlaybackFailed();
}
}
waitingChange(false);
}

View File

@ -25,6 +25,9 @@ public:
Document(
not_null<DocumentData*> document,
std::shared_ptr<Reader> reader);
Document(
not_null<PhotoData*> photo,
std::shared_ptr<Reader> reader);
explicit Document(std::unique_ptr<Loader> loader);
void play(const PlaybackOptions &options);
@ -33,14 +36,16 @@ public:
[[nodiscard]] Player &player();
[[nodiscard]] const Player &player() const;
[[nodiscard]] const Information &info() const;
// [[nodiscard]] not_null<DocumentData*> data() const;
[[nodiscard]] bool waitingShown() const;
[[nodiscard]] float64 waitingOpacity() const;
[[nodiscard]] Ui::RadialState waitingState() const;
private:
Document(std::shared_ptr<Reader> reader, DocumentData *document);
Document(
std::shared_ptr<Reader> reader,
DocumentData *document,
PhotoData *photo);
friend class Instance;
@ -59,6 +64,7 @@ private:
void validateGoodThumbnail();
DocumentData *_document = nullptr;
PhotoData *_photo = nullptr;
Player _player;
Information _info;

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_document.h"
#include "data/data_file_origin.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_streaming.h"
@ -35,6 +36,15 @@ Instance::Instance(
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() {
if (_shared) {
unlockPlayer();

View File

@ -34,6 +34,10 @@ public:
not_null<DocumentData*> document,
Data::FileOrigin origin,
Fn<void()> waitingCallback);
Instance(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
Fn<void()> waitingCallback);
~Instance();
[[nodiscard]] bool valid() const;

View File

@ -130,7 +130,7 @@ QWidget *PipDelegate::pipParentWidget() {
return _parent;
}
Images::Options VideoThumbOptions(not_null<DocumentData*> document) {
Images::Options VideoThumbOptions(DocumentData *document) {
const auto result = Images::Option::Smooth | Images::Option::Blurred;
return (document && document->isVideoMessage())
? (result | Images::Option::Circled)
@ -240,6 +240,12 @@ struct OverlayWidget::Streamed {
QWidget *controlsParent,
not_null<PlaybackControls::Delegate*> controlsDelegate,
Fn<void()> waitingCallback);
Streamed(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
QWidget *controlsParent,
not_null<PlaybackControls::Delegate*> controlsDelegate,
Fn<void()> waitingCallback);
Streaming::Instance instance;
PlaybackControls controls;
@ -277,6 +283,16 @@ OverlayWidget::Streamed::Streamed(
, 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(
QWidget *parent,
not_null<DocumentData*> document,
@ -356,7 +372,7 @@ OverlayWidget::OverlayWidget()
Core::App().calls().currentCallValue(
) | rpl::start_with_next([=](Calls::Call *call) {
if (!_streamed) {
if (!_streamed || videoIsGifOrUserpic()) {
return;
} else if (call) {
playbackPauseOnCall();
@ -441,8 +457,10 @@ QSize OverlayWidget::videoSize() const {
return flipSizeByRotation(_streamed->instance.info().video.size);
}
bool OverlayWidget::videoIsGifv() const {
return _streamed && _document->isAnimation() && !_document->isVideoMessage();
bool OverlayWidget::videoIsGifOrUserpic() const {
return _streamed
&& (!_document
|| (_document->isAnimation() && !_document->isVideoMessage()));
}
QImage OverlayWidget::videoFrame() const {
@ -709,7 +727,7 @@ void OverlayWidget::refreshCaptionGeometry() {
_groupThumbs = nullptr;
_groupThumbsRect = QRect();
}
const auto captionBottom = (_streamed && !videoIsGifv())
const auto captionBottom = (_streamed && !videoIsGifOrUserpic())
? (_streamed->controls.y() - st::mediaviewCaptionMargin.height())
: _groupThumbs
? _groupThumbsTop
@ -884,7 +902,7 @@ void OverlayWidget::contentSizeChanged() {
}
void OverlayWidget::resizeContentByScreenSize() {
const auto bottom = (!_streamed || videoIsGifv())
const auto bottom = (!_streamed || videoIsGifOrUserpic())
? height()
: (_streamed->controls.y()
- st::mediaviewCaptionPadding.bottom()
@ -942,8 +960,10 @@ float64 OverlayWidget::radialProgress() const {
}
bool OverlayWidget::radialLoading() const {
if (_document) {
return _document->loading() && !_streamed;
if (_streamed) {
return false;
} else if (_document) {
return _document->loading();
} else if (_photo) {
return _photo->displayLoading();
}
@ -1133,7 +1153,9 @@ void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
_photo = photo;
_photoMedia = _photo->createMediaView();
_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
|| _mousePressed
|| (_fullScreenVideo
&& !videoIsGifv()
&& !videoIsGifOrUserpic()
&& _streamed->controls.geometry().contains(_lastMouseMovePos))) {
return;
}
@ -1307,7 +1329,9 @@ void OverlayWidget::onSaveAs() {
updateOver(_lastMouseMovePos);
}
} else {
if (!_photo || !_photoMedia->loaded()) return;
if (!_photo || !_photoMedia->loaded()) {
return;
}
const auto image = _photoMedia->image(Data::PhotoSize::Large)->original();
auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
@ -1858,7 +1882,7 @@ void OverlayWidget::refreshCaption(HistoryItem *item) {
using namespace HistoryView;
_caption = Ui::Text::String(st::msgMinWidth);
const auto duration = (_streamed && !videoIsGifv())
const auto duration = (_streamed && _document && !videoIsGifOrUserpic())
? _document->getDuration()
: 0;
const auto base = duration
@ -2045,18 +2069,29 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item)
_radial.stop();
refreshMediaViewer();
_staticContent = QPixmap();
if (_photo->videoCanBePlayed()) {
initStreaming();
}
refreshCaption(item);
_zoom = 0;
_zoomToScreen = _zoomToDefault = 0;
_blurred = true;
_staticContent = QPixmap();
_down = OverNone;
const auto size = style::ConvertScale(flipSizeByRotation(QSize(
photo->width(),
photo->height())));
_w = size.width();
_h = size.height();
if (!_staticContent.isNull()) {
// Video thumbnail.
const auto size = style::ConvertScale(
flipSizeByRotation(_staticContent.size()));
_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();
refreshFromLabel(item);
displayFinished();
@ -2252,16 +2287,24 @@ void OverlayWidget::displayFinished() {
}
}
bool OverlayWidget::canInitStreaming() const {
return (_document && _documentMedia->canBePlayed())
|| (_photo && _photo->videoCanBePlayed());
}
bool OverlayWidget::initStreaming(bool continueStreaming) {
Expects(_document != nullptr);
Expects(_documentMedia->canBePlayed());
Expects(canInitStreaming());
if (_streamed) {
return true;
}
initStreamingThumbnail();
if (!createStreamingObjects()) {
_document->setInappPlaybackFailed();
if (_document) {
_document->setInappPlaybackFailed();
} else {
_photo->setVideoPlaybackFailed();
}
return false;
}
@ -2301,22 +2344,47 @@ void OverlayWidget::startStreamingPlayer() {
const auto position = _document
? _document->session().settings().mediaLastPlaybackPosition(_document->id)
: _photo
? _photo->videoStartPosition()
: 0;
restartAtSeekPosition(position);
}
void OverlayWidget::initStreamingThumbnail() {
Expects(_document != nullptr);
Expects(_photo || _document);
_touchbarDisplay.fire(TouchBarItemType::Video);
const auto good = _documentMedia->goodThumbnail();
const auto useGood = (good != nullptr);
const auto thumbnail = _documentMedia->thumbnail();
const auto useThumb = (thumbnail != nullptr);
const auto blurred = _documentMedia->thumbnailInline();
const auto size = useGood ? good->size() : _document->dimensions;
if (!useGood && !thumbnail && !blurred) {
const auto computePhotoThumbnail = [&] {
const auto thumbnail = _photoMedia->image(Data::PhotoSize::Thumbnail);
if (thumbnail) {
return thumbnail;
} else if (_peer && _peer->userpicPhotoId() == _photo->id) {
if (const auto view = _peer->activeUserpicView()) {
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;
} else if (size.isEmpty()) {
return;
@ -2325,16 +2393,16 @@ void OverlayWidget::initStreamingThumbnail() {
const auto h = size.height();
const auto options = VideoThumbOptions(_document);
const auto goodOptions = (options & ~Images::Option::Blurred);
_staticContent = (useGood
_staticContent = (good
? good
: useThumb
: thumbnail
? thumbnail
: blurred
? blurred
: Image::BlankMedia().get())->pixNoCache(
w,
h,
useGood ? goodOptions : options,
good ? goodOptions : options,
w / cIntRetinaFactor(),
h / cIntRetinaFactor());
_staticContent.setDevicePixelRatio(cRetinaFactor());
@ -2358,24 +2426,36 @@ void OverlayWidget::applyVideoSize() {
}
bool OverlayWidget::createStreamingObjects() {
_streamed = std::make_unique<Streamed>(
_document,
fileOrigin(),
this,
static_cast<PlaybackControls::Delegate*>(this),
[=] { waitingAnimationCallback(); });
Expects(_photo || _document);
if (_document) {
_streamed = std::make_unique<Streamed>(
_document,
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()) {
_streamed = nullptr;
return false;
}
_streamed->instance.setPriority(kOverlayLoaderPriority);
_streamed->instance.lockPlayer();
_streamed->withSound = _document->isAudioFile()
|| _document->isVideoFile()
|| _document->isVoiceMessage()
|| _document->isVideoMessage();
_streamed->withSound = _document
&& (_document->isAudioFile()
|| _document->isVideoFile()
|| _document->isVoiceMessage()
|| _document->isVideoMessage());
if (videoIsGifv()) {
if (videoIsGifOrUserpic()) {
_streamed->controls.hide();
} else {
refreshClipControllerGeometry();
@ -2430,17 +2510,25 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
}
void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
Expects(_document != nullptr);
Expects(_document || _photo);
if (error == Streaming::Error::NotStreamable) {
_document->setNotSupportsStreaming();
if (_document) {
_document->setNotSupportsStreaming();
} else {
_photo->setVideoPlaybackFailed();
}
} else if (error == Streaming::Error::OpenFailed) {
_document->setInappPlaybackFailed();
if (_document) {
_document->setInappPlaybackFailed();
} else {
_photo->setVideoPlaybackFailed();
}
}
if (!_documentMedia->canBePlayed()) {
redisplayContent();
} else {
if (canInitStreaming()) {
updatePlaybackState();
} else {
redisplayContent();
}
}
@ -2543,7 +2631,7 @@ void OverlayWidget::initThemePreview() {
}
void OverlayWidget::refreshClipControllerGeometry() {
if (!_streamed || videoIsGifv()) {
if (!_streamed || videoIsGifOrUserpic()) {
return;
}
@ -2578,7 +2666,7 @@ void OverlayWidget::playbackControlsFromFullScreen() {
}
void OverlayWidget::playbackControlsToPictureInPicture() {
if (!videoIsGifv()) {
if (!videoIsGifOrUserpic()) {
switchToPip();
}
}
@ -2608,7 +2696,7 @@ void OverlayWidget::playbackPauseResume() {
_streamed->resumeOnCallEnd = false;
if (_streamed->instance.player().failed()) {
clearStreaming();
if (!_documentMedia->canBePlayed() || !initStreaming()) {
if (!canInitStreaming() || !initStreaming()) {
redisplayContent();
}
} else if (_streamed->instance.player().finished()
@ -2627,7 +2715,6 @@ void OverlayWidget::playbackPauseResume() {
void OverlayWidget::restartAtSeekPosition(crl::time position) {
Expects(_streamed != nullptr);
Expects(_document != nullptr);
if (videoShown()) {
_streamed->instance.saveFrameToCover();
@ -2638,11 +2725,12 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
}
auto options = Streaming::PlaybackOptions();
options.position = position;
options.audioId = AudioMsgId(_document, _msgid);
if (!_streamed->withSound) {
options.mode = Streaming::Mode::Video;
options.loop = true;
} else {
Assert(_document != nullptr);
options.audioId = AudioMsgId(_document, _msgid);
options.speed = Core::App().settings().videoPlaybackSpeed();
if (_pip) {
_pip = nullptr;
@ -2706,7 +2794,7 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
Core::App().settings().setVideoPlaybackSpeed(speed);
Core::App().saveSettingsDelayed();
}
if (_streamed && !videoIsGifv()) {
if (_streamed && !videoIsGifOrUserpic()) {
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
_streamed->instance.setSpeed(speed);
}
@ -2743,7 +2831,7 @@ void OverlayWidget::switchToPip() {
void OverlayWidget::playbackToggleFullScreen() {
Expects(_streamed != nullptr);
if (!videoShown() || (videoIsGifv() && !_fullScreenVideo)) {
if (!videoShown() || (videoIsGifOrUserpic() && !_fullScreenVideo)) {
return;
}
_fullScreenVideo = !_fullScreenVideo;
@ -2797,7 +2885,7 @@ void OverlayWidget::playbackPauseMusic() {
void OverlayWidget::updatePlaybackState() {
Expects(_streamed != nullptr);
if (videoIsGifv()) {
if (videoIsGifOrUserpic()) {
return;
}
const auto state = _streamed->instance.player().prepareLegacyState();

View File

@ -283,7 +283,7 @@ private:
void refreshClipControllerGeometry();
void refreshCaptionGeometry();
[[nodiscard]] bool initStreaming(bool continueStreaming = false);
bool initStreaming(bool continueStreaming = false);
void startStreamingPlayer();
void initStreamingThumbnail();
void streamingReady(Streaming::Information &&info);
@ -346,7 +346,7 @@ private:
void applyVideoSize();
[[nodiscard]] bool videoShown() const;
[[nodiscard]] QSize videoSize() const;
[[nodiscard]] bool videoIsGifv() const;
[[nodiscard]] bool videoIsGifOrUserpic() const;
[[nodiscard]] QImage videoFrame() const;
[[nodiscard]] QImage videoFrameForDirectPaint() const;
[[nodiscard]] QImage transformVideoFrame(QImage frame) const;
@ -356,6 +356,7 @@ private:
void paintTransformedVideoFrame(Painter &p);
void paintTransformedStaticContent(Painter &p);
void clearStreaming(bool savePosition = true);
bool canInitStreaming() const;
QBrush _transparentBrush;

View File

@ -21,6 +21,8 @@ namespace {
constexpr auto kDocumentBaseCacheTag = 0x0000000000010000ULL;
constexpr auto kDocumentBaseCacheMask = 0x000000000000FF00ULL;
constexpr auto kPhotoBaseCacheTag = 0x0000000000020000ULL;
constexpr auto kPhotoBaseCacheMask = 0x000000000000FF00ULL;
constexpr auto kSerializeTypeShift = quint8(0x08);
constexpr auto kNonStorageLocationToken = quint8(0x10);
const auto kInMediaCacheLocation = QString("*media_cache*");
@ -405,9 +407,6 @@ Storage::Cache::Key StorageFileLocation::cacheKey() 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) {
case Type::Document: {
const auto high = kDocumentBaseCacheTag
@ -425,6 +424,18 @@ Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
| ((uint64(_dcId) & 0xFFULL) << 8)
| (_volumeId >> 56);
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 };
}
@ -432,7 +443,6 @@ Storage::Cache::Key StorageFileLocation::bigFileBaseCacheKey() const {
case Type::PeerPhoto:
case Type::Encrypted:
case Type::Secure:
case Type::Photo:
case Type::Takeout:
Unexpected("Not implemented file location type.");

View File

@ -611,9 +611,9 @@ inline bool operator>=(
struct ImageWithLocation {
ImageLocation location;
int bytesCount = 0;
QByteArray bytes;
QImage preloaded;
int bytesCount = 0;
};
InMemoryKey inMemoryKey(const StorageFileLocation &location);

View File

@ -51,8 +51,8 @@ ImageWithLocation FromPhotoSize(
data.vtype())) },
data.vw().v,
data.vh().v),
.bytes = bytes,
.bytesCount = bytes.size(),
.bytes = bytes
};
}, [&](const MTPDphotoStrippedSize &data) {
return ImageWithLocation();
@ -69,8 +69,8 @@ ImageWithLocation FromPhotoSize(
// data.vtype())) },
// width, // ???
// height), // ???
// .bytes = bytes,
// .bytesCount = bytes.size(),
// .bytes = bytes
//};
}, [&](const MTPDphotoSizeEmpty &) {
return ImageWithLocation();
@ -110,8 +110,8 @@ ImageWithLocation FromPhotoSize(
data.vtype())) },
data.vw().v,
data.vh().v),
.bytes = bytes,
.bytesCount = bytes.size(),
.bytes = bytes
};
}, [&](const MTPDphotoStrippedSize &data) {
return ImageWithLocation();
@ -128,8 +128,8 @@ ImageWithLocation FromPhotoSize(
// data.vtype())) },
// width, // ???
// height), // ???
// .bytes = bytes,
// .bytesCount = bytes.size(),
// .bytes = bytes
//};
}, [&](const MTPDphotoSizeEmpty &) {
return ImageWithLocation();
@ -172,8 +172,8 @@ ImageWithLocation FromPhotoSize(
location.vlocal_id())) },
data.vw().v,
data.vh().v),
.bytes = bytes,
.bytesCount = bytes.size(),
.bytes = bytes
};
}, [&](const MTPDphotoStrippedSize &data) {
return ImageWithLocation();
@ -190,8 +190,8 @@ ImageWithLocation FromPhotoSize(
// data.vtype())) },
// width, // ???
// height), // ???
// .bytes = bytes,
// .bytesCount = bytes.size(),
// .bytes = bytes
//};
}, [&](const MTPDphotoSizeEmpty &) {
return ImageWithLocation();
@ -212,9 +212,9 @@ ImageWithLocation FromImageInMemory(
DownloadLocation{ InMemoryLocation{ bytes } },
image.width(),
image.height()),
.bytesCount = bytes.size(),
.bytes = bytes,
.preloaded = image
.preloaded = image,
.bytesCount = bytes.size(),
};
}
@ -263,7 +263,7 @@ ImageWithLocation FromVideoSize(
data.vtype())) },
data.vw().v,
data.vh().v),
.bytesCount = data.vsize().v
.bytesCount = data.vsize().v,
};
});
}
@ -285,7 +285,7 @@ ImageWithLocation FromVideoSize(
data.vtype())) },
data.vw().v,
data.vh().v),
.bytesCount = data.vsize().v
.bytesCount = data.vsize().v,
};
});
}