diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index eef46f7a8..e58d43331 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -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 { + 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() + ? std::make_unique( + &session().downloader(), + videoLocation().file().data.get_unchecked(), + videoByteSize(), + origin) + : nullptr; +} + PhotoClickHandler::PhotoClickHandler( not_null photo, FullMsgId context, diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index f048d821d..0f887e55c 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -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; // For now they return size of the 'large' image. int width() const; @@ -136,6 +157,8 @@ private: QByteArray _inlineThumbnailBytes; std::array _images; Data::CloudFile _video; + crl::time _videoStartTime = 0; + bool _videoPlaybackFailed = false; int32 _dc = 0; uint64 _access = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 994ebf66a..015ae44bd 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -190,6 +190,14 @@ std::vector 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::max()))); +} + } // namespace Session::Session(not_null session) @@ -2158,7 +2166,8 @@ not_null Session::processPhoto( small, thumbnail, large, - ImageWithLocation{}); + ImageWithLocation{}, + crl::time(0)); }, [&](const MTPDphotoEmpty &data) { return photo(data.vid().v); }); @@ -2175,7 +2184,8 @@ not_null 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 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 { 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 Session::document(DocumentId id) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index ae80d386b..cb91257bb 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -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 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 document, diff --git a/Telegram/SourceFiles/data/data_streaming.cpp b/Telegram/SourceFiles/data/data_streaming.cpp index 748600689..0b791261e 100644 --- a/Telegram/SourceFiles/data/data_streaming.cpp +++ b/Telegram/SourceFiles/data/data_streaming.cpp @@ -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 +template bool PruneDestroyedAndSet( base::flat_map< - not_null, + not_null, std::weak_ptr> &objects, - not_null document, + not_null data, const std::shared_ptr &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 owner) Streaming::~Streaming() = default; -std::shared_ptr Streaming::sharedReader( - not_null document, + +template +[[nodiscard]] std::shared_ptr Streaming::sharedReader( + base::flat_map, std::weak_ptr> &readers, + not_null 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( 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 +[[nodiscard]] std::shared_ptr Streaming::sharedDocument( + base::flat_map, std::weak_ptr> &documents, + base::flat_map, std::weak_ptr> &readers, + not_null 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(data, std::move(reader)); + if (!PruneDestroyedAndSet(documents, data, result)) { + documents.emplace_or_assign(data, result); + } + return result; +} + +std::shared_ptr Streaming::sharedReader( + not_null document, + FileOrigin origin, + bool forceRemoteLoader) { + return sharedReader(_fileReaders, document, origin, forceRemoteLoader); } std::shared_ptr Streaming::sharedDocument( not_null 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, 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::sharedReader( + not_null photo, + FileOrigin origin, + bool forceRemoteLoader) { + return sharedReader(_photoReaders, photo, origin, forceRemoteLoader); +} + +std::shared_ptr Streaming::sharedDocument( + not_null photo, + FileOrigin origin) { + return sharedDocument(_photoDocuments, _photoReaders, photo, origin); } void Streaming::keepAlive(not_null 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(); diff --git a/Telegram/SourceFiles/data/data_streaming.h b/Telegram/SourceFiles/data/data_streaming.h index 9f7112842..7a30bd7f7 100644 --- a/Telegram/SourceFiles/data/data_streaming.h +++ b/Telegram/SourceFiles/data/data_streaming.h @@ -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 document, FileOrigin origin); + [[nodiscard]] std::shared_ptr sharedReader( + not_null photo, + FileOrigin origin, + bool forceRemoteLoader = false); + [[nodiscard]] std::shared_ptr sharedDocument( + not_null photo, + FileOrigin origin); + void keepAlive(not_null document); private: void clearKeptAlive(); + template + [[nodiscard]] std::shared_ptr sharedReader( + base::flat_map, std::weak_ptr> &readers, + not_null data, + FileOrigin origin, + bool forceRemoteLoader = false); + + template + [[nodiscard]] std::shared_ptr sharedDocument( + base::flat_map, std::weak_ptr> &documents, + base::flat_map, std::weak_ptr> &readers, + not_null data, + FileOrigin origin); + const not_null _owner; - base::flat_map, std::weak_ptr> _readers; base::flat_map< not_null, - std::weak_ptr> _documents; + std::weak_ptr> _fileReaders; + base::flat_map< + not_null, + std::weak_ptr> _fileDocuments; + + base::flat_map, std::weak_ptr> _photoReaders; + base::flat_map< + not_null, + std::weak_ptr> _photoDocuments; base::flat_map, crl::time> _keptAlive; base::Timer _keptAliveTimer; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp index 0ce345893..398c87939 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp @@ -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 document, std::shared_ptr 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) -: Document(std::make_shared(std::move(loader)), nullptr) { +Document::Document( + not_null photo, + std::shared_ptr reader) +: Document(std::move(reader), nullptr, photo) { } -Document::Document(std::shared_ptr reader, DocumentData *document) +Document::Document(std::unique_ptr loader) +: Document(std::make_shared(std::move(loader)), nullptr, nullptr) { +} + +Document::Document( + std::shared_ptr 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 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); } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_document.h b/Telegram/SourceFiles/media/streaming/media_streaming_document.h index 2def4f870..1d4dfb160 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_document.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_document.h @@ -25,6 +25,9 @@ public: Document( not_null document, std::shared_ptr reader); + Document( + not_null photo, + std::shared_ptr reader); explicit Document(std::unique_ptr 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 data() const; [[nodiscard]] bool waitingShown() const; [[nodiscard]] float64 waitingOpacity() const; [[nodiscard]] Ui::RadialState waitingState() const; private: - Document(std::shared_ptr reader, DocumentData *document); + Document( + std::shared_ptr 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; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp index 77e818f12..782d7c40a 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp @@ -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 photo, + Data::FileOrigin origin, + Fn waitingCallback) +: Instance( + photo->owner().streaming().sharedDocument(photo, origin), + std::move(waitingCallback)) { +} + Instance::~Instance() { if (_shared) { unlockPlayer(); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h index 37332264a..a92b0c87e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h @@ -34,6 +34,10 @@ public: not_null document, Data::FileOrigin origin, Fn waitingCallback); + Instance( + not_null photo, + Data::FileOrigin origin, + Fn waitingCallback); ~Instance(); [[nodiscard]] bool valid() const; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 9952438ad..0c224817b 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -130,7 +130,7 @@ QWidget *PipDelegate::pipParentWidget() { return _parent; } -Images::Options VideoThumbOptions(not_null 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 controlsDelegate, Fn waitingCallback); + Streamed( + not_null photo, + Data::FileOrigin origin, + QWidget *controlsParent, + not_null controlsDelegate, + Fn waitingCallback); Streaming::Instance instance; PlaybackControls controls; @@ -277,6 +283,16 @@ OverlayWidget::Streamed::Streamed( , controls(controlsParent, controlsDelegate) { } +OverlayWidget::Streamed::Streamed( + not_null photo, + Data::FileOrigin origin, + QWidget *controlsParent, + not_null controlsDelegate, + Fn waitingCallback) +: instance(photo, origin, std::move(waitingCallback)) +, controls(controlsParent, controlsDelegate) { +} + OverlayWidget::PipWrap::PipWrap( QWidget *parent, not_null 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 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 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( - _document, - fileOrigin(), - this, - static_cast(this), - [=] { waitingAnimationCallback(); }); + Expects(_photo || _document); + + if (_document) { + _streamed = std::make_unique( + _document, + fileOrigin(), + this, + static_cast(this), + [=] { waitingAnimationCallback(); }); + } else { + _streamed = std::make_unique( + _photo, + fileOrigin(), + this, + static_cast(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(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index df627cdce..d10d6bfcf 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -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; diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index f9f765c39..a2a8362d6 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -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."); diff --git a/Telegram/SourceFiles/ui/image/image_location.h b/Telegram/SourceFiles/ui/image/image_location.h index 747912cb7..a477e876e 100644 --- a/Telegram/SourceFiles/ui/image/image_location.h +++ b/Telegram/SourceFiles/ui/image/image_location.h @@ -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); diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp index 1f2d97415..2c84b3b4f 100644 --- a/Telegram/SourceFiles/ui/image/image_location_factory.cpp +++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp @@ -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, }; }); }