diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 48833ccd6..392b4edf9 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -127,19 +127,23 @@ struct FrameRequest { return result; } - bool empty() const { + [[nodiscard]] bool empty() const { return resize.isEmpty(); } - bool operator==(const FrameRequest &other) const { + [[nodiscard]] bool operator==(const FrameRequest &other) const { return (resize == other.resize) && (outer == other.outer) && (radius == other.radius) && (corners == other.corners); } - bool operator!=(const FrameRequest &other) const { + [[nodiscard]] bool operator!=(const FrameRequest &other) const { return !(*this == other); } + + [[nodiscard]] bool goodFor(const FrameRequest &other) const { + return (*this == other) || (strict && !other.strict); + } }; } // namespace Streaming diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp index d2846c1c4..addf3251d 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp @@ -84,6 +84,7 @@ void Document::registerInstance(not_null instance) { void Document::unregisterInstance(not_null instance) { _instances.remove(instance); + _player.unregisterInstance(instance); } bool Document::waitingShown() const { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp index 82e1b2e04..88ad74fe2 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp @@ -137,7 +137,7 @@ void Instance::callWaitingCallback() { } QImage Instance::frame(const FrameRequest &request) const { - return player().frame(request); + return player().frame(request, this); } bool Instance::markFrameShown() { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index ee9c18f4f..d343f8125 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -809,10 +809,18 @@ QSize Player::videoSize() const { return _information.video.size; } -QImage Player::frame(const FrameRequest &request) const { +QImage Player::frame( + const FrameRequest &request, + const Instance *instance) const { Expects(_video != nullptr); - return _video->frame(request); + return _video->frame(request, instance); +} + +void Player::unregisterInstance(not_null instance) { + if (_video) { + _video->unregisterInstance(instance); + } } Media::Player::TrackState Player::prepareLegacyState() const { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.h b/Telegram/SourceFiles/media/streaming/media_streaming_player.h index 38a6bef7a..e5df3cd6e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.h @@ -29,6 +29,7 @@ class Reader; class File; class AudioTrack; class VideoTrack; +class Instance; class Player final : private FileDelegate { public: @@ -60,7 +61,10 @@ public: [[nodiscard]] rpl::producer fullInCache() const; [[nodiscard]] QSize videoSize() const; - [[nodiscard]] QImage frame(const FrameRequest &request) const; + [[nodiscard]] QImage frame( + const FrameRequest &request, + const Instance *instance = nullptr) const; + void unregisterInstance(not_null instance); bool markFrameShown(); [[nodiscard]] Media::Player::TrackState prepareLegacyState() const; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index a928555ce..1f9d37e0f 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -45,7 +45,10 @@ public: void interrupt(); void frameShown(); void addTimelineDelay(crl::time delayed); - void updateFrameRequest(const FrameRequest &request); + void updateFrameRequest( + const Instance *instance, + const FrameRequest &request); + void removeFrameRequest(const Instance *instance); private: enum class FrameResult { @@ -68,6 +71,8 @@ private: void readFrames(); [[nodiscard]] ReadEnoughState readEnoughFrames(crl::time trackTime); [[nodiscard]] FrameResult readFrame(not_null frame); + void fillRequests(not_null frame) const; + [[nodiscard]] QSize chooseOriginalResize() const; void presentFrameIfNeeded(); void callReady(); [[nodiscard]] bool loopAround(); @@ -98,7 +103,7 @@ private: crl::time _loopingShift = 0; rpl::event_stream<> _checkNextFrame; rpl::event_stream<> _waitingForData; - FrameRequest _request = FrameRequest::NonStrict(); + base::flat_map _requests; bool _queued = false; base::ConcurrentTimer _readFramesTimer; @@ -297,6 +302,36 @@ auto VideoTrackObject::readFrame(not_null frame) -> FrameResult { return FrameResult::Done; } +void VideoTrackObject::fillRequests(not_null frame) const { + auto i = frame->prepared.begin(); + for (const auto &[instance, request] : _requests) { + while (i != frame->prepared.end() && i->first < instance) { + i = frame->prepared.erase(i); + } + if (i == frame->prepared.end() || i->first > instance) { + i = frame->prepared.emplace(instance, request).first; + } + ++i; + } + while (i != frame->prepared.end()) { + i = frame->prepared.erase(i); + } +} + +QSize VideoTrackObject::chooseOriginalResize() const { + auto chosen = QSize(); + for (const auto &[_, request] : _requests) { + const auto byWidth = (request.resize.width() >= chosen.width()); + const auto byHeight = (request.resize.height() >= chosen.height()); + if (byWidth && byHeight) { + chosen = request.resize; + } else if (byWidth || byHeight) { + return QSize(); + } + } + return chosen; +} + void VideoTrackObject::presentFrameIfNeeded() { if (_pausedTime != kTimeUnknown || _resumedTime == kTimeUnknown) { return; @@ -304,19 +339,19 @@ void VideoTrackObject::presentFrameIfNeeded() { const auto rasterize = [&](not_null frame) { Expects(frame->position != kFinishedPosition); - frame->request = _request; + fillRequests(frame); frame->original = ConvertFrame( _stream, frame->decoded.get(), - frame->request.resize, + chooseOriginalResize(), std::move(frame->original)); if (frame->original.isNull()) { - frame->prepared = QImage(); + frame->prepared.clear(); fail(Error::InvalidData); return; } - VideoTrack::PrepareFrameByRequest(frame); + VideoTrack::PrepareFrameByRequests(frame); Ensures(VideoTrack::IsRasterized(frame)); }; @@ -405,8 +440,14 @@ void VideoTrackObject::addTimelineDelay(crl::time delayed) { _syncTimePoint.worldTime += delayed; } -void VideoTrackObject::updateFrameRequest(const FrameRequest &request) { - _request = request; +void VideoTrackObject::updateFrameRequest( + const Instance *instance, + const FrameRequest &request) { + _requests.emplace(instance, request); +} + +void VideoTrackObject::removeFrameRequest(const Instance *instance) { + _requests.remove(instance); } bool VideoTrackObject::tryReadFirstFrame(FFmpeg::Packet &&packet) { @@ -898,33 +939,75 @@ bool VideoTrack::markFrameShown() { return true; } -QImage VideoTrack::frame(const FrameRequest &request) { +QImage VideoTrack::frame( + const FrameRequest &request, + const Instance *instance) { const auto frame = _shared->frameForPaint(); - const auto changed = (frame->request != request) - && (request.strict || !frame->request.strict); + const auto i = frame->prepared.find(instance); + const auto none = (i == frame->prepared.end()); + const auto preparedFor = none + ? FrameRequest::NonStrict() + : i->second.request; + const auto changed = !preparedFor.goodFor(request); + const auto useRequest = changed ? request : preparedFor; if (changed) { - frame->request = request; _wrapped.with([=](Implementation &unwrapped) { - unwrapped.updateFrameRequest(request); + unwrapped.updateFrameRequest(instance, request); }); } - return PrepareFrameByRequest(frame, !changed); + if (GoodForRequest(frame->original, useRequest)) { + return frame->original; + } else if (changed || none || i->second.image.isNull()) { + const auto j = none + ? frame->prepared.emplace(instance, useRequest).first + : i; + if (frame->prepared.size() > 1) { + for (auto &[alreadyInstance, prepared] : frame->prepared) { + if (alreadyInstance != instance + && prepared.request == useRequest + && !prepared.image.isNull()) { + return prepared.image; + } + } + } + j->second.image = PrepareByRequest( + frame->original, + useRequest, + std::move(j->second.image)); + return j->second.image; + } + return i->second.image; } -QImage VideoTrack::PrepareFrameByRequest( - not_null frame, - bool useExistingPrepared) { +void VideoTrack::unregisterInstance(not_null instance) { + _wrapped.with([=](Implementation &unwrapped) { + unwrapped.removeFrameRequest(instance); + }); +} + +void VideoTrack::PrepareFrameByRequests(not_null frame) { Expects(!frame->original.isNull()); - if (GoodForRequest(frame->original, frame->request)) { - return frame->original; - } else if (frame->prepared.isNull() || !useExistingPrepared) { - frame->prepared = PrepareByRequest( - frame->original, - frame->request, - std::move(frame->prepared)); + const auto begin = frame->prepared.begin(); + const auto end = frame->prepared.end(); + for (auto i = begin; i != end; ++i) { + auto &prepared = i->second; + if (!GoodForRequest(frame->original, prepared.request)) { + auto j = begin; + for (; j != i; ++j) { + if (j->second.request == prepared.request) { + prepared.image = QImage(); + break; + } + } + if (j == i) { + prepared.image = PrepareByRequest( + frame->original, + prepared.request, + std::move(prepared.image)); + } + } } - return frame->prepared; } bool VideoTrack::IsDecoded(not_null frame) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h index 593d69682..aea570a57 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h @@ -18,6 +18,7 @@ constexpr auto kFrameDisplayTimeAlreadyDone = std::numeric_limits::max(); class VideoTrackObject; +class Instance; class VideoTrack final { public: @@ -53,7 +54,10 @@ public: void addTimelineDelay(crl::time delayed); bool markFrameShown(); [[nodiscard]] crl::time nextFrameDisplayTime() const; - [[nodiscard]] QImage frame(const FrameRequest &request); + [[nodiscard]] QImage frame( + const FrameRequest &request, + const Instance *instance); + void unregisterInstance(not_null instance); [[nodiscard]] rpl::producer<> checkNextFrame() const; [[nodiscard]] rpl::producer<> waitingForData() const; @@ -63,6 +67,13 @@ public: private: friend class VideoTrackObject; + struct Prepared { + Prepared(const FrameRequest &request) : request(request) { + } + + FrameRequest request = FrameRequest::NonStrict(); + QImage image; + }; struct Frame { FFmpeg::FramePointer decoded = FFmpeg::MakeFramePointer(); QImage original; @@ -70,8 +81,7 @@ private: crl::time displayed = kTimeUnknown; crl::time display = kTimeUnknown; - FrameRequest request = FrameRequest::NonStrict(); - QImage prepared; + base::flat_map prepared; }; class Shared { @@ -129,9 +139,7 @@ private: }; - static QImage PrepareFrameByRequest( - not_null frame, - bool useExistingPrepared = false); + static void PrepareFrameByRequests(not_null frame); [[nodiscard]] static bool IsDecoded(not_null frame); [[nodiscard]] static bool IsRasterized(not_null frame); [[nodiscard]] static bool IsStale( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 226ae00fb..35b7cee5f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -377,7 +377,7 @@ QImage OverlayWidget::videoFrame() const { // ? ImageRoundRadius::Ellipse // : ImageRoundRadius::None; return _streamed->instance.player().ready() - ? _streamed->instance.player().frame(request) + ? _streamed->instance.frame(request) : _streamed->instance.info().video.cover; }